diff --git a/dom/file/Blob.cpp b/dom/file/Blob.cpp index f59a049e396c..2b0b425ed613 100644 --- a/dom/file/Blob.cpp +++ b/dom/file/Blob.cpp @@ -5,7 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Blob.h" -#include "EmptyBlobImpl.h" #include "File.h" #include "MemoryBlobImpl.h" #include "mozilla/dom/BlobBinding.h" @@ -73,14 +72,6 @@ Blob* Blob::Create(nsISupports* aParent, BlobImpl* aImpl) { return aImpl->IsFile() ? new File(aParent, aImpl) : new Blob(aParent, aImpl); } -/* static */ -already_AddRefed Blob::CreateEmptyBlob(nsISupports* aParent, - const nsAString& aContentType) { - RefPtr blob = Blob::Create(aParent, new EmptyBlobImpl(aContentType)); - MOZ_ASSERT(!blob->mImpl->IsFile()); - return blob.forget(); -} - /* static */ already_AddRefed Blob::CreateStringBlob(nsISupports* aParent, const nsACString& aData, diff --git a/dom/file/Blob.h b/dom/file/Blob.h index cd7846a315f8..bc3e718bd184 100644 --- a/dom/file/Blob.h +++ b/dom/file/Blob.h @@ -50,9 +50,6 @@ class Blob : public nsIMutable, // This creates a Blob or a File based on the type of BlobImpl. static Blob* Create(nsISupports* aParent, BlobImpl* aImpl); - static already_AddRefed CreateEmptyBlob(nsISupports* aParent, - const nsAString& aContentType); - static already_AddRefed CreateStringBlob(nsISupports* aParent, const nsACString& aData, const nsAString& aContentType); diff --git a/dom/indexedDB/ActorsChild.cpp b/dom/indexedDB/ActorsChild.cpp index c913e111c1e9..7374885c7a98 100644 --- a/dom/indexedDB/ActorsChild.cpp +++ b/dom/indexedDB/ActorsChild.cpp @@ -19,9 +19,11 @@ #include "IDBTransaction.h" #include "IndexedDatabase.h" #include "IndexedDatabaseInlines.h" +#include #include "mozilla/BasicEvents.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/Maybe.h" +#include "mozilla/SnappyUncompressInputStream.h" #include "mozilla/TypeTraits.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" @@ -73,6 +75,16 @@ namespace dom { namespace indexedDB { +namespace { + +/******************************************************************************* + * Constants + ******************************************************************************/ + +const uint32_t kFileCopyBufferSize = 32768; + +} // namespace + /******************************************************************************* * ThreadLocal ******************************************************************************/ @@ -496,14 +508,11 @@ class PermissionRequestMainProcessHelper final : public PermissionRequestBase { void DeserializeStructuredCloneFiles( IDBDatabase* aDatabase, const nsTArray& aSerializedFiles, - const nsTArray>* aModuleSet, - nsTArray& aFiles) { - MOZ_ASSERT_IF(aModuleSet, !aModuleSet->IsEmpty()); + bool aForPreprocess, nsTArray& aFiles) { MOZ_ASSERT(aFiles.IsEmpty()); + MOZ_ASSERT_IF(aForPreprocess, aSerializedFiles.Length() == 1); if (!aSerializedFiles.IsEmpty()) { - uint32_t moduleIndex = 0; - const uint32_t count = aSerializedFiles.Length(); aFiles.SetCapacity(count); @@ -511,6 +520,9 @@ void DeserializeStructuredCloneFiles( const SerializedStructuredCloneFile& serializedFile = aSerializedFiles[index]; + MOZ_ASSERT_IF(aForPreprocess, serializedFile.type() == + StructuredCloneFile::eStructuredClone); + const BlobOrMutableFile& blobOrMutableFile = serializedFile.file(); switch (serializedFile.type()) { @@ -579,55 +591,47 @@ void DeserializeStructuredCloneFiles( } case StructuredCloneFile::eStructuredClone: { - StructuredCloneFile* file = aFiles.AppendElement(); - MOZ_ASSERT(file); + if (aForPreprocess) { + MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::TIPCBlob); - file->mType = StructuredCloneFile::eStructuredClone; + const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob(); - break; - } + RefPtr blobImpl = IPCBlobUtils::Deserialize(ipcBlob); + MOZ_ASSERT(blobImpl); - case StructuredCloneFile::eWasmBytecode: { - if (aModuleSet) { + RefPtr blob = + Blob::Create(aDatabase->GetOwnerGlobal(), blobImpl); + + StructuredCloneFile* file = aFiles.AppendElement(); + MOZ_ASSERT(file); + + file->mType = StructuredCloneFile::eStructuredClone; + file->mBlob.swap(blob); + } else { MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t); StructuredCloneFile* file = aFiles.AppendElement(); MOZ_ASSERT(file); - file->mType = StructuredCloneFile::eWasmBytecode; - - MOZ_ASSERT(moduleIndex < aModuleSet->Length()); - file->mWasmModule = aModuleSet->ElementAt(moduleIndex); - - moduleIndex++; - - break; + file->mType = StructuredCloneFile::eStructuredClone; } - MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::TIPCBlob); - - const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob(); - - RefPtr blobImpl = IPCBlobUtils::Deserialize(ipcBlob); - MOZ_ASSERT(blobImpl); - - RefPtr blob = - Blob::Create(aDatabase->GetOwnerGlobal(), blobImpl); - - StructuredCloneFile* file = aFiles.AppendElement(); - MOZ_ASSERT(file); - - file->mType = StructuredCloneFile::eWasmBytecode; - file->mBlob.swap(blob); - break; } + case StructuredCloneFile::eWasmBytecode: case StructuredCloneFile::eWasmCompiled: { + MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t); + StructuredCloneFile* file = aFiles.AppendElement(); MOZ_ASSERT(file); - file->mType = StructuredCloneFile::eWasmCompiled; + file->mType = serializedFile.type(); + + // Don't set mBlob, support for storing WebAssembly.Modules has been + // removed in bug 1469395. Support for de-serialization of + // WebAssembly.Modules has been removed in bug 1561876. Full removal + // is tracked in bug 1487479. break; } @@ -1284,29 +1288,42 @@ class BackgroundRequestChild::PreprocessHelper final : public CancelableRunnable, public nsIInputStreamCallback, public nsIFileMetadataCallback { + enum class State { + // Just created on the owning thread, dispatched to the thread pool. Next + // step is either Finishing if stream was ready to be read or + // WaitingForStreamReady if the stream is not ready. + Initial, + + // Waiting for stream to be ready on a thread pool thread. Next state is + // Finishing. + WaitingForStreamReady, + + // Waiting to finish/finishing on the owning thread. Next step is Completed. + Finishing, + + // All done. + Completed + }; + nsCOMPtr mOwningEventTarget; - nsTArray> mStreams; - nsTArray> mModuleSet; - BackgroundRequestChild* mActor; - - // This is populated when the processing of the stream runs. - PRFileDesc* mCurrentBytecodeFileDesc; - RefPtr mTaskQueue; nsCOMPtr mTaskQueueEventTarget; - - uint32_t mModuleSetIndex; + nsCOMPtr mStream; + UniquePtr mCloneData; + BackgroundRequestChild* mActor; + uint32_t mCloneDataIndex; nsresult mResultCode; + State mState; public: - PreprocessHelper(uint32_t aModuleSetIndex, BackgroundRequestChild* aActor) + PreprocessHelper(uint32_t aCloneDataIndex, BackgroundRequestChild* aActor) : CancelableRunnable( "indexedDB::BackgroundRequestChild::PreprocessHelper"), mOwningEventTarget(aActor->GetActorEventTarget()), mActor(aActor), - mCurrentBytecodeFileDesc(nullptr), - mModuleSetIndex(aModuleSetIndex), - mResultCode(NS_OK) { + mCloneDataIndex(aCloneDataIndex), + mResultCode(NS_OK), + mState(State::Initial) { AssertIsOnOwningThread(); MOZ_ASSERT(aActor); aActor->AssertIsOnOwningThread(); @@ -1328,33 +1345,29 @@ class BackgroundRequestChild::PreprocessHelper final mActor = nullptr; } - nsresult Init(const nsTArray& aFiles); + nsresult Init(const StructuredCloneFile& aFile); nsresult Dispatch(); private: ~PreprocessHelper() { + MOZ_ASSERT(mState == State::Initial || mState == State::Completed); + if (mTaskQueue) { mTaskQueue->BeginShutdown(); } } - void RunOnOwningThread(); + nsresult Start(); - void ProcessCurrentStream(); + nsresult ProcessStream(); - nsresult WaitForStreamReady(nsIInputStream* aInputStream); - - void ContinueWithStatus(nsresult aStatus); - - nsresult DataIsReady(nsIInputStream* aInputStream); + void Finish(); NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIRUNNABLE NS_DECL_NSIINPUTSTREAMCALLBACK NS_DECL_NSIFILEMETADATACALLBACK - - virtual nsresult Cancel() override; }; /******************************************************************************* @@ -2537,7 +2550,7 @@ BackgroundRequestChild::BackgroundRequestChild(IDBRequest* aRequest) : BackgroundRequestChildBase(aRequest), mTransaction(aRequest->GetTransaction()), mRunningPreprocessHelpers(0), - mCurrentModuleSetIndex(0), + mCurrentCloneDataIndex(0), mPreprocessResultCode(NS_OK), mGetAll(false) { MOZ_ASSERT(mTransaction); @@ -2575,27 +2588,27 @@ void BackgroundRequestChild::MaybeSendContinue() { } void BackgroundRequestChild::OnPreprocessFinished( - uint32_t aModuleSetIndex, nsTArray>& aModuleSet) { + uint32_t aCloneDataIndex, UniquePtr aCloneData) { AssertIsOnOwningThread(); - MOZ_ASSERT(aModuleSetIndex < mPreprocessHelpers.Length()); - MOZ_ASSERT(!aModuleSet.IsEmpty()); - MOZ_ASSERT(mPreprocessHelpers[aModuleSetIndex]); - MOZ_ASSERT(mModuleSets[aModuleSetIndex].IsEmpty()); + MOZ_ASSERT(aCloneDataIndex < mPreprocessHelpers.Length()); + MOZ_ASSERT(aCloneData); + MOZ_ASSERT(mPreprocessHelpers[aCloneDataIndex]); + MOZ_ASSERT(!mCloneDatas[aCloneDataIndex]); - mModuleSets[aModuleSetIndex].SwapElements(aModuleSet); + mCloneDatas[aCloneDataIndex] = std::move(aCloneData); MaybeSendContinue(); - mPreprocessHelpers[aModuleSetIndex] = nullptr; + mPreprocessHelpers[aCloneDataIndex] = nullptr; } -void BackgroundRequestChild::OnPreprocessFailed(uint32_t aModuleSetIndex, +void BackgroundRequestChild::OnPreprocessFailed(uint32_t aCloneDataIndex, nsresult aErrorCode) { AssertIsOnOwningThread(); - MOZ_ASSERT(aModuleSetIndex < mPreprocessHelpers.Length()); + MOZ_ASSERT(aCloneDataIndex < mPreprocessHelpers.Length()); MOZ_ASSERT(NS_FAILED(aErrorCode)); - MOZ_ASSERT(mPreprocessHelpers[aModuleSetIndex]); - MOZ_ASSERT(mModuleSets[aModuleSetIndex].IsEmpty()); + MOZ_ASSERT(mPreprocessHelpers[aCloneDataIndex]); + MOZ_ASSERT(!mCloneDatas[aCloneDataIndex]); if (NS_SUCCEEDED(mPreprocessResultCode)) { mPreprocessResultCode = aErrorCode; @@ -2603,18 +2616,18 @@ void BackgroundRequestChild::OnPreprocessFailed(uint32_t aModuleSetIndex, MaybeSendContinue(); - mPreprocessHelpers[aModuleSetIndex] = nullptr; + mPreprocessHelpers[aCloneDataIndex] = nullptr; } -const nsTArray>* -BackgroundRequestChild::GetNextModuleSet(const StructuredCloneReadInfo& aInfo) { - if (!aInfo.mHasPreprocessInfo) { - return nullptr; - } +UniquePtr BackgroundRequestChild::GetNextCloneData() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mCurrentCloneDataIndex < mCloneDatas.Length()); + MOZ_ASSERT(mCloneDatas[mCurrentCloneDataIndex]); - MOZ_ASSERT(mCurrentModuleSetIndex < mModuleSets.Length()); - MOZ_ASSERT(!mModuleSets[mCurrentModuleSetIndex].IsEmpty()); - return &mModuleSets[mCurrentModuleSetIndex++]; + UniquePtr cloneData; + mCloneDatas[mCurrentCloneDataIndex++].swap(cloneData); + + return cloneData; } void BackgroundRequestChild::HandleResponse(nsresult aResponse) { @@ -2653,9 +2666,14 @@ void BackgroundRequestChild::HandleResponse( StructuredCloneReadInfo cloneReadInfo(std::move(serializedCloneInfo)); DeserializeStructuredCloneFiles(mTransaction->Database(), aResponse.files(), - GetNextModuleSet(cloneReadInfo), + /* aForPreprocess */ false, cloneReadInfo.mFiles); + if (cloneReadInfo.mHasPreprocessInfo) { + UniquePtr cloneData = GetNextCloneData(); + cloneReadInfo.mData = std::move(*cloneData); + } + ResultHelper helper(mRequest, mTransaction, &cloneReadInfo); DispatchSuccessEvent(&helper); @@ -2687,9 +2705,14 @@ void BackgroundRequestChild::HandleResponse( // Get the files nsTArray files; DeserializeStructuredCloneFiles(database, serializedCloneInfo.files(), - GetNextModuleSet(*cloneReadInfo), files); + /* aForPreprocess */ false, files); cloneReadInfo->mFiles = std::move(files); + + if (cloneReadInfo->mHasPreprocessInfo) { + UniquePtr cloneData = GetNextCloneData(); + cloneReadInfo->mData = std::move(*cloneData); + } } } @@ -2717,7 +2740,7 @@ void BackgroundRequestChild::HandleResponse(uint64_t aResponse) { } nsresult BackgroundRequestChild::HandlePreprocess( - const WasmModulePreprocessInfo& aPreprocessInfo) { + const PreprocessInfo& aPreprocessInfo) { AssertIsOnOwningThread(); IDBDatabase* database = mTransaction->Database(); @@ -2725,13 +2748,15 @@ nsresult BackgroundRequestChild::HandlePreprocess( mPreprocessHelpers.SetLength(1); nsTArray files; - DeserializeStructuredCloneFiles(database, aPreprocessInfo.files(), nullptr, - files); + DeserializeStructuredCloneFiles(database, aPreprocessInfo.files(), + /* aForPreprocess */ true, files); + + MOZ_ASSERT(files.Length() == 1); RefPtr& preprocessHelper = mPreprocessHelpers[0]; preprocessHelper = new PreprocessHelper(0, this); - nsresult rv = preprocessHelper->Init(files); + nsresult rv = preprocessHelper->Init(files[0]); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -2743,13 +2768,13 @@ nsresult BackgroundRequestChild::HandlePreprocess( mRunningPreprocessHelpers++; - mModuleSets.SetLength(1); + mCloneDatas.SetLength(1); return NS_OK; } nsresult BackgroundRequestChild::HandlePreprocess( - const nsTArray& aPreprocessInfos) { + const nsTArray& aPreprocessInfos) { AssertIsOnOwningThread(); IDBDatabase* database = mTransaction->Database(); @@ -2762,16 +2787,18 @@ nsresult BackgroundRequestChild::HandlePreprocess( // and has the potential to cause some annoying browser hiccups. // Consider using a single thread or a very small threadpool. for (uint32_t index = 0; index < count; index++) { - const WasmModulePreprocessInfo& preprocessInfo = aPreprocessInfos[index]; + const PreprocessInfo& preprocessInfo = aPreprocessInfos[index]; nsTArray files; - DeserializeStructuredCloneFiles(database, preprocessInfo.files(), nullptr, - files); + DeserializeStructuredCloneFiles(database, preprocessInfo.files(), + /* aForPreprocess */ true, files); + + MOZ_ASSERT(files.Length() == 1); RefPtr& preprocessHelper = mPreprocessHelpers[index]; preprocessHelper = new PreprocessHelper(index, this); - nsresult rv = preprocessHelper->Init(files); + nsresult rv = preprocessHelper->Init(files[0]); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -2784,7 +2811,7 @@ nsresult BackgroundRequestChild::HandlePreprocess( mRunningPreprocessHelpers++; } - mModuleSets.SetLength(count); + mCloneDatas.SetLength(count); mGetAll = true; @@ -2949,51 +2976,44 @@ mozilla::ipc::IPCResult BackgroundRequestChild::RecvPreprocess( } nsresult BackgroundRequestChild::PreprocessHelper::Init( - const nsTArray& aFiles) { + const StructuredCloneFile& aFile) { AssertIsOnOwningThread(); - MOZ_ASSERT(!aFiles.IsEmpty()); + MOZ_ASSERT(aFile.mBlob); + MOZ_ASSERT(aFile.mType == StructuredCloneFile::eStructuredClone); + MOZ_ASSERT(mState == State::Initial); - nsTArray> streams; - for (uint32_t index = 0; index < aFiles.Length(); index++) { - const StructuredCloneFile& bytecodeFile = aFiles[index]; + // The stream transport service is used for asynchronous processing. It has a + // threadpool with a high cap of 25 threads. Fortunately, the service can be + // used on workers too. + nsCOMPtr target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); - MOZ_ASSERT(bytecodeFile.mType == StructuredCloneFile::eWasmBytecode); - MOZ_ASSERT(bytecodeFile.mBlob); + // We use a TaskQueue here in order to be sure that the events are dispatched + // in the correct order. This is not guaranteed in case we use the I/O thread + // directly. + mTaskQueue = new TaskQueue(target.forget()); + mTaskQueueEventTarget = mTaskQueue->WrapAsEventTarget(); - ErrorResult errorResult; + ErrorResult errorResult; - nsCOMPtr bytecodeStream; - bytecodeFile.mBlob->CreateInputStream(getter_AddRefs(bytecodeStream), - errorResult); - if (NS_WARN_IF(errorResult.Failed())) { - return errorResult.StealNSResult(); - } - - streams.AppendElement(bytecodeStream); + nsCOMPtr stream; + aFile.mBlob->CreateInputStream(getter_AddRefs(stream), errorResult); + if (NS_WARN_IF(errorResult.Failed())) { + return errorResult.StealNSResult(); } - mStreams = std::move(streams); + mStream = std::move(stream); + + mCloneData = MakeUnique( + JS::StructuredCloneScope::DifferentProcessForIndexedDB); return NS_OK; } nsresult BackgroundRequestChild::PreprocessHelper::Dispatch() { AssertIsOnOwningThread(); - - if (!mTaskQueue) { - // The stream transport service is used for asynchronous processing. It has - // a threadpool with a high cap of 25 threads. Fortunately, the service can - // be used on workers too. - nsCOMPtr target = - do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); - MOZ_ASSERT(target); - - // We use a TaskQueue here in order to be sure that the events are - // dispatched in the correct order. This is not guaranteed in case we use - // the I/O thread directly. - mTaskQueue = new TaskQueue(target.forget()); - mTaskQueueEventTarget = mTaskQueue->WrapAsEventTarget(); - } + MOZ_ASSERT(mState == State::Initial); nsresult rv = mTaskQueueEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -3003,105 +3023,16 @@ nsresult BackgroundRequestChild::PreprocessHelper::Dispatch() { return NS_OK; } -void BackgroundRequestChild::PreprocessHelper::RunOnOwningThread() { - AssertIsOnOwningThread(); - - if (mActor) { - if (NS_SUCCEEDED(mResultCode)) { - mActor->OnPreprocessFinished(mModuleSetIndex, mModuleSet); - - MOZ_ASSERT(mModuleSet.IsEmpty()); - } else { - mActor->OnPreprocessFailed(mModuleSetIndex, mResultCode); - } - } -} - -class MemUnmap { - uint32_t mSize = 0; - - public: - MemUnmap() = default; - explicit MemUnmap(uint32_t aSize) : mSize(aSize) {} - - void operator()(uint8_t* aP) { - MOZ_ASSERT(mSize); - PR_MemUnmap(aP, mSize); - } -}; - -using UniqueMapping = UniquePtr; - -static UniqueMapping MapFile(PRFileDesc* aFile, PRFileInfo* aInfo) { - if (PR_GetOpenFileInfo(aFile, aInfo) != PR_SUCCESS) { - return nullptr; - } - - PRFileMap* map = PR_CreateFileMap(aFile, aInfo->size, PR_PROT_READONLY); - if (!map) { - return nullptr; - } - - // PRFileMap objects do not need to be kept alive after the memory has been - // mapped, so unconditionally close the PRFileMap, regardless of whether - // PR_MemMap succeeds. - uint8_t* memory = (uint8_t*)PR_MemMap(map, 0, aInfo->size); - PR_CloseFileMap(map); - return UniqueMapping(memory, MemUnmap(aInfo->size)); -} - -void BackgroundRequestChild::PreprocessHelper::ProcessCurrentStream() { +nsresult BackgroundRequestChild::PreprocessHelper::Start() { MOZ_ASSERT(!IsOnOwningThread()); - MOZ_ASSERT(!mStreams.IsEmpty()); + MOZ_ASSERT(mStream); + MOZ_ASSERT(mState == State::Initial); - // We still don't have the current bytecode FileDesc. - if (!mCurrentBytecodeFileDesc) { - const nsCOMPtr& bytecodeStream = mStreams[0]; - MOZ_ASSERT(bytecodeStream); + nsresult rv; - mCurrentBytecodeFileDesc = GetFileDescriptorFromStream(bytecodeStream); - if (!mCurrentBytecodeFileDesc) { - nsresult rv = WaitForStreamReady(bytecodeStream); - if (NS_WARN_IF(NS_FAILED(rv))) { - ContinueWithStatus(rv); - } - return; - } - } - - MOZ_ASSERT(mCurrentBytecodeFileDesc); - - PRFileInfo bytecodeInfo; - UniqueMapping bytecodeMapping = - MapFile(mCurrentBytecodeFileDesc, &bytecodeInfo); - if (NS_WARN_IF(!bytecodeMapping)) { - ContinueWithStatus(NS_ERROR_FAILURE); - return; - } - - RefPtr module = - JS::DeserializeWasmModule(bytecodeMapping.get(), bytecodeInfo.size); - if (NS_WARN_IF(!module)) { - ContinueWithStatus(NS_ERROR_FAILURE); - return; - } - - mModuleSet.AppendElement(module); - mStreams.RemoveElementAt(0); - - ContinueWithStatus(NS_OK); -} - -nsresult BackgroundRequestChild::PreprocessHelper::WaitForStreamReady( - nsIInputStream* aInputStream) { - MOZ_ASSERT(!IsOnOwningThread()); - MOZ_ASSERT(aInputStream); - - nsCOMPtr asyncFileMetadata = - do_QueryInterface(aInputStream); - if (asyncFileMetadata) { - nsresult rv = - asyncFileMetadata->AsyncFileMetadataWait(this, mTaskQueueEventTarget); + PRFileDesc* fileDesc = GetFileDescriptorFromStream(mStream); + if (fileDesc) { + rv = ProcessStream(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -3109,12 +3040,24 @@ nsresult BackgroundRequestChild::PreprocessHelper::WaitForStreamReady( return NS_OK; } - nsCOMPtr asyncStream = do_QueryInterface(aInputStream); + mState = State::WaitingForStreamReady; + + nsCOMPtr asyncFileMetadata = do_QueryInterface(mStream); + if (asyncFileMetadata) { + rv = asyncFileMetadata->AsyncFileMetadataWait(this, mTaskQueueEventTarget); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + nsCOMPtr asyncStream = do_QueryInterface(mStream); if (!asyncStream) { return NS_ERROR_NO_INTERFACE; } - nsresult rv = asyncStream->AsyncWait(this, 0, 0, mTaskQueueEventTarget); + rv = asyncStream->AsyncWait(this, 0, 0, mTaskQueueEventTarget); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -3122,33 +3065,75 @@ nsresult BackgroundRequestChild::PreprocessHelper::WaitForStreamReady( return NS_OK; } -void BackgroundRequestChild::PreprocessHelper::ContinueWithStatus( - nsresult aStatus) { +nsresult BackgroundRequestChild::PreprocessHelper::ProcessStream() { MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mStream); + MOZ_ASSERT(mState == State::Initial || + mState == State::WaitingForStreamReady); - // Let's reset the value for the next operation. - mCurrentBytecodeFileDesc = nullptr; + // We need to get the internal stream (which is an nsFileInputStream) because + // SnappyUncompressInputStream doesn't support reading from async input + // streams. - nsCOMPtr eventTarget; + nsCOMPtr blobInputStream = do_QueryInterface(mStream); + MOZ_ASSERT(blobInputStream); - if (NS_WARN_IF(NS_FAILED(aStatus))) { - // If the previous operation failed, we don't continue the processing of the - // other streams. - MOZ_ASSERT(mResultCode == NS_OK); - mResultCode = aStatus; + nsCOMPtr internalInputStream = + blobInputStream->GetInternalStream(); + MOZ_ASSERT(internalInputStream); - eventTarget = mOwningEventTarget; - } else if (mStreams.IsEmpty()) { - // If all the streams have been processed, we can go back to the owning - // thread. - eventTarget = mOwningEventTarget; - } else { - // Continue the processing. - eventTarget = mTaskQueueEventTarget; + RefPtr snappyInputStream = + new SnappyUncompressInputStream(internalInputStream); + + nsresult rv; + + do { + char buffer[kFileCopyBufferSize]; + + uint32_t numRead; + rv = snappyInputStream->Read(buffer, sizeof(buffer), &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (!numRead) { + break; + } + + if (NS_WARN_IF(!mCloneData->AppendBytes(buffer, numRead))) { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + } while (true); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } - nsresult rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL); - Unused << NS_WARN_IF(NS_FAILED(rv)); + mState = State::Finishing; + + rv = mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void BackgroundRequestChild::PreprocessHelper::Finish() { + AssertIsOnOwningThread(); + + if (mActor) { + if (NS_SUCCEEDED(mResultCode)) { + mActor->OnPreprocessFinished(mCloneDataIndex, std::move(mCloneData)); + + MOZ_ASSERT(!mCloneData); + } else { + mActor->OnPreprocessFailed(mCloneDataIndex, mResultCode); + } + } + + mState = State::Completed; } NS_IMPL_ISUPPORTS_INHERITED(BackgroundRequestChild::PreprocessHelper, @@ -3157,10 +3142,40 @@ NS_IMPL_ISUPPORTS_INHERITED(BackgroundRequestChild::PreprocessHelper, NS_IMETHODIMP BackgroundRequestChild::PreprocessHelper::Run() { - if (IsOnOwningThread()) { - RunOnOwningThread(); - } else { - ProcessCurrentStream(); + nsresult rv; + + switch (mState) { + case State::Initial: + rv = Start(); + break; + + case State::WaitingForStreamReady: + rv = ProcessStream(); + break; + + case State::Finishing: + Finish(); + return NS_OK; + + default: + MOZ_CRASH("Bad state!"); + } + + if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) { + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = rv; + } + + // Must set mState before dispatching otherwise we will race with the owning + // thread. + mState = State::Finishing; + + if (IsOnOwningThread()) { + Finish(); + } else { + MOZ_ALWAYS_SUCCEEDS( + mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); + } } return NS_OK; @@ -3169,42 +3184,25 @@ BackgroundRequestChild::PreprocessHelper::Run() { NS_IMETHODIMP BackgroundRequestChild::PreprocessHelper::OnInputStreamReady( nsIAsyncInputStream* aStream) { - return DataIsReady(aStream); + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mState == State::WaitingForStreamReady); + + MOZ_ALWAYS_SUCCEEDS(this->Run()); + + return NS_OK; } NS_IMETHODIMP BackgroundRequestChild::PreprocessHelper::OnFileMetadataReady( nsIAsyncFileMetadata* aObject) { - nsCOMPtr stream = do_QueryInterface(aObject); - MOZ_ASSERT(stream, "It was a stream before!"); - - return DataIsReady(stream); -} - -nsresult BackgroundRequestChild::PreprocessHelper::DataIsReady( - nsIInputStream* aStream) { MOZ_ASSERT(!IsOnOwningThread()); - MOZ_ASSERT(aStream); - MOZ_ASSERT(!mStreams.IsEmpty()); + MOZ_ASSERT(mState == State::WaitingForStreamReady); - // We still don't have the current bytecode FileDesc. - if (!mCurrentBytecodeFileDesc) { - mCurrentBytecodeFileDesc = GetFileDescriptorFromStream(aStream); - if (!mCurrentBytecodeFileDesc) { - ContinueWithStatus(NS_ERROR_FAILURE); - return NS_OK; - } + MOZ_ALWAYS_SUCCEEDS(this->Run()); - // Let's continue with the processing of the current stream. - ProcessCurrentStream(); - return NS_OK; - } - - MOZ_CRASH("If we have both fileDescs why are we here?"); + return NS_OK; } -nsresult BackgroundRequestChild::PreprocessHelper::Cancel() { return NS_OK; } - /******************************************************************************* * BackgroundCursorChild ******************************************************************************/ @@ -3369,9 +3367,9 @@ void BackgroundCursorChild::HandleResponse( StructuredCloneReadInfo cloneReadInfo(std::move(response.cloneInfo())); cloneReadInfo.mDatabase = mTransaction->Database(); - DeserializeStructuredCloneFiles(mTransaction->Database(), - response.cloneInfo().files(), nullptr, - cloneReadInfo.mFiles); + DeserializeStructuredCloneFiles( + mTransaction->Database(), response.cloneInfo().files(), + /* aForPreprocess */ false, cloneReadInfo.mFiles); RefPtr newCursor; @@ -3428,9 +3426,9 @@ void BackgroundCursorChild::HandleResponse( StructuredCloneReadInfo cloneReadInfo(std::move(response.cloneInfo())); cloneReadInfo.mDatabase = mTransaction->Database(); - DeserializeStructuredCloneFiles(mTransaction->Database(), - aResponse.cloneInfo().files(), nullptr, - cloneReadInfo.mFiles); + DeserializeStructuredCloneFiles( + mTransaction->Database(), aResponse.cloneInfo().files(), + /* aForPreprocess */ false, cloneReadInfo.mFiles); RefPtr newCursor; diff --git a/dom/indexedDB/ActorsChild.h b/dom/indexedDB/ActorsChild.h index 3702f67d3efc..58601bd3c8b5 100644 --- a/dom/indexedDB/ActorsChild.h +++ b/dom/indexedDB/ActorsChild.h @@ -30,10 +30,6 @@ class nsIEventTarget; struct nsID; -namespace JS { -struct WasmModule; -} // namespace JS - namespace mozilla { namespace ipc { @@ -570,9 +566,9 @@ class BackgroundRequestChild final : public BackgroundRequestChildBase, RefPtr mTransaction; nsTArray> mPreprocessHelpers; - nsTArray>> mModuleSets; + nsTArray> mCloneDatas; uint32_t mRunningPreprocessHelpers; - uint32_t mCurrentModuleSetIndex; + uint32_t mCurrentCloneDataIndex; nsresult mPreprocessResultCode; bool mGetAll; @@ -586,13 +582,12 @@ class BackgroundRequestChild final : public BackgroundRequestChildBase, void MaybeSendContinue(); - void OnPreprocessFinished(uint32_t aModuleSetIndex, - nsTArray>& aModuleSet); + void OnPreprocessFinished(uint32_t aCloneDataIndex, + UniquePtr aCloneData); void OnPreprocessFailed(uint32_t aModuleSetIndex, nsresult aErrorCode); - const nsTArray>* GetNextModuleSet( - const StructuredCloneReadInfo& aInfo); + UniquePtr GetNextCloneData(); void HandleResponse(nsresult aResponse); @@ -609,10 +604,9 @@ class BackgroundRequestChild final : public BackgroundRequestChildBase, void HandleResponse(uint64_t aResponse); - nsresult HandlePreprocess(const WasmModulePreprocessInfo& aPreprocessInfo); + nsresult HandlePreprocess(const PreprocessInfo& aPreprocessInfo); - nsresult HandlePreprocess( - const nsTArray& aPreprocessInfos); + nsresult HandlePreprocess(const nsTArray& aPreprocessInfos); // IPDL methods are only called by IPDL. virtual void ActorDestroy(ActorDestroyReason aWhy) override; diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index aaf24607b36a..a152b2147ad9 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -8454,10 +8454,9 @@ nsresult DeserializeStructuredCloneFile(FileManager* aFileManager, return NS_OK; } -nsresult DeserializeStructuredCloneFiles(FileManager* aFileManager, - const nsAString& aText, - nsTArray& aResult, - bool* aHasPreprocessInfo) { +nsresult DeserializeStructuredCloneFiles( + FileManager* aFileManager, const nsAString& aText, + nsTArray& aResult) { MOZ_ASSERT(!IsOnBackgroundThread()); nsCharSeparatedTokenizerTemplate tokenizer(aText, @@ -8475,20 +8474,6 @@ nsresult DeserializeStructuredCloneFiles(FileManager* aFileManager, if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - if (!aHasPreprocessInfo) { - continue; - } - - if (file->mType == StructuredCloneFile::eWasmBytecode) { - *aHasPreprocessInfo = true; - } else if (file->mType == StructuredCloneFile::eWasmCompiled) { - MOZ_ASSERT(aResult.Length() > 1); - MOZ_ASSERT(aResult[aResult.Length() - 2].mType == - StructuredCloneFile::eWasmBytecode); - - *aHasPreprocessInfo = true; - } } return NS_OK; @@ -8540,7 +8525,7 @@ nsresult SerializeStructuredCloneFiles( for (uint32_t index = 0; index < count; index++) { const StructuredCloneFile& file = aFiles[index]; - if (aForPreprocess && file.mType != StructuredCloneFile::eWasmBytecode) { + if (aForPreprocess && file.mType != StructuredCloneFile::eStructuredClone) { continue; } @@ -8615,23 +8600,12 @@ nsresult SerializeStructuredCloneFiles( } case StructuredCloneFile::eStructuredClone: { - SerializedStructuredCloneFile* file = aResult.AppendElement(fallible); - MOZ_ASSERT(file); - - file->file() = null_t(); - file->type() = StructuredCloneFile::eStructuredClone; - - break; - } - - case StructuredCloneFile::eWasmBytecode: { if (!aForPreprocess) { - SerializedStructuredCloneFile* serializedFile = - aResult.AppendElement(fallible); - MOZ_ASSERT(serializedFile); + SerializedStructuredCloneFile* file = aResult.AppendElement(fallible); + MOZ_ASSERT(file); - serializedFile->file() = null_t(); - serializedFile->type() = StructuredCloneFile::eWasmBytecode; + file->file() = null_t(); + file->type() = StructuredCloneFile::eStructuredClone; } else { RefPtr impl = new FileBlobImpl(nativeFile); impl->SetFileId(file.mFileInfo->Id()); @@ -8650,7 +8624,7 @@ nsresult SerializeStructuredCloneFiles( MOZ_ASSERT(serializedFile); serializedFile->file() = ipcBlob; - serializedFile->type() = StructuredCloneFile::eWasmBytecode; + serializedFile->type() = StructuredCloneFile::eStructuredClone; aDatabase->MapBlob(ipcBlob, file.mFileInfo); } @@ -8658,13 +8632,20 @@ nsresult SerializeStructuredCloneFiles( break; } + case StructuredCloneFile::eWasmBytecode: case StructuredCloneFile::eWasmCompiled: { SerializedStructuredCloneFile* serializedFile = aResult.AppendElement(fallible); MOZ_ASSERT(serializedFile); + // Set file() to null, support for storing WebAssembly.Modules has been + // removed in bug 1469395. Support for de-serialization of + // WebAssembly.Modules modules has been removed in bug 1561876. Full + // removal is tracked in bug 1487479. + serializedFile->file() = null_t(); - serializedFile->type() = StructuredCloneFile::eWasmCompiled; + serializedFile->type() = file.mType; + break; } @@ -10358,7 +10339,7 @@ nsresult DatabaseConnection::UpdateRefcountFunction::ProcessValue( } nsTArray files; - rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, nullptr); + rv = DeserializeStructuredCloneFiles(mFileManager, ids, files); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -13776,23 +13757,8 @@ bool TransactionBase::VerifyRequestParams( } case StructuredCloneFile::eStructuredClone: - ASSERT_UNLESS_FUZZING(); - return false; - case StructuredCloneFile::eWasmBytecode: case StructuredCloneFile::eWasmCompiled: - if (NS_WARN_IF( - file.type() != - DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent)) { - ASSERT_UNLESS_FUZZING(); - return false; - } - if (NS_WARN_IF(!file.get_PBackgroundIDBDatabaseFileParent())) { - ASSERT_UNLESS_FUZZING(); - return false; - } - break; - case StructuredCloneFile::eEndGuard: ASSERT_UNLESS_FUZZING(); return false; @@ -18219,8 +18185,8 @@ nsresult DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob( } if (!aFileIds.IsVoid()) { - nsresult rv = DeserializeStructuredCloneFiles( - aFileManager, aFileIds, aInfo->mFiles, &aInfo->mHasPreprocessInfo); + nsresult rv = + DeserializeStructuredCloneFiles(aFileManager, aFileIds, aInfo->mFiles); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -18243,8 +18209,7 @@ nsresult DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob( nsresult rv; if (!aFileIds.IsVoid()) { - rv = DeserializeStructuredCloneFiles(aFileManager, aFileIds, aInfo->mFiles, - &aInfo->mHasPreprocessInfo); + rv = DeserializeStructuredCloneFiles(aFileManager, aFileIds, aInfo->mFiles); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -18259,6 +18224,11 @@ nsresult DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob( return NS_ERROR_UNEXPECTED; } + if (IndexedDatabaseManager::PreprocessingEnabled()) { + aInfo->mHasPreprocessInfo = true; + return NS_OK; + } + StructuredCloneFile& file = aInfo->mFiles[index]; MOZ_ASSERT(file.mFileInfo); MOZ_ASSERT(file.mType == StructuredCloneFile::eStructuredClone); @@ -24132,9 +24102,7 @@ bool ObjectStoreAddOrPutRequestOp::Init(TransactionBase* aTransaction) { const FileAddInfo& fileAddInfo = fileAddInfos[index]; MOZ_ASSERT(fileAddInfo.type() == StructuredCloneFile::eBlob || - fileAddInfo.type() == StructuredCloneFile::eMutableFile || - fileAddInfo.type() == StructuredCloneFile::eWasmBytecode || - fileAddInfo.type() == StructuredCloneFile::eWasmCompiled); + fileAddInfo.type() == StructuredCloneFile::eMutableFile); const DatabaseOrMutableFile& file = fileAddInfo.file(); @@ -24172,22 +24140,6 @@ bool ObjectStoreAddOrPutRequestOp::Init(TransactionBase* aTransaction) { break; } - case StructuredCloneFile::eWasmBytecode: - case StructuredCloneFile::eWasmCompiled: { - MOZ_ASSERT(file.type() == - DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent); - - storedFileInfo->mFileActor = static_cast( - file.get_PBackgroundIDBDatabaseFileParent()); - MOZ_ASSERT(storedFileInfo->mFileActor); - - storedFileInfo->mFileInfo = storedFileInfo->mFileActor->GetFileInfo(); - MOZ_ASSERT(storedFileInfo->mFileInfo); - - storedFileInfo->mType = fileAddInfo.type(); - break; - } - default: MOZ_CRASH("Should never get here!"); } @@ -24651,8 +24603,8 @@ void MoveData( } template <> -void MoveData(StructuredCloneReadInfo& aInfo, - WasmModulePreprocessInfo& aResult) {} +void MoveData(StructuredCloneReadInfo& aInfo, + PreprocessInfo& aResult) {} template nsresult ObjectStoreGetRequestOp::ConvertResponse( @@ -24761,7 +24713,7 @@ nsresult ObjectStoreGetRequestOp::GetPreprocessParams( if (mGetAll) { aParams = ObjectStoreGetAllPreprocessParams(); - FallibleTArray falliblePreprocessInfos; + FallibleTArray falliblePreprocessInfos; if (NS_WARN_IF(!falliblePreprocessInfos.SetLength(mPreprocessInfoCount, fallible))) { return NS_ERROR_OUT_OF_MEMORY; @@ -24781,7 +24733,7 @@ nsresult ObjectStoreGetRequestOp::GetPreprocessParams( } } - nsTArray& preprocessInfos = + nsTArray& preprocessInfos = aParams.get_ObjectStoreGetAllPreprocessParams().preprocessInfos(); falliblePreprocessInfos.SwapElements(preprocessInfos); @@ -24791,7 +24743,7 @@ nsresult ObjectStoreGetRequestOp::GetPreprocessParams( aParams = ObjectStoreGetPreprocessParams(); - WasmModulePreprocessInfo& preprocessInfo = + PreprocessInfo& preprocessInfo = aParams.get_ObjectStoreGetPreprocessParams().preprocessInfo(); nsresult rv = ConvertResponse(mResponse[0], preprocessInfo); diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 1b41b9e14e9f..0e620540f520 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -689,26 +689,16 @@ class ValueDeserializationHelper { MOZ_ASSERT(aFile.mType == StructuredCloneFile::eWasmBytecode); MOZ_ASSERT(!aFile.mBlob); - // If we don't have a WasmModule, we are probably using it for an index - // creation, but Wasm module can't be used in index creation, so just make a - // dummy object. - if (!aFile.mWasmModule) { - JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); + // Just create a plain object here, support for de-serialization of + // WebAssembly.Modules has been removed in bug 1561876. Full removal is + // tracked in bug 1487479. - if (NS_WARN_IF(!obj)) { - return false; - } - - aResult.set(obj); - return true; - } - - JS::Rooted moduleObj(aCx, aFile.mWasmModule->createObject(aCx)); - if (NS_WARN_IF(!moduleObj)) { + JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!obj)) { return false; } - aResult.set(moduleObj); + aResult.set(obj); return true; } }; @@ -804,7 +794,7 @@ JSObject* CopyingStructuredCloneReadCallback(JSContext* aCx, MOZ_ASSERT(aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE); if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE || - aTag == SCTAG_DOM_MUTABLEFILE || aTag == SCTAG_DOM_WASM) { + aTag == SCTAG_DOM_MUTABLEFILE) { auto* cloneInfo = static_cast(aClosure); @@ -854,28 +844,14 @@ JSObject* CopyingStructuredCloneReadCallback(JSContext* aCx, return result; } - if (aTag == SCTAG_DOM_MUTABLEFILE) { - MOZ_ASSERT(file.mType == StructuredCloneFile::eMutableFile); + MOZ_ASSERT(file.mType == StructuredCloneFile::eMutableFile); - JS::Rooted wrappedMutableFile(aCx); - if (NS_WARN_IF(!ToJSValue(aCx, file.mMutableFile, &wrappedMutableFile))) { - return nullptr; - } - - result.set(&wrappedMutableFile.toObject()); - - return result; - } - - MOZ_ASSERT(file.mType == StructuredCloneFile::eWasmBytecode); - - JS::Rooted wrappedModule(aCx, - file.mWasmModule->createObject(aCx)); - if (NS_WARN_IF(!wrappedModule)) { + JS::Rooted wrappedMutableFile(aCx); + if (NS_WARN_IF(!ToJSValue(aCx, file.mMutableFile, &wrappedMutableFile))) { return nullptr; } - result.set(wrappedModule); + result.set(&wrappedMutableFile.toObject()); return result; } diff --git a/dom/indexedDB/IndexedDatabase.h b/dom/indexedDB/IndexedDatabase.h index 936562d22272..1b4f9e60e017 100644 --- a/dom/indexedDB/IndexedDatabase.h +++ b/dom/indexedDB/IndexedDatabase.h @@ -11,10 +11,6 @@ #include "nsCOMPtr.h" #include "nsTArray.h" -namespace JS { -struct WasmModule; -} // namespace JS - namespace mozilla { namespace dom { @@ -39,7 +35,6 @@ struct StructuredCloneFile { RefPtr mBlob; RefPtr mMutableFile; - RefPtr mWasmModule; RefPtr mFileInfo; FileType mType; diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index b42e76d6b08d..91127fe67fb4 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -123,6 +123,7 @@ const char kPrefMaxSerilizedMsgSize[] = IDB_PREF_BRANCH_ROOT "maxSerializedMsgSize"; const char kPrefErrorEventToSelfError[] = IDB_PREF_BRANCH_ROOT "errorEventToSelfError"; +const char kPreprocessingPref[] = IDB_PREF_BRANCH_ROOT "preprocessing"; #define IDB_PREF_LOGGING_BRANCH_ROOT IDB_PREF_BRANCH_ROOT "logging." @@ -147,6 +148,7 @@ Atomic gFileHandleEnabled(false); Atomic gPrefErrorEventToSelfError(false); Atomic gDataThresholdBytes(0); Atomic gMaxSerializedMsgSize(0); +Atomic gPreprocessingEnabled(false); void AtomicBoolPrefChangedCallback(const char* aPrefName, Atomic* aClosure) { @@ -281,6 +283,10 @@ nsresult IndexedDatabaseManager::Init() { Preferences::RegisterCallbackAndCall(MaxSerializedMsgSizePrefChangeCallback, kPrefMaxSerilizedMsgSize); + Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback, + kPreprocessingPref, + &gPreprocessingEnabled); + nsAutoCString acceptLang; Preferences::GetLocalizedCString("intl.accept_languages", acceptLang); @@ -336,6 +342,9 @@ void IndexedDatabaseManager::Destroy() { Preferences::UnregisterCallback(MaxSerializedMsgSizePrefChangeCallback, kPrefMaxSerilizedMsgSize); + Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback, + kPreprocessingPref, &gPreprocessingEnabled); + delete this; } @@ -617,6 +626,15 @@ uint32_t IndexedDatabaseManager::MaxSerializedMsgSize() { return gMaxSerializedMsgSize; } +// static +bool IndexedDatabaseManager::PreprocessingEnabled() { + MOZ_ASSERT(gDBManager, + "PreprocessingEnabled() called before indexedDB has been " + "initialized!"); + + return gPreprocessingEnabled; +} + void IndexedDatabaseManager::ClearBackgroundActor() { MOZ_ASSERT(NS_IsMainThread()); diff --git a/dom/indexedDB/IndexedDatabaseManager.h b/dom/indexedDB/IndexedDatabaseManager.h index 157fceecd9a3..6e421c1f0746 100644 --- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -95,6 +95,8 @@ class IndexedDatabaseManager final { static uint32_t MaxSerializedMsgSize(); + static bool PreprocessingEnabled(); + void ClearBackgroundActor(); already_AddRefed GetFileManager(PersistenceType aPersistenceType, diff --git a/dom/indexedDB/PBackgroundIDBRequest.ipdl b/dom/indexedDB/PBackgroundIDBRequest.ipdl index f0c0d2920a3a..a98ac194235c 100644 --- a/dom/indexedDB/PBackgroundIDBRequest.ipdl +++ b/dom/indexedDB/PBackgroundIDBRequest.ipdl @@ -109,19 +109,19 @@ union RequestResponse IndexCountResponse; }; -struct WasmModulePreprocessInfo +struct PreprocessInfo { SerializedStructuredCloneFile[] files; }; struct ObjectStoreGetPreprocessParams { - WasmModulePreprocessInfo preprocessInfo; + PreprocessInfo preprocessInfo; }; struct ObjectStoreGetAllPreprocessParams { - WasmModulePreprocessInfo[] preprocessInfos; + PreprocessInfo[] preprocessInfos; }; union PreprocessParams diff --git a/dom/indexedDB/test/unit/test_view_put_get_values.js b/dom/indexedDB/test/unit/test_view_put_get_values.js index 553a1c80de11..b055c398cd2c 100644 --- a/dom/indexedDB/test/unit/test_view_put_get_values.js +++ b/dom/indexedDB/test/unit/test_view_put_get_values.js @@ -16,11 +16,26 @@ function* testSteps() { const viewData = { key: 1, view: getRandomView(100000) }; - for (let external of [false, true]) { - if (external) { - info("Setting data threshold pref"); + const tests = [ + { + external: false, + preprocessing: false, + }, + { + external: true, + preprocessing: false, + }, + { + external: true, + preprocessing: true, + }, + ]; + for (let test of tests) { + if (test.external) { if (this.window) { + info("Setting data threshold pref"); + SpecialPowers.pushPrefEnv( { set: [["dom.indexedDB.dataThreshold", 0]] }, continueToNextStep @@ -31,6 +46,20 @@ function* testSteps() { } } + if (test.preprocessing) { + if (this.window) { + info("Setting preprocessing pref"); + + SpecialPowers.pushPrefEnv( + { set: [["dom.indexedDB.preprocessing", true]] }, + continueToNextStep + ); + yield undefined; + } else { + enablePreprocessing(); + } + } + info("Opening database"); let request = indexedDB.open(name); @@ -88,7 +117,7 @@ function* testSteps() { getCurrentUsage(grabFileUsageAndContinueHandler); let fileUsage = yield undefined; - if (external) { + if (test.external) { ok(fileUsage > 0, "File usage is not zero"); } else { ok(fileUsage == 0, "File usage is zero"); @@ -100,6 +129,21 @@ function* testSteps() { request.onerror = errorHandler; request.onsuccess = continueToNextStepSync; yield undefined; + + if (this.window) { + info("Resetting prefs"); + + SpecialPowers.popPrefEnv(continueToNextStep); + yield undefined; + } else { + if (test.external) { + resetDataThreshold(); + } + + if (test.preprocessing) { + resetPreprocessing(); + } + } } finishTest(); diff --git a/dom/indexedDB/test/unit/test_wasm_get_values.js b/dom/indexedDB/test/unit/test_wasm_get_values.js new file mode 100644 index 000000000000..6ecf029da9f6 --- /dev/null +++ b/dom/indexedDB/test/unit/test_wasm_get_values.js @@ -0,0 +1,58 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var testGenerator = testSteps(); + +function* testSteps() { + const name = "test_wasm_recompile.js"; + + const objectStoreName = "Wasm"; + + const wasmData = { key: 1 }; + + // The goal of this test is to prove that stored wasm is never deserialized. + + info("Installing profile"); + + clearAllDatabases(continueToNextStepSync); + yield undefined; + + // The profile was created with a mythical build (buildId: 20180309213541, + // cpuId: X64=0x2). It contains one stored wasm module (file id 1 - bytecode + // and file id 2 - compiled/machine code). The file create_db.js in the + // package was run locally (specifically it was temporarily added to + // xpcshell-parent-process.ini and then executed: + // mach xpcshell-test dom/indexedDB/test/unit/create_db.js + installPackagedProfile("wasm_get_values_profile"); + + info("Opening database"); + + let request = indexedDB.open(name); + request.onerror = errorHandler; + request.onupgradeneeded = unexpectedSuccessHandler; + request.onsuccess = continueToNextStepSync; + yield undefined; + + // success + let db = request.result; + db.onerror = errorHandler; + + info("Getting wasm"); + + request = db + .transaction([objectStoreName]) + .objectStore(objectStoreName) + .get(wasmData.key); + request.onsuccess = continueToNextStepSync; + yield undefined; + + info("Verifying wasm"); + + let isWasmModule = request.result instanceof WebAssembly.Module; + ok(!isWasmModule, "Object is not wasm module"); + + finishTest(); + yield undefined; +} diff --git a/dom/indexedDB/test/unit/test_wasm_recompile.js b/dom/indexedDB/test/unit/test_wasm_recompile.js deleted file mode 100644 index eb13d567a48e..000000000000 --- a/dom/indexedDB/test/unit/test_wasm_recompile.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -var testGenerator = testSteps(); - -function* testSteps() { - const name = "test_wasm_recompile.js"; - - const objectStoreName = "Wasm"; - - const wasmData = { key: 1, wasm: null }; - - // The goal of this test is to prove that wasm is recompiled and the on-disk - // copy updated. - - if (!isWasmSupported()) { - finishTest(); - yield undefined; - } - - getWasmBinary( - '(module (func $f (result i32) (i32.const 42)) (func (export "run") (result i32) (call $f)))' - ); - let binary = yield undefined; - - wasmData.wasm = getWasmModule(binary); - - info("Installing profile"); - - clearAllDatabases(continueToNextStepSync); - yield undefined; - - // The profile was created with a mythical build (buildId: 20180309213541, - // cpuId: X64=0x2). It contains one stored wasm module (file id 1 - bytecode - // and file id 2 - compiled/machine code). The file create_db.js in the - // package was run locally (specifically it was temporarily added to - // xpcshell-parent-process.ini and then executed: - // mach xpcshell-test dom/indexedDB/test/unit/create_db.js - installPackagedProfile("wasm_recompile_profile"); - - let filesDir = getChromeFilesDir(); - - let file = filesDir.clone(); - file.append("2"); - - info("Reading out contents of compiled blob"); - - File.createFromNsIFile(file).then(grabEventAndContinueHandler); - let domFile = yield undefined; - - let fileReader = new FileReader(); - fileReader.onload = continueToNextStepSync; - fileReader.readAsArrayBuffer(domFile); - - yield undefined; - - let compiledBuffer = fileReader.result; - - info("Opening database"); - - let request = indexedDB.open(name); - request.onerror = errorHandler; - request.onupgradeneeded = unexpectedSuccessHandler; - request.onsuccess = continueToNextStepSync; - yield undefined; - - // success - let db = request.result; - db.onerror = errorHandler; - - info("Getting wasm"); - - request = db - .transaction([objectStoreName]) - .objectStore(objectStoreName) - .get(wasmData.key); - request.onsuccess = continueToNextStepSync; - yield undefined; - - info("Verifying wasm module"); - - verifyWasmModule(request.result, wasmData.wasm); - yield undefined; - - info("Reading out contents of new compiled blob"); - - File.createFromNsIFile(file).then(grabEventAndContinueHandler); - domFile = yield undefined; - - fileReader = new FileReader(); - fileReader.onload = continueToNextStepSync; - fileReader.readAsArrayBuffer(domFile); - - yield undefined; - - let newCompiledBuffer = fileReader.result; - - info("Verifying that re-storing of re-compiled code has been disabled"); - - ok(compareBuffers(newCompiledBuffer, compiledBuffer), "Blobs don't differ"); - - info("Getting wasm again"); - - request = db - .transaction([objectStoreName]) - .objectStore(objectStoreName) - .get(wasmData.key); - request.onsuccess = continueToNextStepSync; - yield undefined; - - info("Verifying wasm module"); - - verifyWasmModule(request.result, wasmData.wasm); - yield undefined; - - info("Reading out contents of new compiled blob again"); - - File.createFromNsIFile(file).then(grabEventAndContinueHandler); - domFile = yield undefined; - - fileReader = new FileReader(); - fileReader.onload = continueToNextStepSync; - fileReader.readAsArrayBuffer(domFile); - - yield undefined; - - let newCompiledBuffer2 = fileReader.result; - - info("Verifying blob didn't change"); - - ok( - compareBuffers(newCompiledBuffer2, newCompiledBuffer), - "Blob didn't change" - ); - - finishTest(); - yield undefined; -} diff --git a/dom/indexedDB/test/unit/wasm_recompile_profile.zip b/dom/indexedDB/test/unit/wasm_get_values_profile.zip similarity index 100% rename from dom/indexedDB/test/unit/wasm_recompile_profile.zip rename to dom/indexedDB/test/unit/wasm_get_values_profile.zip diff --git a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js index a6dc0c9dadff..2c60892867f5 100644 --- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js +++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js @@ -494,16 +494,6 @@ function verifyView(view1, view2) { continueToNextStep(); } -function verifyWasmModule(module1, module2) { - // We assume the given modules have no imports and export a single function - // named 'run'. - var instance1 = new WebAssembly.Instance(module1); - var instance2 = new WebAssembly.Instance(module2); - is(instance1.exports.run(), instance2.exports.run(), "same run() result"); - - continueToNextStep(); -} - function grabFileUsageAndContinueHandler(request) { testGenerator.next(request.result.fileUsage); } @@ -531,11 +521,26 @@ function setDataThreshold(threshold) { SpecialPowers.setIntPref("dom.indexedDB.dataThreshold", threshold); } +function resetDataThreshold() { + info("Clearing data threshold pref"); + SpecialPowers.clearUserPref("dom.indexedDB.dataThreshold"); +} + function setMaxSerializedMsgSize(aSize) { info("Setting maximal size of a serialized message to " + aSize); SpecialPowers.setIntPref("dom.indexedDB.maxSerializedMsgSize", aSize); } +function enablePreprocessing() { + info("Setting preprocessing pref"); + SpecialPowers.setBoolPref("dom.indexedDB.preprocessing", true); +} + +function resetPreprocessing() { + info("Clearing preprocessing pref"); + SpecialPowers.clearUserPref("dom.indexedDB.preprocessing"); +} + function getPrincipal(url) { let uri = Services.io.newURI(url); return Services.scriptSecurityManager.createContentPrincipal(uri, {}); diff --git a/dom/indexedDB/test/unit/xpcshell-parent-process.ini b/dom/indexedDB/test/unit/xpcshell-parent-process.ini index 2dbbbe62affd..ddc2edcc2a9a 100644 --- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini +++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini @@ -26,7 +26,7 @@ support-files = schema23upgrade_profile.zip snappyUpgrade_profile.zip storagePersistentUpgrade_profile.zip - wasm_recompile_profile.zip + wasm_get_values_profile.zip xpcshell-shared.ini [include:xpcshell-shared.ini] @@ -66,5 +66,6 @@ skip-if = os == "android" # bug 951017: intermittent failure on Android x86 emulator skip-if = os == "android" && processor == "x86" [test_unexpectedDirectory.js] +[test_view_put_get_values.js] +[test_wasm_get_values.js] [test_wasm_put_get_values.js] -[test_wasm_recompile.js] diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index a5322e5fa19f..fca2af80bdff 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -3018,5 +3018,3 @@ void MediaFormatReader::OnFirstDemuxFailed(TrackInfo::TrackType aType, } // namespace mozilla #undef NS_DispatchToMainThread -#undef LOGV -#undef LOG diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index d8ff7065ecbf..9c223578529a 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -172,6 +172,10 @@ class nsMainThreadPtrHolder< namespace mozilla { +#ifdef LOG +# undef LOG +#endif + LazyLogModule gMediaManagerLog("MediaManager"); #define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__)) @@ -4615,6 +4619,4 @@ void GetUserMediaWindowListener::NotifyChrome() { })); } -#undef LOG - } // namespace mozilla diff --git a/dom/media/MediaRecorder.cpp b/dom/media/MediaRecorder.cpp index 5acf1a3d523d..a2cfe84016ca 100644 --- a/dom/media/MediaRecorder.cpp +++ b/dom/media/MediaRecorder.cpp @@ -40,6 +40,10 @@ #include "nsProxyRelease.h" #include "nsTArray.h" +#ifdef LOG +# undef LOG +#endif + mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder"); #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg) @@ -197,15 +201,72 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper) * Therefore, the reference dependency in gecko is: * ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle * reference between Session and MediaRecorder. - * 2) A Session is destroyed after MediaRecorder::Stop has been called _and_ all - * encoded media data has been passed to OnDataAvailable handler. 3) - * MediaRecorder::Stop is called by user or the document is going to inactive or - * invisible. + * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being + * called _and_ all encoded media data been passed to OnDataAvailable handler. + * 3) MediaRecorder::Stop is called by user or the document is going to + * inactive or invisible. */ class MediaRecorder::Session : public PrincipalChangeObserver, public DOMMediaStream::TrackListener { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Session) + // Main thread task. + // Create a blob event and send back to client. + class PushBlobRunnable : public Runnable, public MutableBlobStorageCallback { + public: + // We need to always declare refcounting because + // MutableBlobStorageCallback has pure-virtual refcounting. + NS_DECL_ISUPPORTS_INHERITED + + // aDestroyRunnable can be null. If it's not, it will be dispatched after + // the PushBlobRunnable::Run(). + PushBlobRunnable(Session* aSession, Runnable* aDestroyRunnable) + : Runnable("dom::MediaRecorder::Session::PushBlobRunnable"), + mSession(aSession), + mDestroyRunnable(aDestroyRunnable) {} + + NS_IMETHOD Run() override { + LOG(LogLevel::Debug, ("Session.PushBlobRunnable s=(%p)", mSession.get())); + MOZ_ASSERT(NS_IsMainThread()); + + mSession->GetBlobWhenReady(this); + return NS_OK; + } + + void BlobStoreCompleted(MutableBlobStorage* aBlobStorage, Blob* aBlob, + nsresult aRv) override { + RefPtr recorder = mSession->mRecorder; + if (!recorder) { + return; + } + + if (NS_FAILED(aRv)) { + mSession->DoSessionEndTask(aRv); + return; + } + + nsresult rv = recorder->CreateAndDispatchBlobEvent(aBlob); + if (NS_FAILED(rv)) { + mSession->DoSessionEndTask(aRv); + } + + if (mDestroyRunnable && + NS_FAILED(NS_DispatchToMainThread(mDestroyRunnable.forget()))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread failed"); + } + } + + private: + ~PushBlobRunnable() = default; + + RefPtr mSession; + + // The generation of the blob is async. In order to avoid dispatching the + // DestroyRunnable before pushing the blob event, we store the runnable + // here. + RefPtr mDestroyRunnable; + }; + class StoreEncodedBufferRunnable final : public Runnable { RefPtr mSession; nsTArray> mBuffer; @@ -238,6 +299,31 @@ class MediaRecorder::Session : public PrincipalChangeObserver, } }; + // Notify encoder error, run in main thread task. (Bug 1095381) + class EncoderErrorNotifierRunnable : public Runnable { + public: + explicit EncoderErrorNotifierRunnable(Session* aSession) + : Runnable("dom::MediaRecorder::Session::EncoderErrorNotifierRunnable"), + mSession(aSession) {} + + NS_IMETHOD Run() override { + LOG(LogLevel::Debug, + ("Session.ErrorNotifyRunnable s=(%p)", mSession.get())); + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr recorder = mSession->mRecorder; + if (!recorder) { + return NS_OK; + } + + recorder->NotifyError(NS_ERROR_UNEXPECTED); + return NS_OK; + } + + private: + RefPtr mSession; + }; + // Fire a named event, run in main thread task. class DispatchEventRunnable : public Runnable { public: @@ -264,6 +350,75 @@ class MediaRecorder::Session : public PrincipalChangeObserver, nsString mEventName; }; + // Main thread task. + // To delete RecordingSession object. + class DestroyRunnable : public Runnable { + public: + explicit DestroyRunnable(Session* aSession) + : Runnable("dom::MediaRecorder::Session::DestroyRunnable"), + mSession(aSession) {} + + explicit DestroyRunnable(already_AddRefed aSession) + : Runnable("dom::MediaRecorder::Session::DestroyRunnable"), + mSession(aSession) {} + + NS_IMETHOD Run() override { + LOG(LogLevel::Debug, + ("Session.DestroyRunnable session refcnt = (%d) s=(%p)", + static_cast(mSession->mRefCnt), mSession.get())); + MOZ_ASSERT(NS_IsMainThread() && mSession); + RefPtr recorder = mSession->mRecorder; + if (!recorder) { + return NS_OK; + } + // SourceMediaStream is ended, and send out TRACK_EVENT_END notification. + // Read Thread will be terminate soon. + // We need to switch MediaRecorder to "Stop" state first to make sure + // MediaRecorder is not associated with this Session anymore, then, it's + // safe to delete this Session. + // Also avoid to run if this session already call stop before + if (mSession->mRunningState.isOk() && + mSession->mRunningState.unwrap() != RunningState::Stopping && + mSession->mRunningState.unwrap() != RunningState::Stopped) { + recorder->StopForSessionDestruction(); + if (NS_FAILED(NS_DispatchToMainThread( + new DestroyRunnable(mSession.forget())))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread failed"); + } + return NS_OK; + } + + if (mSession->mRunningState.isOk()) { + mSession->mRunningState = RunningState::Stopped; + } + + // Dispatch stop event and clear MIME type. + mSession->mMimeType = NS_LITERAL_STRING(""); + recorder->SetMimeType(mSession->mMimeType); + recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop")); + + RefPtr session = mSession.forget(); + session->Shutdown()->Then( + GetCurrentThreadSerialEventTarget(), __func__, + [session]() { + gSessions.RemoveEntry(session); + if (gSessions.Count() == 0 && gMediaRecorderShutdownBlocker) { + // All sessions finished before shutdown, no need to keep the + // blocker. + RefPtr barrier = GetShutdownBarrier(); + barrier->RemoveBlocker(gMediaRecorderShutdownBlocker); + gMediaRecorderShutdownBlocker = nullptr; + } + }, + []() { MOZ_CRASH("Not reached"); }); + return NS_OK; + } + + private: + // Call mSession::Release automatically while DestroyRunnable be destroy. + RefPtr mSession; + }; + class EncoderListener : public MediaEncoderListener { public: EncoderListener(TaskQueue* aEncoderThread, Session* aSession) @@ -307,21 +462,22 @@ class MediaRecorder::Session : public PrincipalChangeObserver, RefPtr mSession; }; + friend class EncoderErrorNotifierRunnable; + friend class PushBlobRunnable; + friend class DestroyRunnable; + public: Session(MediaRecorder* aRecorder, uint32_t aTimeSlice) : mRecorder(aRecorder), mMediaStreamReady(false), - mMainThread(mRecorder->GetOwner()->EventTargetFor(TaskCategory::Other)), mTimeSlice(aTimeSlice), - mStartTime(TimeStamp::Now()), mRunningState(RunningState::Idling) { MOZ_ASSERT(NS_IsMainThread()); aRecorder->GetMimeType(mMimeType); mMaxMemory = Preferences::GetUint("media.recorder.max_memory", MAX_ALLOW_MEMORY_BUFFER); - mLastBlobTimeStamp = mStartTime; - Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1); + mLastBlobTimeStamp = TimeStamp::Now(); } void PrincipalChanged(MediaStreamTrack* aTrack) override { @@ -433,7 +589,7 @@ class MediaRecorder::Session : public PrincipalChangeObserver, if (mRunningState.isOk() && mRunningState.unwrap() == RunningState::Idling) { LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this)); - // End the Session directly if there is no encoder. + // End the Session directly if there is no ExtractRunnable. DoSessionEndTask(NS_OK); } else if (mRunningState.isOk() && (mRunningState.unwrap() == RunningState::Starting || @@ -470,26 +626,17 @@ class MediaRecorder::Session : public PrincipalChangeObserver, return NS_OK; } - void RequestData() { + nsresult RequestData() { LOG(LogLevel::Debug, ("Session.RequestData")); MOZ_ASSERT(NS_IsMainThread()); - GatherBlob()->Then( - mMainThread, __func__, - [this, self = RefPtr(this)]( - const BlobPromise::ResolveOrRejectValue& aResult) { - if (aResult.IsReject()) { - LOG(LogLevel::Warning, ("GatherBlob failed for RequestData()")); - DoSessionEndTask(aResult.RejectValue()); - return; - } + if (NS_FAILED( + NS_DispatchToMainThread(new PushBlobRunnable(this, nullptr)))) { + MOZ_ASSERT(false, "RequestData NS_DispatchToMainThread failed"); + return NS_ERROR_FAILURE; + } - nsresult rv = - mRecorder->CreateAndDispatchBlobEvent(aResult.ResolveValue()); - if (NS_FAILED(rv)) { - DoSessionEndTask(NS_OK); - } - }); + return NS_OK; } void MaybeCreateMutableBlobStorage() { @@ -499,46 +646,14 @@ class MediaRecorder::Session : public PrincipalChangeObserver, } } - static const bool IsExclusive = true; - using BlobPromise = - MozPromise, nsresult, IsExclusive>; - class BlobStorer : public MutableBlobStorageCallback { - MozPromiseHolder mHolder; - - virtual ~BlobStorer() = default; - - public: - BlobStorer() = default; - - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlobStorer, override) - - void BlobStoreCompleted(MutableBlobStorage*, Blob* aBlob, - nsresult aRv) override { - MOZ_ASSERT(NS_IsMainThread()); - if (NS_FAILED(aRv)) { - mHolder.Reject(aRv, __func__); - } else { - mHolder.Resolve(nsMainThreadPtrHandle( - MakeAndAddRef>( - "BlobStorer::ResolveBlob", aBlob)), - __func__); - } - } - - RefPtr Promise() { return mHolder.Ensure(__func__); } - }; - - // Stops gathering data into the current blob and resolves when the current - // blob is available. Future data will be stored in a new blob. - RefPtr GatherBlob() { + void GetBlobWhenReady(MutableBlobStorageCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); - RefPtr storer = MakeAndAddRef(); - MaybeCreateMutableBlobStorage(); - mMutableBlobStorage->GetBlobWhenReady( - mRecorder->GetOwner(), NS_ConvertUTF16toUTF8(mMimeType), storer); - mMutableBlobStorage = nullptr; - return storer->Promise(); + MaybeCreateMutableBlobStorage(); + mMutableBlobStorage->GetBlobWhenReady(mRecorder->GetParentObject(), + NS_ConvertUTF16toUTF8(mMimeType), + aCallback); + mMutableBlobStorage = nullptr; } RefPtr SizeOfExcludingThis( @@ -563,16 +678,17 @@ class MediaRecorder::Session : public PrincipalChangeObserver, } private: + // Only DestroyRunnable is allowed to delete Session object on main thread. virtual ~Session() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mShutdownPromise); LOG(LogLevel::Debug, ("Session.~Session (%p)", this)); } - // Pull encoded media data from MediaEncoder and put into MutableBlobStorage. - // If the bool aForceFlush is true, we will force a dispatch of a blob to - // main thread. - void Extract(bool aForceFlush) { + // Destroy this session object in the end of this function. + // If the bool aForceFlush is true, we will force to dispatch a + // PushBlobRunnable to main thread. + void Extract(bool aForceFlush, Runnable* aDestroyRunnable) { MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); LOG(LogLevel::Debug, ("Session.Extract %p", this)); @@ -600,24 +716,16 @@ class MediaRecorder::Session : public PrincipalChangeObserver, pushBlob = true; } if (pushBlob) { - mLastBlobTimeStamp = TimeStamp::Now(); - InvokeAsync(mMainThread, this, __func__, &Session::GatherBlob) - ->Then(mMainThread, __func__, - [this, self = RefPtr(this)]( - const BlobPromise::ResolveOrRejectValue& aResult) { - if (aResult.IsReject()) { - LOG(LogLevel::Warning, - ("GatherBlob failed for pushing blob")); - DoSessionEndTask(aResult.RejectValue()); - return; - } - - nsresult rv = mRecorder->CreateAndDispatchBlobEvent( - aResult.ResolveValue()); - if (NS_FAILED(rv)) { - DoSessionEndTask(NS_OK); - } - }); + if (NS_FAILED(NS_DispatchToMainThread( + new PushBlobRunnable(this, aDestroyRunnable)))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed"); + } else { + mLastBlobTimeStamp = TimeStamp::Now(); + } + } else if (aDestroyRunnable) { + if (NS_FAILED(NS_DispatchToMainThread(aDestroyRunnable))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed"); + } } } @@ -671,7 +779,7 @@ class MediaRecorder::Session : public PrincipalChangeObserver, // When MediaRecorder supports multiple tracks, we should set up a single // MediaInputPort from the input stream, and let main thread check // track principals async later. - nsPIDOMWindowInner* window = mRecorder->GetOwner(); + nsPIDOMWindowInner* window = mRecorder->GetParentObject(); Document* document = window ? window->GetExtantDoc() : nullptr; nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, NS_LITERAL_CSTRING("Media"), document, @@ -874,18 +982,12 @@ class MediaRecorder::Session : public PrincipalChangeObserver, // appropriate video keyframe interval defined in milliseconds. mEncoder->SetVideoKeyFrameInterval(mTimeSlice); - // Set mRunningState to Running so that DoSessionEndTask will + // Set mRunningState to Running so that ExtractRunnable/DestroyRunnable will // take the responsibility to end the session. mRunningState = RunningState::Starting; } - // This is the task that will stop recording per spec: - // - Stop gathering data (this is inherently async) - // - Set state to "inactive" - // - Fire an error event, if NS_FAILED(rv) - // - Discard blob data if rv is NS_ERROR_DOM_SECURITY_ERR - // - Fire a Blob event - // - Fire an event named stop + // application should get blob and onstop event void DoSessionEndTask(nsresult rv) { MOZ_ASSERT(NS_IsMainThread()); if (mRunningState.isErr()) { @@ -899,11 +1001,11 @@ class MediaRecorder::Session : public PrincipalChangeObserver, return; } - bool needsStartEvent = false; if (mRunningState.isOk() && (mRunningState.unwrap() == RunningState::Idling || mRunningState.unwrap() == RunningState::Starting)) { - needsStartEvent = true; + NS_DispatchToMainThread( + new DispatchEventRunnable(this, NS_LITERAL_STRING("start"))); } if (rv == NS_OK) { @@ -912,100 +1014,77 @@ class MediaRecorder::Session : public PrincipalChangeObserver, mRunningState = Err(rv); } - GatherBlob() - ->Then(mMainThread, __func__, - [this, self = RefPtr(this), rv, needsStartEvent]( - const BlobPromise::ResolveOrRejectValue& aResult) { - if (mRecorder->mSessions.LastElement() == this) { - // Set state to inactive, but only if the recorder is not - // controlled by another session already. - mRecorder->ForceInactive(); - } + if (NS_FAILED(rv)) { + mRecorder->ForceInactive(); + NS_DispatchToMainThread(NewRunnableMethod( + "dom::MediaRecorder::NotifyError", mRecorder, + &MediaRecorder::NotifyError, rv)); + } - if (needsStartEvent) { - mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("start")); - } + RefPtr destroyRunnable = new DestroyRunnable(this); - // If there was an error, Fire the appropriate one - if (NS_FAILED(rv)) { - mRecorder->NotifyError(rv); - } - - // Fire a blob event named dataavailable - RefPtr blob; - if (rv == NS_ERROR_DOM_SECURITY_ERR || aResult.IsReject()) { - // In case of SecurityError, the blob data must be discarded. - // We create a new empty one and throw the blob with its data - // away. - // In case we failed to gather blob data, we create an empty - // memory blob instead. - blob = Blob::CreateEmptyBlob(mRecorder->GetParentObject(), - mMimeType); - } else { - blob = aResult.ResolveValue(); - } - if (NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(blob))) { - // Failed to dispatch blob event. That's unexpected. It's - // probably all right to fire an error event if we haven't - // already. - if (NS_SUCCEEDED(rv)) { - mRecorder->NotifyError(NS_ERROR_FAILURE); - } - } - - // Dispatch stop event and clear MIME type. - mMimeType = NS_LITERAL_STRING(""); - mRecorder->SetMimeType(mMimeType); - - // Fire an event named stop - mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop")); - - // And finally, Shutdown and destroy the Session - return Shutdown(); - }) - ->Then(mMainThread, __func__, [this, self = RefPtr(this)] { - gSessions.RemoveEntry(this); - if (gSessions.Count() == 0 && gMediaRecorderShutdownBlocker) { - // All sessions finished before shutdown, no need to keep the - // blocker. - RefPtr barrier = GetShutdownBarrier(); - barrier->RemoveBlocker(gMediaRecorderShutdownBlocker); - gMediaRecorderShutdownBlocker = nullptr; - } - }); + if (rv != NS_ERROR_DOM_SECURITY_ERR) { + // Don't push a blob if there was a security error. + if (NS_FAILED(NS_DispatchToMainThread( + new PushBlobRunnable(this, destroyRunnable)))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed"); + } + } else { + if (NS_FAILED(NS_DispatchToMainThread(destroyRunnable))) { + MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed"); + } + } } void MediaEncoderInitialized() { MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); - NS_DispatchToMainThread(NewRunnableFrom([self = RefPtr(this), this, - mime = mEncoder->MimeType()]() { - if (mRunningState.isErr()) { + // Pull encoded metadata from MediaEncoder + nsTArray> encodedBuf; + nsString mime; + nsresult rv = mEncoder->GetEncodedMetadata(&encodedBuf, mime); + + if (NS_FAILED(rv)) { + MOZ_ASSERT(false); + return; + } + + // Append pulled data into cache buffer. + NS_DispatchToMainThread( + new StoreEncodedBufferRunnable(this, std::move(encodedBuf))); + + RefPtr self = this; + NS_DispatchToMainThread(NewRunnableFrom([self, mime]() { + if (!self->mRecorder) { + MOZ_ASSERT_UNREACHABLE("Recorder should be live"); return NS_OK; } - mMimeType = mime; - mRecorder->SetMimeType(mime); - auto state = mRunningState.unwrap(); - if (state == RunningState::Starting || state == RunningState::Stopping) { - if (state == RunningState::Starting) { - // We set it to Running in the runnable since we can only assign - // mRunningState on main thread. We set it before running the start - // event runnable since that dispatches synchronously (and may cause - // js calls to methods depending on mRunningState). - mRunningState = RunningState::Running; + if (self->mRunningState.isOk()) { + auto state = self->mRunningState.unwrap(); + if (state == RunningState::Starting || + state == RunningState::Stopping) { + if (state == RunningState::Starting) { + // We set it to Running in the runnable since we can only assign + // mRunningState on main thread. We set it before running the start + // event runnable since that dispatches synchronously (and may cause + // js calls to methods depending on mRunningState). + self->mRunningState = RunningState::Running; + } + self->mMimeType = mime; + self->mRecorder->SetMimeType(self->mMimeType); + auto startEvent = MakeRefPtr( + self, NS_LITERAL_STRING("start")); + startEvent->Run(); } - mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("start")); } return NS_OK; })); - - Extract(false); } void MediaEncoderDataAvailable() { MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); - Extract(false); + Extract(false, nullptr); } void MediaEncoderError() { @@ -1019,9 +1098,12 @@ class MediaRecorder::Session : public PrincipalChangeObserver, MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); MOZ_ASSERT(mEncoder->IsShutdown()); - mMainThread->Dispatch(NewRunnableMethod( - "MediaRecorder::Session::MediaEncoderShutdown->DoSessionEndTask", this, - &Session::DoSessionEndTask, NS_OK)); + // For the stop event. Let's the creation of the blob to dispatch this + // runnable. + RefPtr destroyRunnable = new DestroyRunnable(this); + + // Forces the last blob even if it's not time for it yet. + Extract(true, destroyRunnable); // Clean up. mEncoderListener->Forget(); @@ -1038,13 +1120,6 @@ class MediaRecorder::Session : public PrincipalChangeObserver, return mShutdownPromise; } - // This is a coarse calculation and does not reflect the duration of the - // final recording for reasons such as pauses. However it allows us an - // idea of how long people are running their recorders for. - TimeDuration timeDelta = TimeStamp::Now() - mStartTime; - Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION, - timeDelta.ToSeconds()); - mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__); RefPtr self = this; @@ -1081,16 +1156,19 @@ class MediaRecorder::Session : public PrincipalChangeObserver, } // Break the cycle reference between Session and MediaRecorder. - mShutdownPromise = mShutdownPromise->Then( - GetCurrentThreadSerialEventTarget(), __func__, - [self]() { - self->mRecorder->RemoveSession(self); - return ShutdownPromise::CreateAndResolve(true, __func__); - }, - []() { - MOZ_ASSERT_UNREACHABLE("Unexpected reject"); - return ShutdownPromise::CreateAndReject(false, __func__); - }); + if (mRecorder) { + mShutdownPromise = mShutdownPromise->Then( + GetCurrentThreadSerialEventTarget(), __func__, + [self]() { + self->mRecorder->RemoveSession(self); + self->mRecorder = nullptr; + return ShutdownPromise::CreateAndResolve(true, __func__); + }, + []() { + MOZ_ASSERT_UNREACHABLE("Unexpected reject"); + return ShutdownPromise::CreateAndReject(false, __func__); + }); + } if (mEncoderThread) { RefPtr& encoderThread = mEncoderThread; @@ -1115,8 +1193,9 @@ class MediaRecorder::Session : public PrincipalChangeObserver, Stopped, // Session has stopped without any error }; - // Our associated MediaRecorder. - const RefPtr mRecorder; + // Hold reference to MediaRecorder that ensure MediaRecorder is alive + // if there is an active session. Access ONLY on main thread. + RefPtr mRecorder; // Stream currently recorded. RefPtr mMediaStream; @@ -1128,8 +1207,6 @@ class MediaRecorder::Session : public PrincipalChangeObserver, // set. nsTArray> mMediaStreamTracks; - // Main thread used for MozPromise operations. - const RefPtr mMainThread; // Runnable thread for reading data from MediaEncoder. RefPtr mEncoderThread; // MediaEncoder pipeline. @@ -1149,14 +1226,14 @@ class MediaRecorder::Session : public PrincipalChangeObserver, // The interval of passing encoded data from MutableBlobStorage to // onDataAvailable handler. const uint32_t mTimeSlice; - // The time this session started, for telemetry. - const TimeStamp mStartTime; - // The session's current main thread state. The error type gets set when - // ending a recording with an error. An NS_OK error is invalid. + // The session's current main thread state. The error type gets setwhen ending + // a recording with an error. An NS_OK error is invalid. // Main thread only. Result mRunningState; }; +NS_IMPL_ISUPPORTS_INHERITED0(MediaRecorder::Session::PushBlobRunnable, Runnable) + MediaRecorder::~MediaRecorder() { LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this)); UnRegisterActivityObserver(); @@ -1251,6 +1328,8 @@ void MediaRecorder::Start(const Optional& aTimeSlice, mSessions.AppendElement(); mSessions.LastElement() = new Session(this, timeSlice); mSessions.LastElement()->Start(); + mStartTime = TimeStamp::Now(); + Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1); } void MediaRecorder::Stop(ErrorResult& aResult) { @@ -1312,7 +1391,10 @@ void MediaRecorder::RequestData(ErrorResult& aResult) { return; } MOZ_ASSERT(mSessions.Length() > 0); - mSessions.LastElement()->RequestData(); + nsresult rv = mSessions.LastElement()->RequestData(); + if (NS_FAILED(rv)) { + NotifyError(rv); + } } JSObject* MediaRecorder::WrapObject(JSContext* aCx, @@ -1601,6 +1683,22 @@ void MediaRecorder::ForceInactive() { mState = RecordingState::Inactive; } +void MediaRecorder::StopForSessionDestruction() { + LOG(LogLevel::Debug, ("MediaRecorder.StopForSessionDestruction %p", this)); + MediaRecorderReporter::RemoveMediaRecorder(this); + // We do not perform a mState != RecordingState::Recording) check here as + // we may already be inactive due to ForceInactive(). + mState = RecordingState::Inactive; + MOZ_ASSERT(mSessions.Length() > 0); + mSessions.LastElement()->Stop(); + // This is a coarse calculation and does not reflect the duration of the + // final recording for reasons such as pauses. However it allows us an idea + // of how long people are running their recorders for. + TimeDuration timeDelta = TimeStamp::Now() - mStartTime; + Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION, + timeDelta.ToSeconds()); +} + void MediaRecorder::InitializeDomExceptions() { mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR); mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR); @@ -1639,5 +1737,3 @@ StaticRefPtr MediaRecorderReporter::sUniqueInstance; } // namespace dom } // namespace mozilla - -#undef LOG diff --git a/dom/media/MediaRecorder.h b/dom/media/MediaRecorder.h index 92916d011097..0debcf026bc2 100644 --- a/dom/media/MediaRecorder.h +++ b/dom/media/MediaRecorder.h @@ -61,6 +61,8 @@ class MediaRecorder final : public DOMEventTargetHelper, JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + nsPIDOMWindowInner* GetParentObject() { return GetOwner(); } + NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaRecorder, DOMEventTargetHelper) @@ -175,6 +177,8 @@ class MediaRecorder final : public DOMEventTargetHelper, uint32_t mVideoBitsPerSecond; uint32_t mBitsPerSecond; + TimeStamp mStartTime; + // DOMExceptions that are created early and possibly thrown in NotifyError. // Creating them early allows us to capture the JS stack for which cannot be // done at the time the error event is fired. diff --git a/dom/media/encoder/ContainerWriter.h b/dom/media/encoder/ContainerWriter.h index 724c8b90c961..763b5f411a52 100644 --- a/dom/media/encoder/ContainerWriter.h +++ b/dom/media/encoder/ContainerWriter.h @@ -7,7 +7,7 @@ #define ContainerWriter_h_ #include "nsTArray.h" -#include "EncodedFrame.h" +#include "EncodedFrameContainer.h" #include "TrackMetadataBase.h" namespace mozilla { @@ -26,26 +26,23 @@ class ContainerWriter { enum { END_OF_STREAM = 1 << 0 }; /** - * Writes encoded track data from aData into the internal stream of container - * writer. aFlags is used to signal the impl of different conditions - * such as END_OF_STREAM. Each impl may handle different flags, and should be - * documented accordingly. Currently, WriteEncodedTrack doesn't support - * explicit track specification, though each impl may provide logic to - * allocate frames into different tracks. + * Writes encoded track data from aBuffer to a packet, and insert this packet + * into the internal stream of container writer. aDuration is the playback + * duration of this packet in number of samples. aFlags is true with + * END_OF_STREAM if this is the last packet of track. + * Currently, WriteEncodedTrack doesn't support multiple tracks. */ - virtual nsresult WriteEncodedTrack( - const nsTArray>& aData, uint32_t aFlags = 0) = 0; + virtual nsresult WriteEncodedTrack(const EncodedFrameContainer& aData, + uint32_t aFlags = 0) = 0; /** - * Stores the metadata for all given tracks to the muxer. - * - * This method checks the integrity of aMetadata. - * If the metadata isn't well formatted, this method returns NS_ERROR_FAILURE. - * If the metadata is well formatted, it stores the metadata and returns + * Set the meta data pointer into muxer + * This function will check the integrity of aMetadata. + * If the meta data isn't well format, this function will return + * NS_ERROR_FAILURE to caller, else save the pointer to mMetadata and return * NS_OK. */ - virtual nsresult SetMetadata( - const nsTArray>& aMetadata) = 0; + virtual nsresult SetMetadata(TrackMetadataBase* aMetadata) = 0; /** * Indicate if the writer has finished to output data @@ -62,7 +59,7 @@ class ContainerWriter { * even it is not full, and copy these container data to a buffer for * aOutputBufs to append. */ - virtual nsresult GetContainerData(nsTArray>* aOutputBufs, + virtual nsresult GetContainerData(nsTArray >* aOutputBufs, uint32_t aFlags = 0) = 0; protected: diff --git a/dom/media/encoder/EncodedFrame.h b/dom/media/encoder/EncodedFrame.h deleted file mode 100644 index aadba4c6ccad..000000000000 --- a/dom/media/encoder/EncodedFrame.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ -/* 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 EncodedFrame_h_ -#define EncodedFrame_h_ - -#include "nsISupportsImpl.h" -#include "VideoUtils.h" - -namespace mozilla { - -// Represent an encoded frame emitted by an encoder -class EncodedFrame final { - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EncodedFrame) - public: - EncodedFrame() : mTime(0), mDuration(0), mFrameType(UNKNOWN) {} - enum FrameType { - VP8_I_FRAME, // VP8 intraframe - VP8_P_FRAME, // VP8 predicted frame - OPUS_AUDIO_FRAME, // Opus audio frame - UNKNOWN // FrameType not set - }; - void SwapInFrameData(nsTArray& aData) { - mFrameData.SwapElements(aData); - } - nsresult SwapOutFrameData(nsTArray& aData) { - if (mFrameType != UNKNOWN) { - // Reset this frame type to UNKNOWN once the data is swapped out. - mFrameData.SwapElements(aData); - mFrameType = UNKNOWN; - return NS_OK; - } - return NS_ERROR_FAILURE; - } - const nsTArray& GetFrameData() const { return mFrameData; } - // Timestamp in microseconds - uint64_t mTime; - // The playback duration of this packet. The unit is determined by the use - // case. For VP8 the unit should be microseconds. For opus this is the number - // of samples. - uint64_t mDuration; - // Represent what is in the FrameData - FrameType mFrameType; - - uint64_t GetEndTime() const { - // Defend against untested types. This assert can be removed but we want - // to make sure other types are correctly accounted for. - MOZ_ASSERT(mFrameType == OPUS_AUDIO_FRAME || mFrameType == VP8_I_FRAME || - mFrameType == VP8_P_FRAME); - if (mFrameType == OPUS_AUDIO_FRAME) { - // See bug 1356054 for discussion around standardization of time units - // (can remove videoutils import when this goes) - return mTime + FramesToUsecs(mDuration, 48000).value(); - } else { - return mTime + mDuration; - } - } - - private: - // Private destructor, to discourage deletion outside of Release(): - ~EncodedFrame() {} - - // Encoded data - nsTArray mFrameData; -}; - -} // namespace mozilla - -#endif // EncodedFrame_h_ diff --git a/dom/media/encoder/EncodedFrameContainer.h b/dom/media/encoder/EncodedFrameContainer.h new file mode 100644 index 000000000000..e2a86c1d30fc --- /dev/null +++ b/dom/media/encoder/EncodedFrameContainer.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 EncodedFrameContainer_H_ +#define EncodedFrameContainer_H_ + +#include "nsTArray.h" + +namespace mozilla { + +class EncodedFrame; + +/* + * This container is used to carry video or audio encoded data from encoder to + * muxer. The media data object is created by encoder and recycle by the + * destructor. Only allow to store audio or video encoded data in EncodedData. + */ +class EncodedFrameContainer { + public: + // Append encoded frame data + void AppendEncodedFrame(EncodedFrame* aEncodedFrame) { + mEncodedFrames.AppendElement(aEncodedFrame); + } + // Retrieve all of the encoded frames + const nsTArray >& GetEncodedFrames() const { + return mEncodedFrames; + } + + private: + // This container is used to store the video or audio encoded packets. + // Muxer should check mFrameType and get the encoded data type from + // mEncodedFrames. + nsTArray > mEncodedFrames; +}; + +// Represent one encoded frame +class EncodedFrame final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EncodedFrame) + public: + EncodedFrame() : mTimeStamp(0), mDuration(0), mFrameType(UNKNOWN) {} + enum FrameType { + VP8_I_FRAME, // VP8 intraframe + VP8_P_FRAME, // VP8 predicted frame + OPUS_AUDIO_FRAME, // Opus audio frame + VORBIS_AUDIO_FRAME, + AVC_I_FRAME, + AVC_P_FRAME, + AVC_B_FRAME, + AVC_CSD, // AVC codec specific data + AAC_AUDIO_FRAME, + AAC_CSD, // AAC codec specific data + AMR_AUDIO_CSD, + AMR_AUDIO_FRAME, + EVRC_AUDIO_CSD, + EVRC_AUDIO_FRAME, + UNKNOWN // FrameType not set + }; + void SwapInFrameData(nsTArray& aData) { + mFrameData.SwapElements(aData); + } + nsresult SwapOutFrameData(nsTArray& aData) { + if (mFrameType != UNKNOWN) { + // Reset this frame type to UNKNOWN once the data is swapped out. + mFrameData.SwapElements(aData); + mFrameType = UNKNOWN; + return NS_OK; + } + return NS_ERROR_FAILURE; + } + const nsTArray& GetFrameData() const { return mFrameData; } + uint64_t GetTimeStamp() const { return mTimeStamp; } + void SetTimeStamp(uint64_t aTimeStamp) { mTimeStamp = aTimeStamp; } + + uint64_t GetDuration() const { return mDuration; } + void SetDuration(uint64_t aDuration) { mDuration = aDuration; } + + FrameType GetFrameType() const { return mFrameType; } + void SetFrameType(FrameType aFrameType) { mFrameType = aFrameType; } + + private: + // Private destructor, to discourage deletion outside of Release(): + ~EncodedFrame() {} + + // Encoded data + nsTArray mFrameData; + uint64_t mTimeStamp; + // The playback duration of this packet in number of samples + uint64_t mDuration; + // Represent what is in the FrameData + FrameType mFrameType; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/encoder/MediaEncoder.cpp b/dom/media/encoder/MediaEncoder.cpp index c140250b0ddf..a8c5279659b5 100644 --- a/dom/media/encoder/MediaEncoder.cpp +++ b/dom/media/encoder/MediaEncoder.cpp @@ -25,7 +25,6 @@ #include "mozilla/StaticPtr.h" #include "mozilla/TaskQueue.h" #include "mozilla/Unused.h" -#include "Muxer.h" #include "nsIPrincipal.h" #include "nsMimeTypes.h" #include "nsThreadUtils.h" @@ -39,6 +38,10 @@ # include "WebMWriter.h" #endif +#ifdef LOG +# undef LOG +#endif + mozilla::LazyLogModule gMediaEncoderLog("MediaEncoder"); #define LOG(type, msg) MOZ_LOG(gMediaEncoderLog, type, msg) @@ -395,13 +398,14 @@ MediaEncoder::MediaEncoder(TaskQueue* aEncoderThread, VideoTrackEncoder* aVideoEncoder, TrackRate aTrackRate, const nsAString& aMIMEType) : mEncoderThread(aEncoderThread), - mMuxer(MakeUnique(std::move(aWriter))), + mWriter(std::move(aWriter)), mAudioEncoder(aAudioEncoder), mVideoEncoder(aVideoEncoder), mEncoderListener(MakeAndAddRef(mEncoderThread, this)), mStartTime(TimeStamp::Now()), mMIMEType(aMIMEType), mInitialized(false), + mMetadataEncoded(false), mCompleted(false), mError(false), mCanceled(false), @@ -428,14 +432,7 @@ MediaEncoder::MediaEncoder(TaskQueue* aEncoderThread, } } -MediaEncoder::~MediaEncoder() { - MOZ_ASSERT(mListeners.IsEmpty()); - MOZ_ASSERT(!mAudioTrack); - MOZ_ASSERT(!mVideoTrack); - MOZ_ASSERT(!mAudioNode); - MOZ_ASSERT(!mInputPort); - MOZ_ASSERT(!mPipeStream); -} +MediaEncoder::~MediaEncoder() { MOZ_ASSERT(mListeners.IsEmpty()); } void MediaEncoder::RunOnGraph(already_AddRefed aRunnable) { MediaStreamGraphImpl* graph; @@ -654,7 +651,7 @@ already_AddRefed MediaEncoder::CreateEncoder( driftCompensator, aTrackRate, FrameDroppingMode::DISALLOW); } } - writer = MakeUnique(); + writer = MakeUnique(aTrackTypes); mimeType = NS_LITERAL_STRING(VIDEO_WEBM); } else if (MediaEncoder::IsWebMEncoderEnabled() && aMIMEType.EqualsLiteral(AUDIO_WEBM) && @@ -675,7 +672,7 @@ already_AddRefed MediaEncoder::CreateEncoder( } else { mimeType = NS_LITERAL_STRING(AUDIO_WEBM); } - writer = MakeUnique(); + writer = MakeUnique(aTrackTypes); } #endif // MOZ_WEBM_ENCODER else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() && @@ -702,7 +699,7 @@ already_AddRefed MediaEncoder::CreateEncoder( driftCompensator, aTrackRate, FrameDroppingMode::DISALLOW); } } - writer = MakeUnique(); + writer = MakeUnique(aTrackTypes); mimeType = NS_LITERAL_STRING(VIDEO_WEBM); } #endif // MOZ_WEBM_ENCODER @@ -740,78 +737,122 @@ already_AddRefed MediaEncoder::CreateEncoder( audioEncoder, videoEncoder, aTrackRate, mimeType); } +nsresult MediaEncoder::GetEncodedMetadata( + nsTArray>* aOutputBufs, nsAString& aMIMEType) { + AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedMetadata", OTHER); + + MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); + + if (mShutdown) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + if (!mInitialized) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + if (mMetadataEncoded) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + aMIMEType = mMIMEType; + + LOG(LogLevel::Verbose, + ("GetEncodedMetadata TimeStamp = %f", GetEncodeTimeStamp())); + + nsresult rv; + + if (mAudioEncoder) { + if (!mAudioEncoder->IsInitialized()) { + LOG(LogLevel::Error, + ("GetEncodedMetadata Audio encoder not initialized")); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + rv = CopyMetadataToMuxer(mAudioEncoder); + if (NS_FAILED(rv)) { + LOG(LogLevel::Error, ("Failed to Set Audio Metadata")); + SetError(); + return rv; + } + } + if (mVideoEncoder) { + if (!mVideoEncoder->IsInitialized()) { + LOG(LogLevel::Error, + ("GetEncodedMetadata Video encoder not initialized")); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + rv = CopyMetadataToMuxer(mVideoEncoder.get()); + if (NS_FAILED(rv)) { + LOG(LogLevel::Error, ("Failed to Set Video Metadata")); + SetError(); + return rv; + } + } + + rv = mWriter->GetContainerData(aOutputBufs, ContainerWriter::GET_HEADER); + if (NS_FAILED(rv)) { + LOG(LogLevel::Error, ("Writer fail to generate header!")); + SetError(); + return rv; + } + LOG(LogLevel::Verbose, + ("Finish GetEncodedMetadata TimeStamp = %f", GetEncodeTimeStamp())); + mMetadataEncoded = true; + + return NS_OK; +} + nsresult MediaEncoder::GetEncodedData( nsTArray>* aOutputBufs) { AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedData", OTHER); MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); - MOZ_ASSERT(mInitialized); - MOZ_ASSERT_IF(mAudioEncoder, mAudioEncoder->IsInitialized()); - MOZ_ASSERT_IF(mVideoEncoder, mVideoEncoder->IsInitialized()); + + if (!mMetadataEncoded) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } nsresult rv; LOG(LogLevel::Verbose, ("GetEncodedData TimeStamp = %f", GetEncodeTimeStamp())); + EncodedFrameContainer encodedData; - if (mMuxer->NeedsMetadata()) { - nsTArray> meta; - if (mAudioEncoder && !*meta.AppendElement(mAudioEncoder->GetMetadata())) { - LOG(LogLevel::Error, ("Audio metadata is null")); - SetError(); - return NS_ERROR_ABORT; - } - if (mVideoEncoder && !*meta.AppendElement(mVideoEncoder->GetMetadata())) { - LOG(LogLevel::Error, ("Video metadata is null")); - SetError(); - return NS_ERROR_ABORT; - } - - rv = mMuxer->SetMetadata(meta); + if (mVideoEncoder) { + // We're most likely to actually wait for a video frame, so do that first + // to minimize capture offset/lipsync issues. + rv = WriteEncodedDataToMuxer(mVideoEncoder); + LOG(LogLevel::Verbose, + ("Video encoded TimeStamp = %f", GetEncodeTimeStamp())); if (NS_FAILED(rv)) { - LOG(LogLevel::Error, ("SetMetadata failed")); - SetError(); + LOG(LogLevel::Warning, ("Failed to write encoded video data to muxer")); return rv; } } - // First, feed encoded data from encoders to muxer. - - if (mVideoEncoder && !mVideoEncoder->IsEncodingComplete()) { - nsTArray> videoFrames; - rv = mVideoEncoder->GetEncodedTrack(videoFrames); + if (mAudioEncoder) { + rv = WriteEncodedDataToMuxer(mAudioEncoder); + LOG(LogLevel::Verbose, + ("Audio encoded TimeStamp = %f", GetEncodeTimeStamp())); if (NS_FAILED(rv)) { - // Encoding might be canceled. - LOG(LogLevel::Error, ("Failed to get encoded data from video encoder.")); + LOG(LogLevel::Warning, ("Failed to write encoded audio data to muxer")); return rv; } - for (const RefPtr& frame : videoFrames) { - mMuxer->AddEncodedVideoFrame(frame); - } - if (mVideoEncoder->IsEncodingComplete()) { - mMuxer->VideoEndOfStream(); - } } - if (mAudioEncoder && !mAudioEncoder->IsEncodingComplete()) { - nsTArray> audioFrames; - rv = mAudioEncoder->GetEncodedTrack(audioFrames); - if (NS_FAILED(rv)) { - // Encoding might be canceled. - LOG(LogLevel::Error, ("Failed to get encoded data from audio encoder.")); - return rv; - } - for (const RefPtr& frame : audioFrames) { - mMuxer->AddEncodedAudioFrame(frame); - } - if (mAudioEncoder->IsEncodingComplete()) { - mMuxer->AudioEndOfStream(); - } - } - - // Second, get data from muxer. This will do the actual muxing. - - rv = mMuxer->GetData(aOutputBufs); - if (mMuxer->IsFinished()) { + // In audio only or video only case, let unavailable track's flag to be + // true. + bool isAudioCompleted = !mAudioEncoder || mAudioEncoder->IsEncodingComplete(); + bool isVideoCompleted = !mVideoEncoder || mVideoEncoder->IsEncodingComplete(); + rv = mWriter->GetContainerData( + aOutputBufs, + isAudioCompleted && isVideoCompleted ? ContainerWriter::FLUSH_NEEDED : 0); + if (mWriter->IsWritingComplete()) { mCompleted = true; Shutdown(); } @@ -819,9 +860,7 @@ nsresult MediaEncoder::GetEncodedData( LOG(LogLevel::Verbose, ("END GetEncodedData TimeStamp=%f " "mCompleted=%d, aComplete=%d, vComplete=%d", - GetEncodeTimeStamp(), mCompleted, - !mAudioEncoder || mAudioEncoder->IsEncodingComplete(), - !mVideoEncoder || mVideoEncoder->IsEncodingComplete())); + GetEncodeTimeStamp(), mCompleted, isAudioCompleted, isVideoCompleted)); return rv; } @@ -865,6 +904,64 @@ void MediaEncoder::Shutdown() { } } +nsresult MediaEncoder::WriteEncodedDataToMuxer(TrackEncoder* aTrackEncoder) { + AUTO_PROFILER_LABEL("MediaEncoder::WriteEncodedDataToMuxer", OTHER); + + MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); + + if (!aTrackEncoder) { + NS_ERROR("No track encoder to get data from"); + return NS_ERROR_FAILURE; + } + + if (aTrackEncoder->IsEncodingComplete()) { + return NS_OK; + } + + EncodedFrameContainer encodedData; + nsresult rv = aTrackEncoder->GetEncodedTrack(encodedData); + if (NS_FAILED(rv)) { + // Encoding might be canceled. + LOG(LogLevel::Error, ("Failed to get encoded data from encoder.")); + SetError(); + return rv; + } + rv = mWriter->WriteEncodedTrack( + encodedData, + aTrackEncoder->IsEncodingComplete() ? ContainerWriter::END_OF_STREAM : 0); + if (NS_FAILED(rv)) { + LOG(LogLevel::Error, + ("Failed to write encoded track to the media container.")); + SetError(); + } + return rv; +} + +nsresult MediaEncoder::CopyMetadataToMuxer(TrackEncoder* aTrackEncoder) { + AUTO_PROFILER_LABEL("MediaEncoder::CopyMetadataToMuxer", OTHER); + + MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); + + if (!aTrackEncoder) { + NS_ERROR("No track encoder to get metadata from"); + return NS_ERROR_FAILURE; + } + + RefPtr meta = aTrackEncoder->GetMetadata(); + if (meta == nullptr) { + LOG(LogLevel::Error, ("metadata == null")); + SetError(); + return NS_ERROR_ABORT; + } + + nsresult rv = mWriter->SetMetadata(meta); + if (NS_FAILED(rv)) { + LOG(LogLevel::Error, ("SetMetadata failed")); + SetError(); + } + return rv; +} + bool MediaEncoder::IsShutdown() { MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); return mShutdown; @@ -873,8 +970,6 @@ bool MediaEncoder::IsShutdown() { void MediaEncoder::Cancel() { MOZ_ASSERT(NS_IsMainThread()); - Stop(); - RefPtr self = this; nsresult rv = mEncoderThread->Dispatch(NewRunnableFrom([self]() mutable { self->mCanceled = true; @@ -945,11 +1040,6 @@ bool MediaEncoder::IsWebMEncoderEnabled() { } #endif -const nsString& MediaEncoder::MimeType() const { - MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); - return mMIMEType; -} - void MediaEncoder::NotifyInitialized() { MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); @@ -1030,5 +1120,3 @@ void MediaEncoder::SetVideoKeyFrameInterval(int32_t aVideoKeyFrameInterval) { } } // namespace mozilla - -#undef LOG diff --git a/dom/media/encoder/MediaEncoder.h b/dom/media/encoder/MediaEncoder.h index 431d91ac2e35..0e457f09ca2b 100644 --- a/dom/media/encoder/MediaEncoder.h +++ b/dom/media/encoder/MediaEncoder.h @@ -8,7 +8,6 @@ #include "ContainerWriter.h" #include "CubebUtils.h" -#include "MediaQueue.h" #include "MediaStreamGraph.h" #include "MediaStreamListener.h" #include "mozilla/DebugOnly.h" @@ -20,7 +19,6 @@ namespace mozilla { class DriftCompensator; -class Muxer; class Runnable; class TaskQueue; @@ -78,21 +76,29 @@ class MediaEncoderListener { * been initialized and when there's data available. * => encoder->RegisterListener(listener); * - * 3) When the MediaEncoderListener is notified that the MediaEncoder has - * data available, we can encode data. This also encodes metadata on its - * first invocation. + * 3) Connect the MediaStreamTracks to be recorded. + * => encoder->ConnectMediaStreamTrack(track); + * This creates the corresponding TrackEncoder and connects the track and + * the TrackEncoder through a track listener. This also starts encoding. + * + * 4) When the MediaEncoderListener is notified that the MediaEncoder is + * initialized, we can encode metadata. + * => encoder->GetEncodedMetadata(...); + * + * 5) When the MediaEncoderListener is notified that the MediaEncoder has + * data available, we can encode data. * => encoder->GetEncodedData(...); * - * 4) To stop encoding, there are multiple options: + * 6) To stop encoding, there are multiple options: * - * 4.1) Stop() for a graceful stop. + * 6.1) Stop() for a graceful stop. * => encoder->Stop(); * - * 4.2) Cancel() for an immediate stop, if you don't need the data currently + * 6.2) Cancel() for an immediate stop, if you don't need the data currently * buffered. * => encoder->Cancel(); * - * 4.3) When all input tracks end, the MediaEncoder will automatically stop + * 6.3) When all input tracks end, the MediaEncoder will automatically stop * and shut down. */ class MediaEncoder { @@ -151,12 +157,24 @@ class MediaEncoder { uint32_t aAudioBitrate, uint32_t aVideoBitrate, uint8_t aTrackTypes, TrackRate aTrackRate); + /** + * Encodes raw metadata for all tracks to aOutputBufs. aMIMEType is the valid + * mime-type for the returned container data. The buffer of container data is + * allocated in ContainerWriter::GetContainerData(). + * + * Should there be insufficient input data for either track encoder to infer + * the metadata, or if metadata has already been encoded, we return an error + * and the output arguments are undefined. Otherwise we return NS_OK. + */ + nsresult GetEncodedMetadata(nsTArray>* aOutputBufs, + nsAString& aMIMEType); /** * Encodes raw data for all tracks to aOutputBufs. The buffer of container * data is allocated in ContainerWriter::GetContainerData(). * - * On its first call, metadata is also encoded. TrackEncoders must have been - * initialized before this is called. + * This implies that metadata has already been encoded and that all track + * encoders are still active. Should either implication break, we return an + * error and the output argument is undefined. Otherwise we return NS_OK. */ nsresult GetEncodedData(nsTArray>* aOutputBufs); @@ -178,8 +196,6 @@ class MediaEncoder { static bool IsWebMEncoderEnabled(); #endif - const nsString& MimeType() const; - /** * Notifies listeners that this MediaEncoder has been initialized. */ @@ -237,10 +253,15 @@ class MediaEncoder { */ void SetError(); + // Get encoded data from trackEncoder and write to muxer + nsresult WriteEncodedDataToMuxer(TrackEncoder* aTrackEncoder); + // Get metadata from trackEncoder and copy to muxer + nsresult CopyMetadataToMuxer(TrackEncoder* aTrackEncoder); + const RefPtr mEncoderThread; const RefPtr mDriftCompensator; - UniquePtr mMuxer; + UniquePtr mWriter; RefPtr mAudioEncoder; RefPtr mAudioListener; RefPtr mVideoEncoder; @@ -263,10 +284,10 @@ class MediaEncoder { // A video track that we are encoding. Will be null if the input stream // doesn't contain video on start() or if the input is an AudioNode. RefPtr mVideoTrack; - TimeStamp mStartTime; - const nsString mMIMEType; + nsString mMIMEType; bool mInitialized; + bool mMetadataEncoded; bool mCompleted; bool mError; bool mCanceled; diff --git a/dom/media/encoder/Muxer.cpp b/dom/media/encoder/Muxer.cpp deleted file mode 100644 index f20d1a7e3238..000000000000 --- a/dom/media/encoder/Muxer.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ -/* 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 "Muxer.h" - -#include "ContainerWriter.h" - -namespace mozilla { - -LazyLogModule gMuxerLog("Muxer"); -#define LOG(type, ...) MOZ_LOG(gMuxerLog, type, (__VA_ARGS__)) - -Muxer::Muxer(UniquePtr aWriter) - : mWriter(std::move(aWriter)) {} - -bool Muxer::IsFinished() { return mWriter->IsWritingComplete(); } - -nsresult Muxer::SetMetadata( - const nsTArray>& aMetadata) { - nsresult rv = mWriter->SetMetadata(aMetadata); - if (NS_FAILED(rv)) { - LOG(LogLevel::Error, "%p Setting metadata failed, tracks=%zu", this, - aMetadata.Length()); - return rv; - } - - for (const auto& track : aMetadata) { - switch (track->GetKind()) { - case TrackMetadataBase::METADATA_OPUS: { - // In the case of Opus we need to calculate the codec delay based on the - // pre-skip. For more information see: - // https://tools.ietf.org/html/rfc7845#section-4.2 - // Calculate offset in microseconds - OpusMetadata* opusMeta = static_cast(track.get()); - mAudioCodecDelay = static_cast( - LittleEndian::readUint16(opusMeta->mIdHeader.Elements() + 10) * - PR_USEC_PER_SEC / 48000); - MOZ_FALLTHROUGH; - } - case TrackMetadataBase::METADATA_VORBIS: - case TrackMetadataBase::METADATA_AAC: - case TrackMetadataBase::METADATA_AMR: - case TrackMetadataBase::METADATA_EVRC: - MOZ_ASSERT(!mHasAudio, "Only one audio track supported"); - mHasAudio = true; - break; - case TrackMetadataBase::METADATA_VP8: - MOZ_ASSERT(!mHasVideo, "Only one video track supported"); - mHasVideo = true; - break; - default: - MOZ_CRASH("Unknown codec metadata"); - }; - } - mMetadataSet = true; - MOZ_ASSERT(mHasAudio || mHasVideo); - if (!mHasAudio) { - mEncodedAudioFrames.Finish(); - MOZ_ASSERT(mEncodedAudioFrames.AtEndOfStream()); - } - if (!mHasVideo) { - mEncodedVideoFrames.Finish(); - MOZ_ASSERT(mEncodedVideoFrames.AtEndOfStream()); - } - LOG(LogLevel::Info, "%p Metadata set; audio=%d, video=%d", this, mHasAudio, - mHasVideo); - return rv; -} - -void Muxer::AddEncodedAudioFrame(EncodedFrame* aFrame) { - MOZ_ASSERT(mMetadataSet); - MOZ_ASSERT(mHasAudio); - if (aFrame->mFrameType == EncodedFrame::FrameType::OPUS_AUDIO_FRAME) { - aFrame->mTime += mAudioCodecDelay; - } - mEncodedAudioFrames.Push(aFrame); - LOG(LogLevel::Verbose, - "%p Added audio frame of type %u, [start %" PRIu64 ", end %" PRIu64 ")", - this, aFrame->mFrameType, aFrame->mTime, - aFrame->mTime + aFrame->mDuration); -} - -void Muxer::AddEncodedVideoFrame(EncodedFrame* aFrame) { - MOZ_ASSERT(mMetadataSet); - MOZ_ASSERT(mHasVideo); - mEncodedVideoFrames.Push(aFrame); - LOG(LogLevel::Verbose, - "%p Added video frame of type %u, [start %" PRIu64 ", end %" PRIu64 ")", - this, aFrame->mFrameType, aFrame->mTime, - aFrame->mTime + aFrame->mDuration); -} - -void Muxer::AudioEndOfStream() { - MOZ_ASSERT(mMetadataSet); - MOZ_ASSERT(mHasAudio); - LOG(LogLevel::Info, "%p Reached audio EOS", this); - mEncodedAudioFrames.Finish(); -} - -void Muxer::VideoEndOfStream() { - MOZ_ASSERT(mMetadataSet); - MOZ_ASSERT(mHasVideo); - LOG(LogLevel::Info, "%p Reached video EOS", this); - mEncodedVideoFrames.Finish(); -} - -nsresult Muxer::GetData(nsTArray>* aOutputBuffers) { - MOZ_ASSERT(mMetadataSet); - MOZ_ASSERT(mHasAudio || mHasVideo); - - nsresult rv; - if (!mMetadataEncoded) { - rv = mWriter->GetContainerData(aOutputBuffers, ContainerWriter::GET_HEADER); - if (NS_FAILED(rv)) { - LOG(LogLevel::Error, "%p Failed getting metadata from writer", this); - return rv; - } - mMetadataEncoded = true; - } - - if (mEncodedAudioFrames.GetSize() == 0 && !mEncodedAudioFrames.IsFinished() && - mEncodedVideoFrames.GetSize() == 0 && !mEncodedVideoFrames.IsFinished()) { - // Nothing to mux. - return NS_OK; - } - - rv = Mux(); - if (NS_FAILED(rv)) { - LOG(LogLevel::Error, "%p Failed muxing data into writer", this); - return rv; - } - - MOZ_ASSERT_IF( - mEncodedAudioFrames.IsFinished() && mEncodedVideoFrames.IsFinished(), - mEncodedAudioFrames.AtEndOfStream()); - MOZ_ASSERT_IF( - mEncodedAudioFrames.IsFinished() && mEncodedVideoFrames.IsFinished(), - mEncodedVideoFrames.AtEndOfStream()); - uint32_t flags = - mEncodedAudioFrames.AtEndOfStream() && mEncodedVideoFrames.AtEndOfStream() - ? ContainerWriter::FLUSH_NEEDED - : 0; - - if (mEncodedAudioFrames.AtEndOfStream() && - mEncodedVideoFrames.AtEndOfStream()) { - LOG(LogLevel::Info, "%p All data written", this); - } - - return mWriter->GetContainerData(aOutputBuffers, flags); -} - -nsresult Muxer::Mux() { - MOZ_ASSERT(mMetadataSet); - MOZ_ASSERT(mHasAudio || mHasVideo); - - nsTArray> frames; - // The times at which we expect our next video and audio frames. These are - // based on the time + duration (GetEndTime()) of the last seen frames. - // Assumes that the encoders write the correct duration for frames.; - uint64_t expectedNextVideoTime = 0; - uint64_t expectedNextAudioTime = 0; - // Interleave frames until we're out of audio or video - while (mEncodedVideoFrames.GetSize() > 0 && - mEncodedAudioFrames.GetSize() > 0) { - RefPtr videoFrame = mEncodedVideoFrames.PeekFront(); - RefPtr audioFrame = mEncodedAudioFrames.PeekFront(); - // For any expected time our frames should occur at or after that time. - MOZ_ASSERT(videoFrame->mTime >= expectedNextVideoTime); - MOZ_ASSERT(audioFrame->mTime >= expectedNextAudioTime); - if (videoFrame->mTime <= audioFrame->mTime) { - expectedNextVideoTime = videoFrame->GetEndTime(); - RefPtr frame = mEncodedVideoFrames.PopFront(); - frames.AppendElement(frame); - } else { - expectedNextAudioTime = audioFrame->GetEndTime(); - RefPtr frame = mEncodedAudioFrames.PopFront(); - frames.AppendElement(frame); - } - } - - // If we're out of audio we still may be able to add more video... - if (mEncodedAudioFrames.GetSize() == 0) { - while (mEncodedVideoFrames.GetSize() > 0) { - if (!mEncodedAudioFrames.AtEndOfStream() && - mEncodedVideoFrames.PeekFront()->mTime > expectedNextAudioTime) { - // Audio encoding is not complete and since the video frame comes - // after our next audio frame we cannot safely add it. - break; - } - frames.AppendElement(mEncodedVideoFrames.PopFront()); - } - } - - // If we're out of video we still may be able to add more audio... - if (mEncodedVideoFrames.GetSize() == 0) { - while (mEncodedAudioFrames.GetSize() > 0) { - if (!mEncodedVideoFrames.AtEndOfStream() && - mEncodedAudioFrames.PeekFront()->mTime > expectedNextVideoTime) { - // Video encoding is not complete and since the audio frame comes - // after our next video frame we cannot safely add it. - break; - } - frames.AppendElement(mEncodedAudioFrames.PopFront()); - } - } - - LOG(LogLevel::Debug, - "%p Muxed data, remaining-audio=%zu, remaining-video=%zu", this, - mEncodedAudioFrames.GetSize(), mEncodedVideoFrames.GetSize()); - - // If encoding is complete for both encoders we should signal end of stream, - // otherwise we keep going. - uint32_t flags = - mEncodedVideoFrames.AtEndOfStream() && mEncodedAudioFrames.AtEndOfStream() - ? ContainerWriter::END_OF_STREAM - : 0; - nsresult rv = mWriter->WriteEncodedTrack(frames, flags); - if (NS_FAILED(rv)) { - LOG(LogLevel::Error, "Error! Failed to write muxed data to the container"); - } - return rv; -} - -} // namespace mozilla - -#undef LOG diff --git a/dom/media/encoder/Muxer.h b/dom/media/encoder/Muxer.h deleted file mode 100644 index f2f93582d3cf..000000000000 --- a/dom/media/encoder/Muxer.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ -/* 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 DOM_MEDIA_ENCODER_MUXER_H_ -#define DOM_MEDIA_ENCODER_MUXER_H_ - -#include "MediaQueue.h" - -namespace mozilla { - -class ContainerWriter; - -// Generic Muxer class that helps pace the output from track encoders to the -// ContainerWriter, so time never appears to go backwards. -// Note that the entire class is written for single threaded access. -class Muxer { - public: - explicit Muxer(UniquePtr aWriter); - ~Muxer() = default; - - // Returns true when all tracks have ended, and all data has been muxed and - // fetched. - bool IsFinished(); - - // Returns true if this muxer has not been given metadata yet. - bool NeedsMetadata() const { return !mMetadataSet; } - - // Sets metadata for all tracks. This may only be called once. - nsresult SetMetadata(const nsTArray>& aMetadata); - - // Adds an encoded audio frame for muxing - void AddEncodedAudioFrame(EncodedFrame* aFrame); - - // Adds an encoded video frame for muxing - void AddEncodedVideoFrame(EncodedFrame* aFrame); - - // Marks the audio track as ended. Once all tracks for which we have metadata - // have ended, GetData() will drain and the muxer will be marked as finished. - void AudioEndOfStream(); - - // Marks the video track as ended. Once all tracks for which we have metadata - // have ended, GetData() will drain and the muxer will be marked as finished. - void VideoEndOfStream(); - - // Gets the data that has been muxed and written into the container so far. - nsresult GetData(nsTArray>* aOutputBuffers); - - private: - // Writes data in MediaQueues to the ContainerWriter. - nsresult Mux(); - - // Audio frames that have been encoded and are pending write to the muxer. - MediaQueue mEncodedAudioFrames; - // Video frames that have been encoded and are pending write to the muxer. - MediaQueue mEncodedVideoFrames; - // The writer for the specific container we're recording into. - UniquePtr mWriter; - // How much each audio time stamp should be delayed in microseconds. Used to - // adjust for opus codec delay. - uint64_t mAudioCodecDelay = 0; - // True once metadata has been set in the muxer. - bool mMetadataSet = false; - // True once metadata has been written to file. - bool mMetadataEncoded = false; - // True if metadata is set and contains an audio track. - bool mHasAudio = false; - // True if metadata is set and contains a video track. - bool mHasVideo = false; -}; -} // namespace mozilla - -#endif diff --git a/dom/media/encoder/OpusTrackEncoder.cpp b/dom/media/encoder/OpusTrackEncoder.cpp index dd037dbc83bc..ba02a2ee1155 100644 --- a/dom/media/encoder/OpusTrackEncoder.cpp +++ b/dom/media/encoder/OpusTrackEncoder.cpp @@ -10,6 +10,7 @@ #include +#undef LOG #define LOG(args, ...) namespace mozilla { @@ -227,8 +228,7 @@ already_AddRefed OpusTrackEncoder::GetMetadata() { return meta.forget(); } -nsresult OpusTrackEncoder::GetEncodedTrack( - nsTArray>& aData) { +nsresult OpusTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) { AUTO_PROFILER_LABEL("OpusTrackEncoder::GetEncodedTrack", OTHER); MOZ_ASSERT(mInitialized || mCanceled); @@ -325,7 +325,7 @@ nsresult OpusTrackEncoder::GetEncodedTrack( MOZ_ASSERT(frameCopied <= 3844, "frameCopied exceeded expected range"); RefPtr audiodata = new EncodedFrame(); - audiodata->mFrameType = EncodedFrame::OPUS_AUDIO_FRAME; + audiodata->SetFrameType(EncodedFrame::OPUS_AUDIO_FRAME); int framesInPCM = frameCopied; if (mResampler) { AutoTArray resamplingDest; @@ -367,10 +367,10 @@ nsresult OpusTrackEncoder::GetEncodedTrack( mResampledLeftover.Length()); // This is always at 48000Hz. framesInPCM = framesLeft + outframesToCopy; - audiodata->mDuration = framesInPCM; + audiodata->SetDuration(framesInPCM); } else { // The ogg time stamping and pre-skip is always timed at 48000. - audiodata->mDuration = frameCopied * (kOpusSamplingRate / mSamplingRate); + audiodata->SetDuration(frameCopied * (kOpusSamplingRate / mSamplingRate)); } // Remove the raw data which has been pulled to pcm buffer. @@ -422,16 +422,14 @@ nsresult OpusTrackEncoder::GetEncodedTrack( audiodata->SwapInFrameData(frameData); // timestamp should be the time of the first sample - audiodata->mTime = mOutputTimeStamp; + audiodata->SetTimeStamp(mOutputTimeStamp); mOutputTimeStamp += FramesToUsecs(GetPacketDuration(), kOpusSamplingRate).value(); LOG("[Opus] mOutputTimeStamp %lld.", mOutputTimeStamp); - aData.AppendElement(audiodata); + aData.AppendEncodedFrame(audiodata); } return result >= 0 ? NS_OK : NS_ERROR_FAILURE; } } // namespace mozilla - -#undef LOG diff --git a/dom/media/encoder/OpusTrackEncoder.h b/dom/media/encoder/OpusTrackEncoder.h index fc42d3b32ffe..600fda9bb9f5 100644 --- a/dom/media/encoder/OpusTrackEncoder.h +++ b/dom/media/encoder/OpusTrackEncoder.h @@ -33,7 +33,7 @@ class OpusTrackEncoder : public AudioTrackEncoder { already_AddRefed GetMetadata() override; - nsresult GetEncodedTrack(nsTArray>& aData) override; + nsresult GetEncodedTrack(EncodedFrameContainer& aData) override; protected: int GetPacketDuration() override; diff --git a/dom/media/encoder/TrackEncoder.cpp b/dom/media/encoder/TrackEncoder.cpp index 81c5fde4f62e..ab96a8a7b58c 100644 --- a/dom/media/encoder/TrackEncoder.cpp +++ b/dom/media/encoder/TrackEncoder.cpp @@ -763,5 +763,3 @@ void VideoTrackEncoder::SetKeyFrameInterval(int32_t aKeyFrameInterval) { } } // namespace mozilla - -#undef TRACK_LOG diff --git a/dom/media/encoder/TrackEncoder.h b/dom/media/encoder/TrackEncoder.h index 270c858c9e86..957f4cd3abb5 100644 --- a/dom/media/encoder/TrackEncoder.h +++ b/dom/media/encoder/TrackEncoder.h @@ -7,7 +7,7 @@ #define TrackEncoder_h_ #include "AudioSegment.h" -#include "EncodedFrame.h" +#include "EncodedFrameContainer.h" #include "MediaStreamGraph.h" #include "StreamTracks.h" #include "TrackMetadataBase.h" @@ -82,7 +82,7 @@ class TrackEncoder { * Encodes raw segments. Result data is returned in aData, and called on the * worker thread. */ - virtual nsresult GetEncodedTrack(nsTArray>& aData) = 0; + virtual nsresult GetEncodedTrack(EncodedFrameContainer& aData) = 0; /** * Returns true once this TrackEncoder is initialized. diff --git a/dom/media/encoder/VP8TrackEncoder.cpp b/dom/media/encoder/VP8TrackEncoder.cpp index aa9b102a5c5d..a8f0711b791b 100644 --- a/dom/media/encoder/VP8TrackEncoder.cpp +++ b/dom/media/encoder/VP8TrackEncoder.cpp @@ -220,8 +220,7 @@ already_AddRefed VP8TrackEncoder::GetMetadata() { return meta.forget(); } -nsresult VP8TrackEncoder::GetEncodedPartitions( - nsTArray>& aData) { +nsresult VP8TrackEncoder::GetEncodedPartitions(EncodedFrameContainer& aData) { vpx_codec_iter_t iter = nullptr; EncodedFrame::FrameType frameType = EncodedFrame::VP8_P_FRAME; nsTArray frameData; @@ -250,7 +249,7 @@ nsresult VP8TrackEncoder::GetEncodedPartitions( if (!frameData.IsEmpty()) { // Copy the encoded data to aData. EncodedFrame* videoData = new EncodedFrame(); - videoData->mFrameType = frameType; + videoData->SetFrameType(frameType); // Convert the timestamp and duration to Usecs. CheckedInt64 timestamp = FramesToUsecs(pkt->data.frame.pts, mTrackRate); @@ -258,7 +257,7 @@ nsresult VP8TrackEncoder::GetEncodedPartitions( NS_ERROR("Microsecond timestamp overflow"); return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } - videoData->mTime = (uint64_t)timestamp.value(); + videoData->SetTimeStamp((uint64_t)timestamp.value()); mExtractedDuration += pkt->data.frame.duration; if (!mExtractedDuration.isValid()) { @@ -280,13 +279,14 @@ nsresult VP8TrackEncoder::GetEncodedPartitions( } mExtractedDurationUs = totalDuration; - videoData->mDuration = (uint64_t)duration.value(); + videoData->SetDuration((uint64_t)duration.value()); videoData->SwapInFrameData(frameData); VP8LOG(LogLevel::Verbose, "GetEncodedPartitions TimeStamp %" PRIu64 ", Duration %" PRIu64 ", FrameType %d", - videoData->mTime, videoData->mDuration, videoData->mFrameType); - aData.AppendElement(videoData); + videoData->GetTimeStamp(), videoData->GetDuration(), + videoData->GetFrameType()); + aData.AppendEncodedFrame(videoData); } return pkt ? NS_OK : NS_ERROR_NOT_AVAILABLE; @@ -441,8 +441,7 @@ VP8TrackEncoder::EncodeOperation VP8TrackEncoder::GetNextEncodeOperation( * encode it. * 4. Remove the encoded chunks in mSourceSegment after for-loop. */ -nsresult VP8TrackEncoder::GetEncodedTrack( - nsTArray>& aData) { +nsresult VP8TrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) { AUTO_PROFILER_LABEL("VP8TrackEncoder::GetEncodedTrack", OTHER); MOZ_ASSERT(mInitialized || mCanceled); @@ -510,7 +509,7 @@ nsresult VP8TrackEncoder::GetEncodedTrack( // because this frame will be skipped. VP8LOG(LogLevel::Warning, "MediaRecorder lagging behind. Skipping a frame."); - RefPtr last = aData.LastElement(); + RefPtr last = aData.GetEncodedFrames().LastElement(); if (last) { mExtractedDuration += chunk.mDuration; if (!mExtractedDuration.isValid()) { @@ -526,7 +525,8 @@ nsresult VP8TrackEncoder::GetEncodedTrack( NS_ERROR("skipped duration overflow"); return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; } - last->mDuration += static_cast(skippedDuration.value()); + last->SetDuration(last->GetDuration() + + (static_cast(skippedDuration.value()))); } } @@ -570,5 +570,3 @@ nsresult VP8TrackEncoder::GetEncodedTrack( } } // namespace mozilla - -#undef VP8LOG diff --git a/dom/media/encoder/VP8TrackEncoder.h b/dom/media/encoder/VP8TrackEncoder.h index 2c8a302502ec..ed8eb3ebe3ef 100644 --- a/dom/media/encoder/VP8TrackEncoder.h +++ b/dom/media/encoder/VP8TrackEncoder.h @@ -34,7 +34,7 @@ class VP8TrackEncoder : public VideoTrackEncoder { already_AddRefed GetMetadata() final; - nsresult GetEncodedTrack(nsTArray>& aData) final; + nsresult GetEncodedTrack(EncodedFrameContainer& aData) final; protected: nsresult Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, @@ -50,7 +50,7 @@ class VP8TrackEncoder : public VideoTrackEncoder { // null for EOS detection. // NS_OK if some data was appended to aData. // An error nsresult otherwise. - nsresult GetEncodedPartitions(nsTArray>& aData); + nsresult GetEncodedPartitions(EncodedFrameContainer& aData); // Prepare the input data to the mVPXImageWrapper for encoding. nsresult PrepareRawFrame(VideoChunk& aChunk); diff --git a/dom/media/encoder/moz.build b/dom/media/encoder/moz.build index dd1730553fc5..9fe2c9459f40 100644 --- a/dom/media/encoder/moz.build +++ b/dom/media/encoder/moz.build @@ -9,7 +9,7 @@ with Files('*'): EXPORTS += [ 'ContainerWriter.h', - 'EncodedFrame.h', + 'EncodedFrameContainer.h', 'MediaEncoder.h', 'OpusTrackEncoder.h', 'TrackEncoder.h', @@ -18,7 +18,6 @@ EXPORTS += [ UNIFIED_SOURCES += [ 'MediaEncoder.cpp', - 'Muxer.cpp', 'OpusTrackEncoder.cpp', 'TrackEncoder.cpp', ] diff --git a/dom/media/gtest/AudioGenerator.cpp b/dom/media/gtest/AudioGenerator.cpp deleted file mode 100644 index 49b53f3e2203..000000000000 --- a/dom/media/gtest/AudioGenerator.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* -*- 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 https://mozilla.org/MPL/2.0/. */ - -#include "AudioGenerator.h" - -#include "AudioSegment.h" - -using namespace mozilla; - -AudioGenerator::AudioGenerator(int32_t aChannels, int32_t aSampleRate) - : mGenerator(aSampleRate, 1000), mChannels(aChannels) {} - -void AudioGenerator::Generate(AudioSegment& aSegment, const int32_t& aSamples) { - RefPtr buffer = - SharedBuffer::Create(aSamples * sizeof(int16_t)); - int16_t* dest = static_cast(buffer->Data()); - mGenerator.generate(dest, aSamples); - AutoTArray channels; - for (int32_t i = 0; i < mChannels; i++) { - channels.AppendElement(dest); - } - aSegment.AppendFrames(buffer.forget(), channels, aSamples, - PRINCIPAL_HANDLE_NONE); -} diff --git a/dom/media/gtest/AudioGenerator.h b/dom/media/gtest/AudioGenerator.h deleted file mode 100644 index ff3bd5436fbc..000000000000 --- a/dom/media/gtest/AudioGenerator.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -*- 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 https://mozilla.org/MPL/2.0/. */ - -#ifndef DOM_MEDIA_GTEST_AUDIO_GENERATOR_H_ -#define DOM_MEDIA_GTEST_AUDIO_GENERATOR_H_ - -#include "prtime.h" -#include "SineWaveGenerator.h" - -namespace mozilla { -class AudioSegment; -} - -class AudioGenerator { - public: - AudioGenerator(int32_t aChannels, int32_t aSampleRate); - void Generate(mozilla::AudioSegment& aSegment, const int32_t& aSamples); - - private: - mozilla::SineWaveGenerator mGenerator; - const int32_t mChannels; -}; - -#endif // DOM_MEDIA_GTEST_AUDIO_GENERATOR_H_ diff --git a/dom/media/gtest/TestAudioTrackEncoder.cpp b/dom/media/gtest/TestAudioTrackEncoder.cpp index dac8474824c5..a12eac7d68c8 100644 --- a/dom/media/gtest/TestAudioTrackEncoder.cpp +++ b/dom/media/gtest/TestAudioTrackEncoder.cpp @@ -5,11 +5,33 @@ #include "gtest/gtest.h" #include "OpusTrackEncoder.h" - -#include "AudioGenerator.h" +#include "SineWaveGenerator.h" using namespace mozilla; +class AudioGenerator { + public: + AudioGenerator(int32_t aChannels, int32_t aSampleRate) + : mGenerator(aSampleRate, 1000), mChannels(aChannels) {} + + void Generate(AudioSegment& aSegment, const int32_t& aSamples) { + RefPtr buffer = + SharedBuffer::Create(aSamples * sizeof(int16_t)); + int16_t* dest = static_cast(buffer->Data()); + mGenerator.generate(dest, aSamples); + AutoTArray channels; + for (int32_t i = 0; i < mChannels; i++) { + channels.AppendElement(dest); + } + aSegment.AppendFrames(buffer.forget(), channels, aSamples, + PRINCIPAL_HANDLE_NONE); + } + + private: + SineWaveGenerator mGenerator; + const int32_t mChannels; +}; + class TestOpusTrackEncoder : public OpusTrackEncoder { public: TestOpusTrackEncoder() : OpusTrackEncoder(90000) {} @@ -201,13 +223,13 @@ TEST(OpusAudioTrackEncoder, FrameEncode) encoder.AppendAudioSegment(std::move(segment)); - nsTArray> frames; - EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); // Verify that encoded data is 5 seconds long. uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } // 44100 as used above gets resampled to 48000 for opus. const uint64_t five = 48000 * 5; diff --git a/dom/media/gtest/TestMuxer.cpp b/dom/media/gtest/TestMuxer.cpp deleted file mode 100644 index 17981218f38c..000000000000 --- a/dom/media/gtest/TestMuxer.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ -/* 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 - -#include "ContainerWriter.h" -#include "EncodedFrame.h" -#include "gtest/gtest.h" -#include "gmock/gmock.h" -#include "Muxer.h" -#include "OpusTrackEncoder.h" -#include "WebMWriter.h" - -using namespace mozilla; -using testing::_; -using testing::ElementsAre; -using testing::Return; -using testing::StaticAssertTypeEq; - -static RefPtr CreateOpusMetadata(int32_t aChannels, - float aSamplingFrequency, - size_t aIdHeaderSize, - size_t aCommentHeaderSize) { - auto opusMetadata = MakeRefPtr(); - opusMetadata->mChannels = aChannels; - opusMetadata->mSamplingFrequency = aSamplingFrequency; - opusMetadata->mIdHeader.SetLength(aIdHeaderSize); - for (size_t i = 0; i < opusMetadata->mIdHeader.Length(); i++) { - opusMetadata->mIdHeader[i] = 0; - } - opusMetadata->mCommentHeader.SetLength(aCommentHeaderSize); - for (size_t i = 0; i < opusMetadata->mCommentHeader.Length(); i++) { - opusMetadata->mCommentHeader[i] = 0; - } - return opusMetadata; -} - -static RefPtr CreateVP8Metadata(int32_t aWidth, - int32_t aHeight) { - auto vp8Metadata = MakeRefPtr(); - vp8Metadata->mWidth = aWidth; - vp8Metadata->mDisplayWidth = aWidth; - vp8Metadata->mHeight = aHeight; - vp8Metadata->mDisplayHeight = aHeight; - return vp8Metadata; -} - -static RefPtr CreateFrame(EncodedFrame::FrameType aType, - uint64_t aTimeUs, uint64_t aDurationUs, - size_t aDataSize) { - auto frame = MakeRefPtr(); - frame->mTime = aTimeUs; - if (aType == EncodedFrame::OPUS_AUDIO_FRAME) { - // Opus duration is in samples, so figure out how many samples will put us - // closest to aDurationUs without going over. - frame->mDuration = UsecsToFrames(aDurationUs, 48000).value(); - } else { - frame->mDuration = aDurationUs; - } - frame->mFrameType = aType; - - nsTArray data; - data.SetLength(aDataSize); - frame->SwapInFrameData(data); - return frame; -} - -namespace testing { -namespace internal { -// This makes the googletest framework treat nsTArray as an std::vector, so all -// the regular Matchers (like ElementsAre) work for it. -template -class StlContainerView> { - public: - typedef GTEST_REMOVE_CONST_(Element) RawElement; - typedef std::vector type; - typedef const type const_reference; - static const_reference ConstReference(const nsTArray& aContainer) { - StaticAssertTypeEq(); - return type(aContainer.begin(), aContainer.end()); - } - static type Copy(const nsTArray& aContainer) { - return type(aContainer.begin(), aContainer.end()); - } -}; -} // namespace internal -} // namespace testing - -class MockContainerWriter : public ContainerWriter { - public: - MOCK_METHOD2(WriteEncodedTrack, - nsresult(const nsTArray>&, uint32_t)); - MOCK_METHOD1(SetMetadata, - nsresult(const nsTArray>&)); - MOCK_METHOD0(IsWritingComplete, bool()); - MOCK_METHOD2(GetContainerData, - nsresult(nsTArray>*, uint32_t)); -}; - -TEST(MuxerTest, AudioOnly) -{ - MockContainerWriter* writer = new MockContainerWriter(); - Muxer muxer(WrapUnique(writer)); - - // Prepare data - - auto opusMeta = CreateOpusMetadata(1, 48000, 16, 16); - auto audioFrame = CreateFrame(EncodedFrame::OPUS_AUDIO_FRAME, 0, 48000, 4096); - - // Expectations - - EXPECT_CALL(*writer, SetMetadata(ElementsAre(opusMeta))) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, WriteEncodedTrack(ElementsAre(audioFrame), - ContainerWriter::END_OF_STREAM)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, GetContainerData(_, ContainerWriter::GET_HEADER)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, GetContainerData(_, ContainerWriter::FLUSH_NEEDED)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, IsWritingComplete()).Times(0); - - // Test - - EXPECT_EQ(muxer.SetMetadata(nsTArray>({opusMeta})), - NS_OK); - muxer.AddEncodedAudioFrame(audioFrame); - muxer.AudioEndOfStream(); - nsTArray> buffers; - EXPECT_EQ(muxer.GetData(&buffers), NS_OK); -} - -TEST(MuxerTest, AudioVideo) -{ - MockContainerWriter* writer = new MockContainerWriter(); - Muxer muxer(WrapUnique(writer)); - - // Prepare data - - auto opusMeta = CreateOpusMetadata(1, 48000, 16, 16); - auto vp8Meta = CreateVP8Metadata(640, 480); - auto audioFrame = CreateFrame(EncodedFrame::OPUS_AUDIO_FRAME, 0, 48000, 4096); - auto videoFrame = CreateFrame(EncodedFrame::VP8_I_FRAME, 0, 50000, 65536); - - // Expectations - - EXPECT_CALL(*writer, SetMetadata(ElementsAre(opusMeta, vp8Meta))) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, WriteEncodedTrack(ElementsAre(videoFrame, audioFrame), - ContainerWriter::END_OF_STREAM)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, GetContainerData(_, ContainerWriter::GET_HEADER)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, GetContainerData(_, ContainerWriter::FLUSH_NEEDED)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, IsWritingComplete()).Times(0); - - // Test - - EXPECT_EQ(muxer.SetMetadata( - nsTArray>({opusMeta, vp8Meta})), - NS_OK); - muxer.AddEncodedAudioFrame(audioFrame); - muxer.AudioEndOfStream(); - muxer.AddEncodedVideoFrame(videoFrame); - muxer.VideoEndOfStream(); - nsTArray> buffers; - EXPECT_EQ(muxer.GetData(&buffers), NS_OK); -} - -TEST(MuxerTest, AudioVideoOutOfOrder) -{ - MockContainerWriter* writer = new MockContainerWriter(); - Muxer muxer(WrapUnique(writer)); - - // Prepare data - - auto opusMeta = CreateOpusMetadata(1, 48000, 16, 16); - auto vp8Meta = CreateVP8Metadata(640, 480); - auto a0 = CreateFrame(EncodedFrame::OPUS_AUDIO_FRAME, 0, 48, 4096); - auto v0 = CreateFrame(EncodedFrame::VP8_I_FRAME, 0, 50, 65536); - auto a48 = CreateFrame(EncodedFrame::OPUS_AUDIO_FRAME, 48, 48, 4096); - auto v50 = CreateFrame(EncodedFrame::VP8_I_FRAME, 50, 50, 65536); - - // Expectations - - EXPECT_CALL(*writer, SetMetadata(ElementsAre(opusMeta, vp8Meta))) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, WriteEncodedTrack(ElementsAre(v0, a0, a48, v50), - ContainerWriter::END_OF_STREAM)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, GetContainerData(_, ContainerWriter::GET_HEADER)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, GetContainerData(_, ContainerWriter::FLUSH_NEEDED)) - .WillOnce(Return(NS_OK)); - EXPECT_CALL(*writer, IsWritingComplete()).Times(0); - - // Test - - EXPECT_EQ(muxer.SetMetadata( - nsTArray>({opusMeta, vp8Meta})), - NS_OK); - muxer.AddEncodedAudioFrame(a0); - muxer.AddEncodedVideoFrame(v0); - muxer.AddEncodedVideoFrame(v50); - muxer.VideoEndOfStream(); - muxer.AddEncodedAudioFrame(a48); - muxer.AudioEndOfStream(); - nsTArray> buffers; - EXPECT_EQ(muxer.GetData(&buffers), NS_OK); -} diff --git a/dom/media/gtest/TestVideoTrackEncoder.cpp b/dom/media/gtest/TestVideoTrackEncoder.cpp index b3b41b3d9d3f..f04110a023aa 100644 --- a/dom/media/gtest/TestVideoTrackEncoder.cpp +++ b/dom/media/gtest/TestVideoTrackEncoder.cpp @@ -143,8 +143,8 @@ TEST(VP8VideoTrackEncoder, FrameEncode) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(images.Length())); // Pull Encoded Data back from encoder. - nsTArray> frames; - EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); } // Test that encoding a single frame gives useful output. @@ -165,20 +165,21 @@ TEST(VP8VideoTrackEncoder, SingleFrameEncode) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Read out encoded data, and verify. + const nsTArray>& frames = container.GetEncodedFrames(); const size_t oneElement = 1; ASSERT_EQ(oneElement, frames.Length()); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->mFrameType) + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->GetFrameType()) << "We only have one frame, so it should be a keyframe"; const uint64_t halfSecond = PR_USEC_PER_SEC / 2; - EXPECT_EQ(halfSecond, frames[0]->mDuration); + EXPECT_EQ(halfSecond, frames[0]->GetDuration()); } // Test that encoding a couple of identical images gives useful output. @@ -203,15 +204,15 @@ TEST(VP8VideoTrackEncoder, SameFrameEncode) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1.5)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify total duration being 1.5s. uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t oneAndAHalf = (PR_USEC_PER_SEC / 2) * 3; EXPECT_EQ(oneAndAHalf, totalDuration); @@ -239,15 +240,15 @@ TEST(VP8VideoTrackEncoder, SkippedFrames) encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(100)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify total duration being 100 * 1ms = 100ms. uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t hundredMillis = PR_USEC_PER_SEC / 10; EXPECT_EQ(hundredMillis, totalDuration); @@ -281,15 +282,15 @@ TEST(VP8VideoTrackEncoder, RoundingErrorFramesEncode) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify total duration being 1s. uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t oneSecond = PR_USEC_PER_SEC; EXPECT_EQ(oneSecond, totalDuration); @@ -318,8 +319,8 @@ TEST(VP8VideoTrackEncoder, TimestampFrameEncode) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.3)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); @@ -330,9 +331,9 @@ TEST(VP8VideoTrackEncoder, TimestampFrameEncode) (PR_USEC_PER_SEC / 10)}; uint64_t totalDuration = 0; size_t i = 0; - for (auto& frame : frames) { - EXPECT_EQ(expectedDurations[i++], frame->mDuration); - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + EXPECT_EQ(expectedDurations[i++], frame->GetDuration()); + totalDuration += frame->GetDuration(); } const uint64_t pointThree = (PR_USEC_PER_SEC / 10) * 3; EXPECT_EQ(pointThree, totalDuration); @@ -367,8 +368,8 @@ TEST(VP8VideoTrackEncoder, DriftingFrameEncode) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.3)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); @@ -379,9 +380,9 @@ TEST(VP8VideoTrackEncoder, DriftingFrameEncode) (PR_USEC_PER_SEC / 10) * 2}; uint64_t totalDuration = 0; size_t i = 0; - for (auto& frame : frames) { - EXPECT_EQ(expectedDurations[i++], frame->mDuration); - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + EXPECT_EQ(expectedDurations[i++], frame->GetDuration()); + totalDuration += frame->GetDuration(); } const uint64_t pointSix = (PR_USEC_PER_SEC / 10) * 6; EXPECT_EQ(pointSix, totalDuration); @@ -432,18 +433,18 @@ TEST(VP8VideoTrackEncoder, Suspended) encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify that we have two encoded frames and a total duration of 0.2s. const uint64_t two = 2; - EXPECT_EQ(two, frames.Length()); + EXPECT_EQ(two, container.GetEncodedFrames().Length()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t pointTwo = (PR_USEC_PER_SEC / 10) * 2; EXPECT_EQ(pointTwo, totalDuration); @@ -482,18 +483,18 @@ TEST(VP8VideoTrackEncoder, SuspendedUntilEnd) encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify that we have one encoded frames and a total duration of 0.1s. const uint64_t one = 1; - EXPECT_EQ(one, frames.Length()); + EXPECT_EQ(one, container.GetEncodedFrames().Length()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t pointOne = PR_USEC_PER_SEC / 10; EXPECT_EQ(pointOne, totalDuration); @@ -521,14 +522,14 @@ TEST(VP8VideoTrackEncoder, AlwaysSuspended) encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify that we have no encoded frames. const uint64_t none = 0; - EXPECT_EQ(none, frames.Length()); + EXPECT_EQ(none, container.GetEncodedFrames().Length()); } // Test that encoding a track that is suspended in the beginning works. @@ -565,18 +566,18 @@ TEST(VP8VideoTrackEncoder, SuspendedBeginning) encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify that we have one encoded frames and a total duration of 0.1s. const uint64_t one = 1; - EXPECT_EQ(one, frames.Length()); + EXPECT_EQ(one, container.GetEncodedFrames().Length()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t half = PR_USEC_PER_SEC / 2; EXPECT_EQ(half, totalDuration); @@ -618,18 +619,18 @@ TEST(VP8VideoTrackEncoder, SuspendedOverlap) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(2)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); // Verify that we have two encoded frames and a total duration of 0.1s. const uint64_t two = 2; - ASSERT_EQ(two, frames.Length()); + ASSERT_EQ(two, container.GetEncodedFrames().Length()); const uint64_t pointFive = (PR_USEC_PER_SEC / 10) * 5; - EXPECT_EQ(pointFive, frames[0]->mDuration); + EXPECT_EQ(pointFive, container.GetEncodedFrames()[0]->GetDuration()); const uint64_t pointSeven = (PR_USEC_PER_SEC / 10) * 7; - EXPECT_EQ(pointSeven, frames[1]->mDuration); + EXPECT_EQ(pointSeven, container.GetEncodedFrames()[1]->GetDuration()); } // Test that ending a track in the middle of already pushed data works. @@ -650,14 +651,14 @@ TEST(VP8VideoTrackEncoder, PrematureEnding) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t half = PR_USEC_PER_SEC / 2; EXPECT_EQ(half, totalDuration); @@ -682,14 +683,14 @@ TEST(VP8VideoTrackEncoder, DelayedStart) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t half = PR_USEC_PER_SEC / 2; EXPECT_EQ(half, totalDuration); @@ -715,14 +716,14 @@ TEST(VP8VideoTrackEncoder, DelayedStartOtherEventOrder) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t half = PR_USEC_PER_SEC / 2; EXPECT_EQ(half, totalDuration); @@ -747,14 +748,14 @@ TEST(VP8VideoTrackEncoder, VeryDelayedStart) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(10.5)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t half = PR_USEC_PER_SEC / 2; EXPECT_EQ(half, totalDuration); @@ -784,34 +785,34 @@ TEST(VP8VideoTrackEncoder, LongFramesReEncoded) { encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1.5)); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_FALSE(encoder.IsEncodingComplete()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t oneSec = PR_USEC_PER_SEC; EXPECT_EQ(oneSec, totalDuration); - EXPECT_EQ(1U, frames.Length()); + EXPECT_EQ(1U, container.GetEncodedFrames().Length()); } { encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(11)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); uint64_t totalDuration = 0; - for (auto& frame : frames) { - totalDuration += frame->mDuration; + for (auto& frame : container.GetEncodedFrames()) { + totalDuration += frame->GetDuration(); } const uint64_t tenSec = PR_USEC_PER_SEC * 10; EXPECT_EQ(tenSec, totalDuration); - EXPECT_EQ(10U, frames.Length()); + EXPECT_EQ(10U, container.GetEncodedFrames().Length()); } } @@ -852,36 +853,37 @@ TEST(VP8VideoTrackEncoder, ShortKeyFrameInterval) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1.2)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(6UL, frames.Length()); // [0, 400ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 400UL, frames[0]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 400UL, frames[0]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->GetFrameType()); // [400ms, 600ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[1]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[1]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[1]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[1]->GetFrameType()); // [600ms, 750ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[2]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[2]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[2]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[2]->GetFrameType()); // [750ms, 900ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[3]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[3]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[3]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[3]->GetFrameType()); // [900ms, 1100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[4]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[4]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[4]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[4]->GetFrameType()); // [1100ms, 1200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType()); } // Test that an encoding with a defined key frame interval encodes keyframes @@ -921,36 +923,37 @@ TEST(VP8VideoTrackEncoder, LongKeyFrameInterval) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(2.2)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(6UL, frames.Length()); // [0, 600ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 600UL, frames[0]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 600UL, frames[0]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->GetFrameType()); // [600ms, 900ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 300UL, frames[1]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[1]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 300UL, frames[1]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[1]->GetFrameType()); // [900ms, 1100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[2]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[2]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[2]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[2]->GetFrameType()); // [1100ms, 1900ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 800UL, frames[3]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[3]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 800UL, frames[3]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[3]->GetFrameType()); // [1900ms, 2100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[4]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[4]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[4]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[4]->GetFrameType()); // [2100ms, 2200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType()); } // Test that an encoding with no defined key frame interval encodes keyframes @@ -988,36 +991,37 @@ TEST(VP8VideoTrackEncoder, DefaultKeyFrameInterval) encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(2.2)); encoder.NotifyEndOfStream(); - nsTArray> frames; - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(6UL, frames.Length()); // [0, 600ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 600UL, frames[0]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 600UL, frames[0]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->GetFrameType()); // [600ms, 900ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 300UL, frames[1]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[1]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 300UL, frames[1]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[1]->GetFrameType()); // [900ms, 1100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[2]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[2]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[2]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[2]->GetFrameType()); // [1100ms, 1900ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 800UL, frames[3]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[3]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 800UL, frames[3]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[3]->GetFrameType()); // [1900ms, 2100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[4]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[4]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[4]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[4]->GetFrameType()); // [2100ms, 2200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType()); } // Test that an encoding where the key frame interval is updated dynamically @@ -1027,7 +1031,7 @@ TEST(VP8VideoTrackEncoder, DynamicKeyFrameIntervalChanges) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); // Set keyframe interval to 100ms. @@ -1076,7 +1080,7 @@ TEST(VP8VideoTrackEncoder, DynamicKeyFrameIntervalChanges) // Advancing 501ms, so the first bit of the frame starting at 500ms is // included. encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(501)); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); { VideoSegment segment; @@ -1102,7 +1106,7 @@ TEST(VP8VideoTrackEncoder, DynamicKeyFrameIntervalChanges) // Advancing 2000ms from 501ms to 2501ms encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(2501)); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); { VideoSegment segment; @@ -1126,67 +1130,68 @@ TEST(VP8VideoTrackEncoder, DynamicKeyFrameIntervalChanges) encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(14UL, frames.Length()); // [0, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[0]->GetFrameType()); // [100ms, 120ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 20UL, frames[1]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[1]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 20UL, frames[1]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[1]->GetFrameType()); // [120ms, 130ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 10UL, frames[2]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[2]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 10UL, frames[2]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[2]->GetFrameType()); // [130ms, 200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 70UL, frames[3]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[3]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 70UL, frames[3]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[3]->GetFrameType()); // [200ms, 300ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[4]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[4]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[4]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[4]->GetFrameType()); // [300ms, 500ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[5]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[5]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType()); // [500ms, 1300ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 800UL, frames[6]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[6]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 800UL, frames[6]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[6]->GetFrameType()); // [1300ms, 1400ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[7]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[7]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[7]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[7]->GetFrameType()); // [1400ms, 2400ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 1000UL, frames[8]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[8]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 1000UL, frames[8]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[8]->GetFrameType()); // [2400ms, 2500ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[9]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[9]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[9]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[9]->GetFrameType()); // [2500ms, 2600ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[10]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[10]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[10]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[10]->GetFrameType()); // [2600ms, 2800ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[11]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[11]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 200UL, frames[11]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[11]->GetFrameType()); // [2800ms, 2900ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[12]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[12]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[12]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[12]->GetFrameType()); // [2900ms, 3000ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[13]->mDuration); - EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[13]->mFrameType); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[13]->GetDuration()); + EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[13]->GetFrameType()); } // Test that an encoding which is disabled on a frame timestamp encodes @@ -1196,7 +1201,7 @@ TEST(VP8VideoTrackEncoder, DisableOnFrameTime) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); // Pass a frame in at t=0. @@ -1221,16 +1226,17 @@ TEST(VP8VideoTrackEncoder, DisableOnFrameTime) encoder.Disable(now + TimeDuration::FromMilliseconds(100)); encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(200)); encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(2UL, frames.Length()); // [0, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration()); // [100ms, 200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[1]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[1]->GetDuration()); } // Test that an encoding which is disabled between two frame timestamps encodes @@ -1240,7 +1246,7 @@ TEST(VP8VideoTrackEncoder, DisableBetweenFrames) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); // Pass a frame in at t=0. @@ -1262,19 +1268,20 @@ TEST(VP8VideoTrackEncoder, DisableBetweenFrames) encoder.Disable(now + TimeDuration::FromMilliseconds(50)); encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(200)); encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(3UL, frames.Length()); // [0, 50ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[0]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[0]->GetDuration()); // [50ms, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->GetDuration()); // [100ms, 200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->GetDuration()); } // Test that an encoding which is enabled on a frame timestamp encodes @@ -1284,7 +1291,7 @@ TEST(VP8VideoTrackEncoder, EnableOnFrameTime) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); // Disable the track at t=0. @@ -1311,16 +1318,17 @@ TEST(VP8VideoTrackEncoder, EnableOnFrameTime) encoder.Enable(now + TimeDuration::FromMilliseconds(100)); encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(200)); encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(2UL, frames.Length()); // [0, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration()); // [100ms, 200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[1]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[1]->GetDuration()); } // Test that an encoding which is enabled between two frame timestamps encodes @@ -1330,7 +1338,7 @@ TEST(VP8VideoTrackEncoder, EnableBetweenFrames) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); // Disable the track at t=0. @@ -1354,19 +1362,20 @@ TEST(VP8VideoTrackEncoder, EnableBetweenFrames) encoder.Enable(now + TimeDuration::FromMilliseconds(50)); encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(200)); encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(3UL, frames.Length()); // [0, 50ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[0]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[0]->GetDuration()); // [50ms, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->GetDuration()); // [100ms, 200ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->GetDuration()); } // Test that making time go backwards removes any future frames in the encoder. @@ -1375,7 +1384,7 @@ TEST(VP8VideoTrackEncoder, BackwardsTimeResets) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); encoder.SetStartOffset(now); @@ -1422,22 +1431,23 @@ TEST(VP8VideoTrackEncoder, BackwardsTimeResets) encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(300)); encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(4UL, frames.Length()); // [0, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration()); // [100ms, 150ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->GetDuration()); // [150ms, 250ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->GetDuration()); // [250ms, 300ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[3]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[3]->GetDuration()); } // Test that trying to encode a null image removes any future frames in the @@ -1447,7 +1457,7 @@ TEST(VP8VideoTrackEncoder, NullImageResets) TestVP8TrackEncoder encoder; YUVBufferGenerator generator; generator.Init(mozilla::gfx::IntSize(640, 480)); - nsTArray> frames; + EncodedFrameContainer container; TimeStamp now = TimeStamp::Now(); encoder.SetStartOffset(now); @@ -1494,19 +1504,20 @@ TEST(VP8VideoTrackEncoder, NullImageResets) encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(300)); encoder.NotifyEndOfStream(); - ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); + const nsTArray>& frames = container.GetEncodedFrames(); ASSERT_EQ(3UL, frames.Length()); // [0, 100ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration()); // [100ms, 250ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[1]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[1]->GetDuration()); // [250ms, 300ms) - EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[2]->mDuration); + EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[2]->GetDuration()); } // EOS test @@ -1520,8 +1531,8 @@ TEST(VP8VideoTrackEncoder, EncodeComplete) // Pull Encoded Data back from encoder. Since we have sent // EOS to encoder, encoder.GetEncodedTrack should return // NS_OK immidiately. - nsTArray> frames; - EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(frames))); + EncodedFrameContainer container; + EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container))); EXPECT_TRUE(encoder.IsEncodingComplete()); } diff --git a/dom/media/gtest/TestWebMWriter.cpp b/dom/media/gtest/TestWebMWriter.cpp index 800c8128e86a..114311022cd0 100644 --- a/dom/media/gtest/TestWebMWriter.cpp +++ b/dom/media/gtest/TestWebMWriter.cpp @@ -40,30 +40,28 @@ class WebMVP8TrackEncoder : public VP8TrackEncoder { } }; -static void GetOpusMetadata(int aChannels, int aSampleRate, - TrackRate aTrackRate, - nsTArray>& aMeta) { - WebMOpusTrackEncoder opusEncoder(aTrackRate); - EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels, aSampleRate)); - aMeta.AppendElement(opusEncoder.GetMetadata()); -} - -static void GetVP8Metadata(int32_t aWidth, int32_t aHeight, - int32_t aDisplayWidth, int32_t aDisplayHeight, - TrackRate aTrackRate, - nsTArray>& aMeta) { - WebMVP8TrackEncoder vp8Encoder; - EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth, - aDisplayHeight)); - aMeta.AppendElement(vp8Encoder.GetMetadata()); -} - const uint64_t FIXED_DURATION = 1000000; const uint32_t FIXED_FRAMESIZE = 500; class TestWebMWriter : public WebMWriter { public: - TestWebMWriter() : WebMWriter(), mTimestamp(0) {} + explicit TestWebMWriter(int aTrackTypes) + : WebMWriter(aTrackTypes), mTimestamp(0) {} + + void SetOpusMetadata(int aChannels, int aSampleRate, TrackRate aTrackRate) { + WebMOpusTrackEncoder opusEncoder(aTrackRate); + EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels, aSampleRate)); + RefPtr opusMeta = opusEncoder.GetMetadata(); + SetMetadata(opusMeta); + } + void SetVP8Metadata(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth, + int32_t aDisplayHeight, TrackRate aTrackRate) { + WebMVP8TrackEncoder vp8Encoder; + EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth, + aDisplayHeight)); + RefPtr vp8Meta = vp8Encoder.GetMetadata(); + SetMetadata(vp8Meta); + } // When we append an I-Frame into WebM muxer, the muxer will treat previous // data as "a cluster". @@ -71,22 +69,22 @@ class TestWebMWriter : public WebMWriter { // previous cluster so that we can retrieve data by |GetContainerData|. void AppendDummyFrame(EncodedFrame::FrameType aFrameType, uint64_t aDuration) { - nsTArray> encodedVideoData; + EncodedFrameContainer encodedVideoData; nsTArray frameData; RefPtr videoData = new EncodedFrame(); // Create dummy frame data. frameData.SetLength(FIXED_FRAMESIZE); - videoData->mFrameType = aFrameType; - videoData->mTime = mTimestamp; - videoData->mDuration = aDuration; + videoData->SetFrameType(aFrameType); + videoData->SetTimeStamp(mTimestamp); + videoData->SetDuration(aDuration); videoData->SwapInFrameData(frameData); - encodedVideoData.AppendElement(videoData); + encodedVideoData.AppendEncodedFrame(videoData); WriteEncodedTrack(encodedVideoData, 0); mTimestamp += aDuration; } bool HaveValidCluster() { - nsTArray> encodedBuf; + nsTArray > encodedBuf; GetContainerData(&encodedBuf, 0); return (encodedBuf.Length() > 0) ? true : false; } @@ -98,32 +96,35 @@ class TestWebMWriter : public WebMWriter { TEST(WebMWriter, Metadata) { - TestWebMWriter writer; + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); // The output should be empty since we didn't set any metadata in writer. - nsTArray> encodedBuf; + nsTArray > encodedBuf; writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); EXPECT_TRUE(encodedBuf.Length() == 0); writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); EXPECT_TRUE(encodedBuf.Length() == 0); - nsTArray> meta; - - // Get opus metadata. + // Set opus metadata. int channel = 1; int sampleRate = 44100; TrackRate aTrackRate = 90000; - GetOpusMetadata(channel, sampleRate, aTrackRate, meta); + writer.SetOpusMetadata(channel, sampleRate, aTrackRate); - // Get vp8 metadata + // No output data since we didn't set both audio/video + // metadata in writer. + writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); + EXPECT_TRUE(encodedBuf.Length() == 0); + writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); + EXPECT_TRUE(encodedBuf.Length() == 0); + + // Set vp8 metadata int32_t width = 640; int32_t height = 480; int32_t displayWidth = 640; int32_t displayHeight = 480; - GetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate, meta); - - // Set metadata - writer.SetMetadata(meta); + writer.SetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate); writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); EXPECT_TRUE(encodedBuf.Length() > 0); @@ -131,22 +132,21 @@ TEST(WebMWriter, Metadata) TEST(WebMWriter, Cluster) { - TestWebMWriter writer; - nsTArray> meta; - // Get opus metadata. + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + // Set opus metadata. int channel = 1; int sampleRate = 48000; TrackRate aTrackRate = 90000; - GetOpusMetadata(channel, sampleRate, aTrackRate, meta); - // Get vp8 metadata + writer.SetOpusMetadata(channel, sampleRate, aTrackRate); + // Set vp8 metadata int32_t width = 320; int32_t height = 240; int32_t displayWidth = 320; int32_t displayHeight = 240; - GetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate, meta); - writer.SetMetadata(meta); + writer.SetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate); - nsTArray> encodedBuf; + nsTArray > encodedBuf; writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER); EXPECT_TRUE(encodedBuf.Length() > 0); encodedBuf.Clear(); @@ -174,20 +174,19 @@ TEST(WebMWriter, Cluster) TEST(WebMWriter, FLUSH_NEEDED) { - TestWebMWriter writer; - nsTArray> meta; - // Get opus metadata. + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + // Set opus metadata. int channel = 2; int sampleRate = 44100; TrackRate aTrackRate = 100000; - GetOpusMetadata(channel, sampleRate, aTrackRate, meta); - // Get vp8 metadata + writer.SetOpusMetadata(channel, sampleRate, aTrackRate); + // Set vp8 metadata int32_t width = 176; int32_t height = 352; int32_t displayWidth = 176; int32_t displayHeight = 352; - GetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate, meta); - writer.SetMetadata(meta); + writer.SetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate); // write the first I-Frame. writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); @@ -200,7 +199,7 @@ TEST(WebMWriter, FLUSH_NEEDED) // retrieved EXPECT_FALSE(writer.HaveValidCluster()); - nsTArray> encodedBuf; + nsTArray > encodedBuf; // Have data because the flag ContainerWriter::FLUSH_NEEDED writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED); EXPECT_TRUE(encodedBuf.Length() > 0); @@ -295,20 +294,19 @@ static int64_t webm_tell(void* aUserData) { TEST(WebMWriter, bug970774_aspect_ratio) { - TestWebMWriter writer; - nsTArray> meta; - // Get opus metadata. + TestWebMWriter writer(ContainerWriter::CREATE_AUDIO_TRACK | + ContainerWriter::CREATE_VIDEO_TRACK); + // Set opus metadata. int channel = 1; int sampleRate = 44100; TrackRate aTrackRate = 90000; - GetOpusMetadata(channel, sampleRate, aTrackRate, meta); + writer.SetOpusMetadata(channel, sampleRate, aTrackRate); // Set vp8 metadata int32_t width = 640; int32_t height = 480; int32_t displayWidth = 1280; int32_t displayHeight = 960; - GetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate, meta); - writer.SetMetadata(meta); + writer.SetVP8Metadata(width, height, displayWidth, displayHeight, aTrackRate); // write the first I-Frame. writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); @@ -317,7 +315,7 @@ TEST(WebMWriter, bug970774_aspect_ratio) writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION); // Get the metadata and the first cluster. - nsTArray> encodedBuf; + nsTArray > encodedBuf; writer.GetContainerData(&encodedBuf, 0); // Flatten the encodedBuf. WebMioData ioData; diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build index 593579544cc9..8acffc696025 100644 --- a/dom/media/gtest/moz.build +++ b/dom/media/gtest/moz.build @@ -14,7 +14,6 @@ LOCAL_INCLUDES += [ ] UNIFIED_SOURCES += [ - 'AudioGenerator.cpp', 'MockMediaResource.cpp', 'TestAudioBuffers.cpp', 'TestAudioCallbackDriver.cpp', @@ -38,7 +37,6 @@ UNIFIED_SOURCES += [ 'TestMediaSpan.cpp', 'TestMP3Demuxer.cpp', 'TestMP4Demuxer.cpp', - 'TestMuxer.cpp', 'TestOpusParser.cpp', 'TestRust.cpp', 'TestTimeUnit.cpp', diff --git a/dom/media/ogg/OggCodecState.cpp b/dom/media/ogg/OggCodecState.cpp index cd95925b417f..52b3ab79879f 100644 --- a/dom/media/ogg/OggCodecState.cpp +++ b/dom/media/ogg/OggCodecState.cpp @@ -1675,6 +1675,4 @@ bool SkeletonState::DecodeHeader(OggPacketPtr aPacket) { return true; } -#undef LOG - } // namespace mozilla diff --git a/dom/media/ogg/OggDemuxer.cpp b/dom/media/ogg/OggDemuxer.cpp index 114ac067fda5..f1446750f515 100644 --- a/dom/media/ogg/OggDemuxer.cpp +++ b/dom/media/ogg/OggDemuxer.cpp @@ -1893,5 +1893,5 @@ nsresult OggDemuxer::SeekBisection(TrackInfo::TrackType aType, int64_t aTarget, } #undef OGG_DEBUG -#undef SEEK_LOG +#undef SEEK_DEBUG } // namespace mozilla diff --git a/dom/media/ogg/OggWriter.cpp b/dom/media/ogg/OggWriter.cpp index 35247fc6858c..0f421697eeb9 100644 --- a/dom/media/ogg/OggWriter.cpp +++ b/dom/media/ogg/OggWriter.cpp @@ -6,6 +6,7 @@ #include "prtime.h" #include "GeckoProfiler.h" +#undef LOG #define LOG(args, ...) namespace mozilla { @@ -45,20 +46,22 @@ nsresult OggWriter::Init() { return (rc == 0) ? NS_OK : NS_ERROR_NOT_INITIALIZED; } -nsresult OggWriter::WriteEncodedTrack( - const nsTArray>& aData, uint32_t aFlags) { +nsresult OggWriter::WriteEncodedTrack(const EncodedFrameContainer& aData, + uint32_t aFlags) { AUTO_PROFILER_LABEL("OggWriter::WriteEncodedTrack", OTHER); - uint32_t len = aData.Length(); + uint32_t len = aData.GetEncodedFrames().Length(); for (uint32_t i = 0; i < len; i++) { - if (aData[i]->mFrameType != EncodedFrame::OPUS_AUDIO_FRAME) { + if (aData.GetEncodedFrames()[i]->GetFrameType() != + EncodedFrame::OPUS_AUDIO_FRAME) { LOG("[OggWriter] wrong encoded data type!"); return NS_ERROR_FAILURE; } // only pass END_OF_STREAM on the last frame! nsresult rv = WriteEncodedData( - aData[i]->GetFrameData(), aData[i]->mDuration, + aData.GetEncodedFrames()[i]->GetFrameData(), + aData.GetEncodedFrames()[i]->GetDuration(), i < len - 1 ? (aFlags & ~ContainerWriter::END_OF_STREAM) : aFlags); if (NS_FAILED(rv)) { LOG("%p Failed to WriteEncodedTrack!", this); @@ -108,7 +111,7 @@ nsresult OggWriter::WriteEncodedData(const nsTArray& aBuffer, return NS_OK; } -void OggWriter::ProduceOggPage(nsTArray>* aOutputBufs) { +void OggWriter::ProduceOggPage(nsTArray >* aOutputBufs) { aOutputBufs->AppendElement(); aOutputBufs->LastElement().SetLength(mOggPage.header_len + mOggPage.body_len); memcpy(aOutputBufs->LastElement().Elements(), mOggPage.header, @@ -117,7 +120,7 @@ void OggWriter::ProduceOggPage(nsTArray>* aOutputBufs) { mOggPage.body, mOggPage.body_len); } -nsresult OggWriter::GetContainerData(nsTArray>* aOutputBufs, +nsresult OggWriter::GetContainerData(nsTArray >* aOutputBufs, uint32_t aFlags) { int rc = -1; AUTO_PROFILER_LABEL("OggWriter::GetContainerData", OTHER); @@ -141,13 +144,12 @@ nsresult OggWriter::GetContainerData(nsTArray>* aOutputBufs, rc = ogg_stream_flush(&mOggStreamState, &mOggPage); NS_ENSURE_TRUE(rc > 0, NS_ERROR_FAILURE); + ProduceOggPage(aOutputBufs); + return NS_OK; + // Force generate a page even if the amount of packet data is not enough. // Usually do so after a header packet. - - ProduceOggPage(aOutputBufs); - } - - if (aFlags & ContainerWriter::FLUSH_NEEDED) { + } else if (aFlags & ContainerWriter::FLUSH_NEEDED) { // rc = 0 means no packet to put into a page, or an internal error. rc = ogg_stream_flush(&mOggStreamState, &mOggPage); } else { @@ -162,25 +164,20 @@ nsresult OggWriter::GetContainerData(nsTArray>* aOutputBufs, if (aFlags & ContainerWriter::FLUSH_NEEDED) { mIsWritingComplete = true; } - // We always return NS_OK here since it's OK to call this without having - // enough data to fill a page. It's the more common case compared to internal - // errors, and we cannot distinguish the two. - return NS_OK; + return (rc > 0) ? NS_OK : NS_ERROR_FAILURE; } -nsresult OggWriter::SetMetadata( - const nsTArray>& aMetadata) { - MOZ_ASSERT(aMetadata.Length() == 1); - MOZ_ASSERT(aMetadata[0]); +nsresult OggWriter::SetMetadata(TrackMetadataBase* aMetadata) { + MOZ_ASSERT(aMetadata); AUTO_PROFILER_LABEL("OggWriter::SetMetadata", OTHER); - if (aMetadata[0]->GetKind() != TrackMetadataBase::METADATA_OPUS) { + if (aMetadata->GetKind() != TrackMetadataBase::METADATA_OPUS) { LOG("wrong meta data type!"); return NS_ERROR_FAILURE; } // Validate each field of METADATA - mMetadata = static_cast(aMetadata[0].get()); + mMetadata = static_cast(aMetadata); if (mMetadata->mIdHeader.Length() == 0) { LOG("miss mIdHeader!"); return NS_ERROR_FAILURE; @@ -194,5 +191,3 @@ nsresult OggWriter::SetMetadata( } } // namespace mozilla - -#undef LOG diff --git a/dom/media/ogg/OggWriter.h b/dom/media/ogg/OggWriter.h index 73d5bd87e996..c4cf1f1787a4 100644 --- a/dom/media/ogg/OggWriter.h +++ b/dom/media/ogg/OggWriter.h @@ -23,17 +23,14 @@ class OggWriter : public ContainerWriter { OggWriter(); ~OggWriter(); - // Write frames into the ogg container. aFlags should be set to END_OF_STREAM - // for the final set of frames. - nsresult WriteEncodedTrack(const nsTArray>& aData, + nsresult WriteEncodedTrack(const EncodedFrameContainer& aData, uint32_t aFlags = 0) override; - nsresult GetContainerData(nsTArray>* aOutputBufs, + nsresult GetContainerData(nsTArray >* aOutputBufs, uint32_t aFlags = 0) override; // Check metadata type integrity and reject unacceptable track encoder. - nsresult SetMetadata( - const nsTArray>& aMetadata) override; + nsresult SetMetadata(TrackMetadataBase* aMetadata) override; private: nsresult Init(); @@ -41,7 +38,7 @@ class OggWriter : public ContainerWriter { nsresult WriteEncodedData(const nsTArray& aBuffer, int aDuration, uint32_t aFlags = 0); - void ProduceOggPage(nsTArray>* aOutputBufs); + void ProduceOggPage(nsTArray >* aOutputBufs); // Store the Medatata from track encoder RefPtr mMetadata; diff --git a/dom/media/ogg/OpusParser.cpp b/dom/media/ogg/OpusParser.cpp index 918888ea8c8a..c0436c4c98c4 100644 --- a/dom/media/ogg/OpusParser.cpp +++ b/dom/media/ogg/OpusParser.cpp @@ -212,6 +212,4 @@ bool OpusParser::IsValidMapping2ChannelsCount(uint8_t aChannels) { return val == valInt || valInt * valInt + 2 == aChannels; } -#undef OPUS_LOG - } // namespace mozilla diff --git a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html index e25d18d93360..d26da7797019 100644 --- a/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html +++ b/dom/media/test/test_mediarecorder_record_4ch_audiocontext.html @@ -56,8 +56,7 @@ function startTest() { } totalBlobSize += e.data.size; ok(totalBlobSize > 0, 'check the totalBlobSize'); - is(e.data.type, expectedMimeType, 'blob should have expected mimetype'); - is(mMediaRecorder.mimeType, expectedMimeType, 'recorder should have expected mimetype'); + is(mMediaRecorder.mimeType, expectedMimeType, 'blob should has mimetype, return ' + mMediaRecorder.mimeType); if (!stopTriggered) { mMediaRecorder.stop(); stopTriggered = true; diff --git a/dom/media/test/test_mediarecorder_record_audionode.html b/dom/media/test/test_mediarecorder_record_audionode.html index 32c8a37a461d..731675b822a8 100644 --- a/dom/media/test/test_mediarecorder_record_audionode.html +++ b/dom/media/test/test_mediarecorder_record_audionode.html @@ -65,9 +65,7 @@ async function testRecord(source, mimeType) { const chunks = []; let {data} = await new Promise(r => recorder.ondataavailable = r); - if (!isOffline) { - is(recorder.state, "recording", "Expected to still be recording"); - } + is(recorder.state, "recording", "Expected to still be recording"); is(data.type, recorder.mimeType, "Blob has recorder mimetype"); if (mimeType != "") { is(data.type, mimeType, "Blob has given mimetype"); diff --git a/dom/media/test/test_mediarecorder_record_getdata_afterstart.html b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html index 56540f2af3da..5b7b9cabe98a 100644 --- a/dom/media/test/test_mediarecorder_record_getdata_afterstart.html +++ b/dom/media/test/test_mediarecorder_record_getdata_afterstart.html @@ -38,13 +38,13 @@ function startTest(test, token) { info('onstart fired successfully'); hasonstart = true; // On audio only case, we produce audio/ogg as mimeType. - is('audio/ogg', mMediaRecorder.mimeType, "MediaRecorder mimetype as expected"); + is('audio/ogg', mMediaRecorder.mimeType, "check the record mimetype return " + mMediaRecorder.mimeType); mMediaRecorder.requestData(); }; mMediaRecorder.onstop = function() { info('onstop fired successfully'); - ok(hasondataavailable, "should have ondataavailable before onstop"); + ok (hasondataavailable, "should have ondataavailable before onstop"); is(mMediaRecorder.state, 'inactive', 'check recording status is inactive'); SimpleTest.finish(); }; @@ -53,9 +53,8 @@ function startTest(test, token) { info('ondataavailable fired successfully'); if (mMediaRecorder.state == 'recording') { hasondataavailable = true; - ok(hasonstart, "should have had start event first"); - is(e.data.type, mMediaRecorder.mimeType, - "blob's mimeType matches the recorder's"); + ok(hasonstart, "should has onstart event first"); + ok(e.data.size > 0, 'check blob has data'); mMediaRecorder.stop(); } }; diff --git a/dom/media/webm/EbmlComposer.cpp b/dom/media/webm/EbmlComposer.cpp index 3fadaf7833f3..cf8dc3051ef2 100644 --- a/dom/media/webm/EbmlComposer.cpp +++ b/dom/media/webm/EbmlComposer.cpp @@ -55,15 +55,14 @@ void EbmlComposer::GenerateHeader() { if (mCodecPrivateData.Length() > 0) { // Extract the pre-skip from mCodecPrivateData // then convert it to nanoseconds. - // For more details see - // https://tools.ietf.org/html/rfc7845#section-4.2 - uint64_t codecDelay = (uint64_t)LittleEndian::readUint16( - mCodecPrivateData.Elements() + 10) * - PR_NSEC_PER_SEC / 48000; + // Details in OpusTrackEncoder.cpp. + mCodecDelay = (uint64_t)LittleEndian::readUint16( + mCodecPrivateData.Elements() + 10) * + PR_NSEC_PER_SEC / 48000; // Fixed 80ms, convert into nanoseconds. uint64_t seekPreRoll = 80 * PR_NSEC_PER_MSEC; writeAudioTrack(&ebml, 0x2, 0x0, "A_OPUS", mSampleFreq, mChannels, - codecDelay, seekPreRoll, + mCodecDelay, seekPreRoll, mCodecPrivateData.Elements(), mCodecPrivateData.Length()); } @@ -115,7 +114,7 @@ void EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame) { EbmlGlobal ebml; ebml.offset = 0; - auto frameType = aFrame->mFrameType; + auto frameType = aFrame->GetFrameType(); const bool isVP8IFrame = (frameType == EncodedFrame::FrameType::VP8_I_FRAME); const bool isVP8PFrame = (frameType == EncodedFrame::FrameType::VP8_P_FRAME); const bool isOpus = (frameType == EncodedFrame::FrameType::OPUS_AUDIO_FRAME); @@ -129,7 +128,11 @@ void EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame) { return; } - int64_t timeCode = aFrame->mTime / ((int)PR_USEC_PER_MSEC) - mClusterTimecode; + int64_t timeCode = + aFrame->GetTimeStamp() / ((int)PR_USEC_PER_MSEC) - mClusterTimecode; + if (isOpus) { + timeCode += mCodecDelay / PR_NSEC_PER_MSEC; + } if (!mHasVideo && timeCode >= FLUSH_AUDIO_ONLY_AFTER_MS) { MOZ_ASSERT(mHasAudio); @@ -154,11 +157,15 @@ void EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame) { mClusterHeaderIndex = mClusters.Length() - 1; mClusterLengthLoc = ebmlLoc.offset; // if timeCode didn't under/overflow before, it shouldn't after this - mClusterTimecode = aFrame->mTime / PR_USEC_PER_MSEC; + mClusterTimecode = aFrame->GetTimeStamp() / PR_USEC_PER_MSEC; Ebml_SerializeUnsigned(&ebml, Timecode, mClusterTimecode); // Can't under-/overflow now - timeCode = aFrame->mTime / ((int)PR_USEC_PER_MSEC) - mClusterTimecode; + timeCode = + aFrame->GetTimeStamp() / ((int)PR_USEC_PER_MSEC) - mClusterTimecode; + if (isOpus) { + timeCode += mCodecDelay / PR_NSEC_PER_MSEC; + } mWritingCluster = true; } diff --git a/dom/media/webm/EbmlComposer.h b/dom/media/webm/EbmlComposer.h index 94ad18be0feb..4aa61b87652d 100644 --- a/dom/media/webm/EbmlComposer.h +++ b/dom/media/webm/EbmlComposer.h @@ -38,8 +38,7 @@ class EbmlComposer { /* * Insert media encoded buffer into muxer and it would be package * into SimpleBlock. If no cluster is opened, new cluster will start for - * writing. Frames passed to this function should already have any codec delay - * applied. + * writing. */ void WriteSimpleBlock(EncodedFrame* aFrame); /* @@ -69,6 +68,8 @@ class EbmlComposer { uint64_t mClusterLengthLoc = 0; // Audio codec specific header data. nsTArray mCodecPrivateData; + // Codec delay in nanoseconds. + uint64_t mCodecDelay = 0; // The timecode of the cluster. uint64_t mClusterTimecode = 0; diff --git a/dom/media/webm/WebMDemuxer.cpp b/dom/media/webm/WebMDemuxer.cpp index 18336654256f..dffe06416121 100644 --- a/dom/media/webm/WebMDemuxer.cpp +++ b/dom/media/webm/WebMDemuxer.cpp @@ -1259,6 +1259,6 @@ int64_t WebMTrackDemuxer::GetEvictionOffset(const TimeUnit& aTime) { return offset; } -} // namespace mozilla #undef WEBM_DEBUG +} // namespace mozilla diff --git a/dom/media/webm/WebMWriter.cpp b/dom/media/webm/WebMWriter.cpp index 3f71a9544d34..6bb26cfbba0e 100644 --- a/dom/media/webm/WebMWriter.cpp +++ b/dom/media/webm/WebMWriter.cpp @@ -10,7 +10,8 @@ namespace mozilla { -WebMWriter::WebMWriter() : ContainerWriter() { +WebMWriter::WebMWriter(uint32_t aTrackTypes) : ContainerWriter() { + mMetadataRequiredFlag = aTrackTypes; mEbmlComposer = new EbmlComposer(); } @@ -18,16 +19,17 @@ WebMWriter::~WebMWriter() { // Out-of-line dtor so mEbmlComposer nsAutoPtr can delete a complete type. } -nsresult WebMWriter::WriteEncodedTrack( - const nsTArray>& aData, uint32_t aFlags) { +nsresult WebMWriter::WriteEncodedTrack(const EncodedFrameContainer& aData, + uint32_t aFlags) { AUTO_PROFILER_LABEL("WebMWriter::WriteEncodedTrack", OTHER); - for (uint32_t i = 0; i < aData.Length(); i++) { - mEbmlComposer->WriteSimpleBlock(aData.ElementAt(i).get()); + for (uint32_t i = 0; i < aData.GetEncodedFrames().Length(); i++) { + mEbmlComposer->WriteSimpleBlock( + aData.GetEncodedFrames().ElementAt(i).get()); } return NS_OK; } -nsresult WebMWriter::GetContainerData(nsTArray>* aOutputBufs, +nsresult WebMWriter::GetContainerData(nsTArray >* aOutputBufs, uint32_t aFlags) { AUTO_PROFILER_LABEL("WebMWriter::GetContainerData", OTHER); mEbmlComposer->ExtractBuffer(aOutputBufs, aFlags); @@ -37,75 +39,40 @@ nsresult WebMWriter::GetContainerData(nsTArray>* aOutputBufs, return NS_OK; } -nsresult WebMWriter::SetMetadata( - const nsTArray>& aMetadata) { +nsresult WebMWriter::SetMetadata(TrackMetadataBase* aMetadata) { + MOZ_ASSERT(aMetadata); AUTO_PROFILER_LABEL("WebMWriter::SetMetadata", OTHER); - MOZ_DIAGNOSTIC_ASSERT(!aMetadata.IsEmpty()); - // Integrity checks - bool bad = false; - for (const RefPtr& metadata : aMetadata) { - MOZ_ASSERT(metadata); - - if (metadata->GetKind() == TrackMetadataBase::METADATA_VP8) { - VP8Metadata* meta = static_cast(metadata.get()); - if (meta->mWidth == 0 || meta->mHeight == 0 || meta->mDisplayWidth == 0 || - meta->mDisplayHeight == 0) { - bad = true; - } - } - - if (metadata->GetKind() == TrackMetadataBase::METADATA_VORBIS) { - VorbisMetadata* meta = static_cast(metadata.get()); - if (meta->mSamplingFrequency == 0 || meta->mChannels == 0 || - meta->mData.IsEmpty()) { - bad = true; - } - } - - if (metadata->GetKind() == TrackMetadataBase::METADATA_OPUS) { - OpusMetadata* meta = static_cast(metadata.get()); - if (meta->mSamplingFrequency == 0 || meta->mChannels == 0 || - meta->mIdHeader.IsEmpty()) { - bad = true; - } - } - } - if (bad) { - return NS_ERROR_FAILURE; + if (aMetadata->GetKind() == TrackMetadataBase::METADATA_VP8) { + VP8Metadata* meta = static_cast(aMetadata); + MOZ_ASSERT(meta, "Cannot find vp8 encoder metadata"); + mEbmlComposer->SetVideoConfig(meta->mWidth, meta->mHeight, + meta->mDisplayWidth, meta->mDisplayHeight); + mMetadataRequiredFlag = + mMetadataRequiredFlag & ~ContainerWriter::CREATE_VIDEO_TRACK; } - // Storing - DebugOnly hasAudio = false; - DebugOnly hasVideo = false; - for (const RefPtr& metadata : aMetadata) { - MOZ_ASSERT(metadata); - - if (metadata->GetKind() == TrackMetadataBase::METADATA_VP8) { - MOZ_ASSERT(!hasVideo); - VP8Metadata* meta = static_cast(metadata.get()); - mEbmlComposer->SetVideoConfig(meta->mWidth, meta->mHeight, - meta->mDisplayWidth, meta->mDisplayHeight); - hasVideo = true; - } - - if (metadata->GetKind() == TrackMetadataBase::METADATA_VORBIS) { - MOZ_ASSERT(!hasAudio); - VorbisMetadata* meta = static_cast(metadata.get()); - mEbmlComposer->SetAudioConfig(meta->mSamplingFrequency, meta->mChannels); - mEbmlComposer->SetAudioCodecPrivateData(meta->mData); - hasAudio = true; - } - - if (metadata->GetKind() == TrackMetadataBase::METADATA_OPUS) { - MOZ_ASSERT(!hasAudio); - OpusMetadata* meta = static_cast(metadata.get()); - mEbmlComposer->SetAudioConfig(meta->mSamplingFrequency, meta->mChannels); - mEbmlComposer->SetAudioCodecPrivateData(meta->mIdHeader); - hasAudio = true; - } + if (aMetadata->GetKind() == TrackMetadataBase::METADATA_VORBIS) { + VorbisMetadata* meta = static_cast(aMetadata); + MOZ_ASSERT(meta, "Cannot find vorbis encoder metadata"); + mEbmlComposer->SetAudioConfig(meta->mSamplingFrequency, meta->mChannels); + mEbmlComposer->SetAudioCodecPrivateData(meta->mData); + mMetadataRequiredFlag = + mMetadataRequiredFlag & ~ContainerWriter::CREATE_AUDIO_TRACK; + } + + if (aMetadata->GetKind() == TrackMetadataBase::METADATA_OPUS) { + OpusMetadata* meta = static_cast(aMetadata); + MOZ_ASSERT(meta, "Cannot find Opus encoder metadata"); + mEbmlComposer->SetAudioConfig(meta->mSamplingFrequency, meta->mChannels); + mEbmlComposer->SetAudioCodecPrivateData(meta->mIdHeader); + mMetadataRequiredFlag = + mMetadataRequiredFlag & ~ContainerWriter::CREATE_AUDIO_TRACK; + } + + if (!mMetadataRequiredFlag) { + mEbmlComposer->GenerateHeader(); } - mEbmlComposer->GenerateHeader(); return NS_OK; } diff --git a/dom/media/webm/WebMWriter.h b/dom/media/webm/WebMWriter.h index 0af81d3d25b3..9e415cc4b943 100644 --- a/dom/media/webm/WebMWriter.h +++ b/dom/media/webm/WebMWriter.h @@ -41,28 +41,30 @@ class VP8Metadata : public TrackMetadataBase { */ class WebMWriter : public ContainerWriter { public: - // Run in MediaRecorder thread - WebMWriter(); + // aTrackTypes indicate this muxer should multiplex into Video only or A/V + // foramt. Run in MediaRecorder thread + explicit WebMWriter(uint32_t aTrackTypes); virtual ~WebMWriter(); - // WriteEncodedTrack inserts raw packets into WebM stream. Does not accept - // any flags: any specified will be ignored. Writing is finalized via - // flushing via GetContainerData(). - nsresult WriteEncodedTrack(const nsTArray>& aData, + // WriteEncodedTrack inserts raw packets into WebM stream. + nsresult WriteEncodedTrack(const EncodedFrameContainer& aData, uint32_t aFlags = 0) override; // GetContainerData outputs multiplexing data. // aFlags indicates the muxer should enter into finished stage and flush out // queue data. - nsresult GetContainerData(nsTArray>* aOutputBufs, + nsresult GetContainerData(nsTArray >* aOutputBufs, uint32_t aFlags = 0) override; // Assign metadata into muxer - nsresult SetMetadata( - const nsTArray>& aMetadata) override; + nsresult SetMetadata(TrackMetadataBase* aMetadata) override; private: nsAutoPtr mEbmlComposer; + + // Indicate what kind of meta data needed in the writer. + // If this value become 0, it means writer can start to generate header. + uint8_t mMetadataRequiredFlag; }; } // namespace mozilla diff --git a/layout/reftests/css-ui-invalid/select/reftest.list b/layout/reftests/css-ui-invalid/select/reftest.list index 9579940b72d4..13ef134be851 100644 --- a/layout/reftests/css-ui-invalid/select/reftest.list +++ b/layout/reftests/css-ui-invalid/select/reftest.list @@ -12,7 +12,7 @@ fuzzy-if(skiaContent,0-2,0-5) needs-focus == select-required-valid.html select-r needs-focus == select-required-multiple-invalid.html select-required-multiple-ref.html fuzzy-if(asyncPan&&!layersGPUAccelerated,0-84,0-77) fuzzy-if(skiaContent,0-1,0-1000) needs-focus == select-required-multiple-invalid-changed.html select-required-multiple-ref.html needs-focus == select-required-multiple-valid.html select-required-multiple-ref.html -fuzzy-if(skiaContent&&!Android,0-2,0-10) needs-focus == select-disabled-fieldset-1.html select-fieldset-ref.html +fuzzy-if(skiaContent&&!Android,0-2,0-10) fuzzy-if(Android,0-9,0-1) needs-focus == select-disabled-fieldset-1.html select-fieldset-ref.html fuzzy-if(skiaContent&&!Android,0-2,0-10) needs-focus == select-disabled-fieldset-2.html select-fieldset-ref.html fuzzy-if(skiaContent,0-2,0-10) needs-focus == select-fieldset-legend.html select-fieldset-legend-ref.html fuzzy-if(skiaContent,0-1,0-5) needs-focus == select-novalidate.html select-required-ref.html diff --git a/toolkit/components/certviewer/content/certviewer.html b/toolkit/components/certviewer/content/certviewer.html index fa6edb7f270f..cc22a343fd54 100644 --- a/toolkit/components/certviewer/content/certviewer.html +++ b/toolkit/components/certviewer/content/certviewer.html @@ -19,6 +19,7 @@ + diff --git a/toolkit/components/certviewer/content/certviewer.js b/toolkit/components/certviewer/content/certviewer.js index 3cfaad0fdc2d..f5995f6cef36 100644 --- a/toolkit/components/certviewer/content/certviewer.js +++ b/toolkit/components/certviewer/content/certviewer.js @@ -8,8 +8,10 @@ import { parse } from "./certDecoder.js"; import { pemToDER } from "./utils.js"; +let gElements = {}; document.addEventListener("DOMContentLoaded", async e => { + gElements.certificateSection = document.querySelector("certificate-section"); let url = new URL(document.URL); let certInfo = url.searchParams.getAll("cert"); if (certInfo.length === 0) { @@ -23,12 +25,11 @@ document.addEventListener("DOMContentLoaded", async e => { export const updateSelectedItem = (() => { let state; return selectedItem => { - let certificateSection = document.querySelector("certificate-section"); if (selectedItem) { if (state !== selectedItem) { state = selectedItem; - certificateSection.updateCertificateSource(selectedItem); - certificateSection.updateSelectedTab(selectedItem); + gElements.certificateSection.updateCertificateSource(selectedItem); + gElements.certificateSection.updateSelectedTab(selectedItem); } } return state; @@ -222,16 +223,17 @@ const adjustCertInformation = cert => { Critical: cert.ext.scts.critical || false, }); - return { - certItems, + certItems.push({ tabName: cert.subject.cn, - }; + }); + + return certItems; }; -const render = async (certs, error) => { +const render = async error => { await customElements.whenDefined("certificate-section"); const CertificateSection = customElements.get("certificate-section"); - document.querySelector("body").append(new CertificateSection(certs, error)); + document.querySelector("body").append(new CertificateSection(error)); return Promise.resolve(); }; @@ -258,9 +260,10 @@ const buildChain = async chain => { return Promise.reject(); } let adjustedCerts = certs.map(cert => adjustCertInformation(cert)); - return render(adjustedCerts, false); + console.log(adjustedCerts); + return render(false); }) .catch(err => { - render(null, true); + render(true); }); }; diff --git a/toolkit/components/certviewer/content/components/certificate-section.js b/toolkit/components/certviewer/content/components/certificate-section.js index d74f1293c799..46d762735853 100644 --- a/toolkit/components/certviewer/content/components/certificate-section.js +++ b/toolkit/components/certviewer/content/components/certificate-section.js @@ -3,13 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { updateSelectedItem } from "../certviewer.js"; +import { certArray } from "./dummy-info.js"; import { InfoGroup } from "./info-group.js"; import { ErrorSection } from "./error-section.js"; class CertificateSection extends HTMLElement { - constructor(certs, error) { + constructor(error) { super(); - this.certs = certs; this.error = error; } @@ -42,9 +42,29 @@ class CertificateSection extends HTMLElement { certificateTabs.appendChild(new ErrorSection()); return; } - for (let i = 0; i < this.certs.length; i++) { - this.createInfoGroupsContainers(this.certs[i].certItems, i); - this.createTabSection(this.certs[i].tabName, i, certificateTabs); + + this.createInfoGroupsContainers(); + for (let i = 0; i < certArray.length; i++) { + let tab = document.createElement("button"); + tab.textContent = "tab" + i; + tab.setAttribute("id", "tab" + i); + tab.setAttribute("aria-controls", "panel" + i); + tab.setAttribute("idnumber", i); + tab.setAttribute("role", "tab"); + tab.classList.add("certificate-tab"); + tab.classList.add("tab"); + certificateTabs.appendChild(tab); + + // If it is the first tab, allow it to be tabbable by the user. + // If it isn't the first tab, do not allow tab functionality, + // as arrow functionality is implemented in certviewer.js. + if (i === 0) { + tab.classList.add("selected"); + tab.setAttribute("tabindex", 0); + } else { + tab.setAttribute("tabindex", -1); + } + this.infoGroupsContainers[0].classList.add("selected"); } this.setAccessibilityEventListeners(); } @@ -92,43 +112,23 @@ class CertificateSection extends HTMLElement { }); } - createInfoGroupsContainers(certArray, i) { - this.infoGroupsContainers[i] = document.createElement("div"); - this.infoGroupsContainers[i].setAttribute("id", "panel" + i); - this.infoGroupsContainers[i].setAttribute("role", "tabpanel"); - this.infoGroupsContainers[i].setAttribute("tabindex", 0); - this.infoGroupsContainers[i].setAttribute("aria-labelledby", "tab" + i); - if (i !== 0) { - this.infoGroupsContainers[i].setAttribute("hidden", true); + createInfoGroupsContainers() { + for (let i = 0; i < certArray.length; i++) { + this.infoGroupsContainers[i] = document.createElement("div"); + this.infoGroupsContainers[i].setAttribute("id", "panel" + i); + this.infoGroupsContainers[i].setAttribute("role", "tabpanel"); + this.infoGroupsContainers[i].setAttribute("tabindex", 0); + this.infoGroupsContainers[i].setAttribute("aria-labelledby", "tab" + i); + if (i !== 0) { + this.infoGroupsContainers[i].setAttribute("hidden", true); + } + this.infoGroupsContainers[i].classList.add("info-groups"); + this.shadowRoot.appendChild(this.infoGroupsContainers[i]); + let arrayItem = certArray[i]; + for (let j = 0; j < arrayItem.length; j++) { + this.infoGroupsContainers[i].appendChild(new InfoGroup(arrayItem[j])); + } } - this.infoGroupsContainers[i].classList.add("info-groups"); - this.shadowRoot.appendChild(this.infoGroupsContainers[i]); - for (let j = 0; j < certArray.length; j++) { - this.infoGroupsContainers[i].appendChild(new InfoGroup(certArray[j])); - } - } - - createTabSection(tabName, i, certificateTabs) { - let tab = document.createElement("button"); - tab.textContent = tabName; - tab.setAttribute("id", "tab" + i); - tab.setAttribute("aria-controls", "panel" + i); - tab.setAttribute("idnumber", i); - tab.setAttribute("role", "tab"); - tab.classList.add("certificate-tab"); - tab.classList.add("tab"); - certificateTabs.appendChild(tab); - - // If it is the first tab, allow it to be tabbable by the user. - // If it isn't the first tab, do not allow tab functionality, - // as arrow functionality is implemented in certviewer.js. - if (i === 0) { - tab.classList.add("selected"); - tab.setAttribute("tabindex", 0); - } else { - tab.setAttribute("tabindex", -1); - } - this.infoGroupsContainers[0].classList.add("selected"); } updateSelectedTab(index) { diff --git a/toolkit/components/certviewer/content/components/dummy-info.js b/toolkit/components/certviewer/content/components/dummy-info.js new file mode 100644 index 000000000000..bb5431a17f81 --- /dev/null +++ b/toolkit/components/certviewer/content/components/dummy-info.js @@ -0,0 +1,182 @@ +/* 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/. */ + +export const certArray = [ + [ + { + sectionTitle: "Subject Name", + sectionItems: [ + { + label: "Common Name", + info: "developer.mozilla.org", + }, + ], + }, + { + sectionTitle: "Issuer Name", + sectionItems: [ + { + label: "Country", + info: "US", + }, + { + label: "Organization", + info: "Amazon", + }, + { + label: "Organizational Unit", + info: "Server CA 1B", + }, + { + label: "Common Name", + info: "Amazon", + }, + ], + }, + { + sectionTitle: "Validity", + sectionItems: [ + { + label: "Not Before", + info: "5/14/2019, 9:00:00 PM (Atlantic Daylight Time)", + }, + { + label: "Not After", + info: "6/15/2020, 9:00:00 AM (Atlantic Daylight Time)", + }, + ], + }, + { + sectionTitle: "Subject Alt Names", + sectionItems: [ + { + label: "DNS Name", + info: "developer.mozilla.org", + }, + { + label: "DNS Name", + info: "beta.developer.mozilla.org", + }, + { + label: "DNS Name", + info: "developer-prod.mdn.mozit.cloud", + }, + { + label: "DNS Name", + info: "wiki.developer.mozilla.org", + }, + ], + }, + { + sectionTitle: "Public Key Info", + sectionItems: [ + { + label: "Algorithm", + info: "RSA", + }, + { + label: "Key Size", + info: "2048 bits", + }, + { + label: "Exponent", + info: "65537", + }, + { + label: "Modulus", + info: + "8B:FF:8A:9E:9E:2B:11:68:78:02:95:57:B6:84:F7:F3:32:46:BE:06:41:29:5B:AF:13:D7:93:28:4A:FC:8D:33:C9:07:BC:C5:CE:45:F5:60:42:A3:65:07:19:69:B8:67:97:9C:DB:B3:A7:67:D6:7A:57:BA:82:4E:63:83:33:B9:64:A1:56:1C:8A:EF:9F:7B:74:08:3F:D0:9B:E5:39:80:1C:C3:5D:4D:1B:4F:4A:23:BE:B5:BC:DD:18:5E:1D:CE:27:C8:7B:F7:5E:E6:9C:C3:E7:69:50:45:D1:BE:01:71:A3:61:19:6D:7F:B6:6E:4B:C0:E5:11:B0:0D:01:D3:5C:66:B1:1D:61:7D:BB:43:E4:40:63:D8:C5:82:18:6B:28:24:15:39:6A:82:4F:60:3F:66:6E:23:86:2A:84:E1:34:70:AE:06:2D:92:A7:84:80:AD:6F:6F:24:52:FA:7B:E8:C2:CD:E2:55:2E:AE:27:07:04:D4:B6:F1:EC:80:2D:D1:B2:E1:74:BE:ED:D4:04:8C:D8:06:44:CC:F9:6C:4E:64:68:35:38:48:59:F7:45:49:BF:34:EE:DD:55:C6:1A:EB:61:1F:4A:FA:30:3F:73:8B:36:A8:90:6E:CB:2E:58:8F:9C:78:0A:AE:4E:45:A0:30:61:5A:6A:F8:A3:32:92:E3", + }, + ], + }, + ], + [ + { + sectionTitle: "Subject Name", + sectionItems: [ + { + label: "Common Name", + info: "developer.mozilla.org 2", + }, + ], + }, + { + sectionTitle: "Issuer Name", + sectionItems: [ + { + label: "Country", + info: "US 2", + }, + { + label: "Organization", + info: "Amazon", + }, + { + label: "Organizational Unit", + info: "Server CA 1B", + }, + { + label: "Common Name", + info: "Amazon", + }, + ], + }, + { + sectionTitle: "Validity", + sectionItems: [ + { + label: "Not Before", + info: "5/14/2019, 9:00:00 PM (Atlantic Daylight Time) 2", + }, + { + label: "Not After", + info: "6/15/2020, 9:00:00 AM (Atlantic Daylight Time)", + }, + ], + }, + { + sectionTitle: "Subject Alt Names", + sectionItems: [ + { + label: "DNS Name", + info: "developer.mozilla.org 2", + }, + { + label: "DNS Name", + info: "beta.developer.mozilla.org", + }, + { + label: "DNS Name", + info: "developer-prod.mdn.mozit.cloud", + }, + { + label: "DNS Name", + info: "wiki.developer.mozilla.org", + }, + ], + }, + { + sectionTitle: "Public Key Info", + sectionItems: [ + { + label: "Algorithm", + info: "RSA 2", + }, + { + label: "Key Size", + info: "2048 bits", + }, + { + label: "Exponent", + info: "65537", + }, + { + label: "Modulus", + info: + "8B:FF:8A:9E:9E:2B:11:68:78:02:95:57:B6:84:F7:F3:32:46:BE:06:41:29:5B:AF:13:D7:93:28:4A:FC:8D:33:C9:07:BC:C5:CE:45:F5:60:42:A3:65:07:19:69:B8:67:97:9C:DB:B3:A7:67:D6:7A:57:BA:82:4E:63:83:33:B9:64:A1:56:1C:8A:EF:9F:7B:74:08:3F:D0:9B:E5:39:80:1C:C3:5D:4D:1B:4F:4A:23:BE:B5:BC:DD:18:5E:1D:CE:27:C8:7B:F7:5E:E6:9C:C3:E7:69:50:45:D1:BE:01:71:A3:61:19:6D:7F:B6:6E:4B:C0:E5:11:B0:0D:01:D3:5C:66:B1:1D:61:7D:BB:43:E4:40:63:D8:C5:82:18:6B:28:24:15:39:6A:82:4F:60:3F:66:6E:23:86:2A:84:E1:34:70:AE:06:2D:92:A7:84:80:AD:6F:6F:24:52:FA:7B:E8:C2:CD:E2:55:2E:AE:27:07:04:D4:B6:F1:EC:80:2D:D1:B2:E1:74:BE:ED:D4:04:8C:D8:06:44:CC:F9:6C:4E:64:68:35:38:48:59:F7:45:49:BF:34:EE:DD:55:C6:1A:EB:61:1F:4A:FA:30:3F:73:8B:36:A8:90:6E:CB:2E:58:8F:9C:78:0A:AE:4E:45:A0:30:61:5A:6A:F8:A3:32:92:E3", + }, + ], + }, + ], +]; diff --git a/toolkit/components/certviewer/jar.mn b/toolkit/components/certviewer/jar.mn index 2dca16268970..f36fd6cbad5d 100644 --- a/toolkit/components/certviewer/jar.mn +++ b/toolkit/components/certviewer/jar.mn @@ -10,6 +10,7 @@ toolkit.jar: content/global/certviewer/components/certificate-section.css (content/components/certificate-section.css) content/global/certviewer/components/error-section.js (content/components/error-section.js) content/global/certviewer/components/error-section.css (content/components/error-section.css) + content/global/certviewer/components/dummy-info.js (content/components/dummy-info.js) content/global/certviewer/components/info-group.js (content/components/info-group.js) content/global/certviewer/components/info-group.css (content/components/info-group.css) content/global/certviewer/components/info-item.js (content/components/info-item.js) diff --git a/toolkit/components/certviewer/tests/browser/adjustedCerts.js b/toolkit/components/certviewer/tests/browser/adjustedCerts.js deleted file mode 100644 index d1494efe16f5..000000000000 --- a/toolkit/components/certviewer/tests/browser/adjustedCerts.js +++ /dev/null @@ -1,245 +0,0 @@ -"use strict"; - -const adjustedCerts = { - certItems: [ - { - sectionTitle: "Subject Name", - sectionItems: [ - { label: "Business Category", info: "Private Organization" }, - { label: "Inc. Country", info: "US" }, - { label: "Inc. State / Province", info: "Delaware" }, - { label: "Serial Number", info: "5157550" }, - { label: "Country", info: "US" }, - { label: "State / Province", info: "California" }, - { label: "Locality", info: "San Francisco" }, - { label: "Organization", info: "GitHub, Inc." }, - { label: "Common Name", info: "github.com" }, - ], - Critical: false, - }, - { - sectionTitle: "Issuer Name", - sectionItems: [ - { label: "Country", info: "US" }, - { label: "Organization", info: "DigiCert Inc" }, - { label: "Organizational Unit", info: "www.digicert.com" }, - { - label: "Common Name", - info: "DigiCert SHA2 Extended Validation Server CA", - }, - ], - Critical: false, - }, - { - sectionTitle: "Validity", - sectionItems: [ - { - label: "Not Before", - info: "5/7/2018, 9:00:00 PM (Brasilia Standard Time)", - }, - { label: "Not Before UTC", info: "Tue, 08 May 2018 00:00:00 GMT" }, - { - label: "Not After", - info: "6/3/2020, 9:00:00 AM (Brasilia Standard Time)", - }, - { label: "Not After UTC", info: "Wed, 03 Jun 2020 12:00:00 GMT" }, - ], - Critical: false, - }, - { - sectionTitle: "Subject Alt Names", - sectionItems: [ - { label: "DNS Name", info: "github.com" }, - { label: "DNS Name", info: "www.github.com" }, - ], - Critical: false, - }, - { - sectionTitle: "Public Key Info", - sectionItems: [ - { label: "Algorithm", info: "RSA" }, - { label: "Key size", info: 2048 }, - { label: "Curve" }, - { label: "Public Value" }, - { label: "Exponent", info: 65537 }, - { - label: "Modulus", - info: - "C6:3C:AA:F2:3C:97:0C:3A:C1:4F:28:AD:72:70:7D:D3:CE:B9:B5:60:73:A4:74:9B:8A:77:46:FD:7A:98:42:4C:C5:30:19:57:9A:A9:33:0B:E1:5D:4D:10:58:CA:77:99:C3:93:F3:F9:75:90:BC:BF:BB:E0:95:BA:2E:C5:8D:73:61:05:D3:10:84:A8:B3:89:B8:2F:73:8C:F0:2A:6E:BE:EE:AE:83:4B:82:11:B1:61:FD:77:61:DA:9B:1B:9A:23:FF:8C:7E:A2:01:06:DD:D1:7F:53:96:08:C1:5A:FA:E7:C0:CA:C8:44:8C:57:A7:A8:61:5F:66:0D:57:D3:B8:96:AC:B6:4A:9C:C1:EA:E8:FB:96:40:29:F6:15:30:B5:04:B0:CC:05:B6:84:C3:24:59:95:7F:A2:65:90:E5:B0:B3:1A:75:59:C4:3F:31:14:0A:D5:CC:AA:3A:85:05:52:06:32:96:07:61:DF:27:82:0C:F7:85:DB:60:31:F0:09:50:C5:B7:1A:23:E1:B0:7D:02:F5:14:1E:C9:CB:E8:7E:2A:33:04:F6:51:3F:52:98:15:E9:0B:76:47:5C:4D:4A:6B:C5:08:15:AE:F8:D1:57:E9:EA:70:14:FF:C9:45:B9:0C:7C:BC:F4:6D:E6:05:52:F9:8C:80:BB:70:56:91:0F:4B", - }, - ], - Critical: false, - }, - { - sectionTitle: "Miscellaneous", - sectionItems: [ - { - label: "Serial Number", - info: "0A:06:30:42:7F:5B:BC:ED:69:57:39:65:93:B6:45:1F", - }, - { label: "Signature Algorithm", info: "SHA-256 with RSA Encryption" }, - { label: "Version", info: "3" }, - { - label: "Download", - info: - "-----BEGIN%20CERTIFICATE-----%0D%0AMIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1%0D%0AMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3%0D%0Ad3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk%0D%0AIFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy%0D%0AMDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB%0D%0ABAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF%0D%0AEwc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG%0D%0AA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD%0D%0AVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA%0D%0Axjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ%0D%0Aw5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj%0D%0A/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE%0D%0AsMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ%0D%0AxbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM%0D%0AfLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag%0D%0Are7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG%0D%0AA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE%0D%0AAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0%0D%0AoDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy%0D%0ALmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy%0D%0AdmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB%0D%0AFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF%0D%0ABQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS%0D%0ABggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0%0D%0AU0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA%0D%0AMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY%0D%0ABPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT%0D%0AWnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg%0D%0AU5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE%0D%0AAwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51%0D%0AvK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV%0D%0ACqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN%0D%0A8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG%0D%0AeOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v%0D%0Aac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN%0D%0AHnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB%0D%0AKqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk%0D%0AmyQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq%0D%0AWUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik=%0D%0A-----END%20CERTIFICATE-----%0D%0A", - }, - ], - Critical: false, - }, - { - sectionTitle: "Fingerprints", - sectionItems: [ - { - label: "SHA-256", - info: - "31:11:50:0C:4A:66:01:2C:DA:E3:33:EC:3F:CA:1C:9D:DE:45:C9:54:44:0E:7E:E4:13:71:6B:FF:36:63:C0:74", - }, - { - label: "SHA-1", - info: "CA:06:F5:6B:25:8B:7A:0D:4F:2B:05:47:09:39:47:86:51:15:19:84", - }, - ], - Critical: false, - }, - { - sectionTitle: "Basic Constraints", - sectionItems: [{ label: "Certificate Authority" }], - Critical: true, - }, - { - sectionTitle: "Key Usages", - sectionItems: [ - { label: "Purposes", info: ["Digital Signature", "Key Encipherment"] }, - ], - Critical: true, - }, - { - sectionTitle: "Extended Key Usages", - sectionItems: [ - { - label: "Purposes", - info: ["Server Authentication", "Client Authentication"], - }, - ], - Critical: false, - }, - { - sectionTitle: "OCSP Stapling", - sectionItems: [{ label: "Required", info: false }], - Critical: false, - }, - { - sectionTitle: "Subject Key ID", - sectionItems: [ - { - label: "Key ID", - info: "C9:C2:53:61:66:9D:5F:AB:25:F4:26:CD:0F:38:9A:A8:49:EA:48:A9", - }, - ], - Critical: false, - }, - { - sectionTitle: "Authority Key ID", - sectionItems: [ - { - label: "Key ID", - info: "3D:D3:50:A5:D6:A0:AD:EE:F3:4A:60:0A:65:D3:21:D4:F8:F8:D6:0F", - }, - ], - Critical: false, - }, - { - sectionTitle: "CRL Endpoints", - sectionItems: [ - { - label: "Distribution Point", - info: "http://crl3.digicert.com/sha2-ev-server-g2.crl", - }, - { - label: "Distribution Point", - info: "http://crl4.digicert.com/sha2-ev-server-g2.crl", - }, - ], - Critical: false, - }, - { - sectionTitle: "Authority Info (AIA)", - sectionItems: [ - { label: "Location", info: "http://ocsp.digicert.com" }, - { label: "Method", info: "Online Certificate Status Protocol (OCSP)" }, - { - label: "Location", - info: - "http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt", - }, - { label: "Method", info: "CA Issuers" }, - ], - Critical: false, - }, - { - sectionTitle: "Certificate Policies", - sectionItems: [ - { - label: "Policy", - info: "ANSI Organizational Identifier ( 2.16.840 )", - }, - { label: "Value", info: "2.16.840.1.114412.2.1" }, - { - label: "Qualifier", - info: "Practices Statement ( 1.3.6.1.5.5.7.2.1 )", - }, - { label: "Value", info: "https://www.digicert.com/CPS" }, - { label: "Policy", info: "Certificate Type ( 2.23.140.1.1 )" }, - { label: "Value", info: "Extended Validation" }, - ], - Critical: false, - }, - { - sectionTitle: "Embedded SCTs", - sectionItems: [ - { - label: "logId", - info: - "A4:B9:09:90:B4:18:58:14:87:BB:13:A2:CC:67:70:0A:3C:35:98:04:F9:1B:DF:B8:E3:77:CD:0E:C8:0D:DC:10", - }, - { label: "name", info: "Google “Pilot”" }, - { label: "signatureAlgorithm", info: "SHA-256 ECDSA" }, - { - label: "timestamp", - info: "5/8/2018, 5:12:39 PM (Brasilia Standard Time)", - }, - { label: "timestampUTC", info: "Tue, 08 May 2018 20:12:39 GMT" }, - { label: "version", info: 1 }, - { - label: "logId", - info: - "56:14:06:9A:2F:D7:C2:EC:D3:F5:E1:BD:44:B2:3E:C7:46:76:B9:BC:99:11:5C:C0:EF:94:98:55:D6:89:D0:DD", - }, - { label: "name", info: "DigiCert Server" }, - { label: "signatureAlgorithm", info: "SHA-256 ECDSA" }, - { - label: "timestamp", - info: "5/8/2018, 5:12:39 PM (Brasilia Standard Time)", - }, - { label: "timestampUTC", info: "Tue, 08 May 2018 20:12:39 GMT" }, - { label: "version", info: 1 }, - { - label: "logId", - info: - "BB:D9:DF:BC:1F:8A:71:B5:93:94:23:97:AA:92:7B:47:38:57:95:0A:AB:52:E8:1A:90:96:64:36:8E:1E:D1:85", - }, - { label: "name", info: "Google “Skydiver”" }, - { label: "signatureAlgorithm", info: "SHA-256 ECDSA" }, - { - label: "timestamp", - info: "5/8/2018, 5:12:39 PM (Brasilia Standard Time)", - }, - { label: "timestampUTC", info: "Tue, 08 May 2018 20:12:39 GMT" }, - { label: "version", info: 1 }, - ], - Critical: false, - }, - ], - tabName: "github.com", -}; diff --git a/toolkit/components/certviewer/tests/browser/browser.ini b/toolkit/components/certviewer/tests/browser/browser.ini index b4e9727a60d7..854308bf2fc4 100644 --- a/toolkit/components/certviewer/tests/browser/browser.ini +++ b/toolkit/components/certviewer/tests/browser/browser.ini @@ -1,5 +1 @@ -[DEFAULT] -support-files = - adjustedCerts.js [browser_openTabAndSendCertInfo.js] -[browser_renderCertToUI.js] diff --git a/toolkit/components/certviewer/tests/browser/browser_renderCertToUI.js b/toolkit/components/certviewer/tests/browser/browser_renderCertToUI.js deleted file mode 100644 index b366a151f810..000000000000 --- a/toolkit/components/certviewer/tests/browser/browser_renderCertToUI.js +++ /dev/null @@ -1,124 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const url = - "about:certificate?cert=MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEyMDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQFEwc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYDVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxjyq8jyXDDrBTyitcnB90865tWBzpHSbindG%2FXqYQkzFMBlXmqkzC%2BFdTRBYyneZw5Pz%2BXWQvL%2B74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj%2F4x%2BogEG3dF%2FU5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero%2B5ZAKfYVMLUEsMwFtoTDJFmVf6JlkOWwsxp1WcQ%2FMRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQxbcaI%2BGwfQL1FB7Jy%2Bh%2BKjME9lE%2FUpgV6Qt2R1xNSmvFCBWu%2BNFX6epwFP%2FJRbkMfLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV%2BrJfQmzQ84mqhJ6kipMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB%2FwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcyLmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG%2FWwCATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWYBPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe%2FuPTWnsu%2Fm4BEC2%2BdIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFgU5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAEAwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d%2B8H4pxtZOUI5eqkntHOFeVCqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N%2BXcqcK0OJYrN8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxGeOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW%2Fip2oJ5grAH8mqQfaunuCVE%2Bvac%2B88lkDK%2FLVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vNHnXVUGw%2ByxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVBKqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc%2F2z2shNoDvxvFUYyY1Oe67xINkmyQKc%2BygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma%2BFXsXBIqWUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna%2Fteik%3D"; - -/* import-globals-from ./adjustedCerts.js */ -Services.scriptloader.loadSubScript( - "chrome://mochitests/content/browser/toolkit/components/certviewer/tests/browser/adjustedCerts.js", - this -); - -add_task(async function test() { - Assert.ok(adjustedCerts, "adjustedCerts found"); - - let tabName = adjustedCerts.tabName; - let certItems = adjustedCerts.certItems; - - await BrowserTestUtils.withNewTab(url, async function(browser) { - await ContentTask.spawn(browser, [certItems, tabName], async function([ - adjustedCerts, - expectedTabName, - ]) { - let certificateSection = await ContentTaskUtils.waitForCondition(() => { - return content.document.querySelector("certificate-section"); - }, "Certificate section found"); - - let infoGroups = certificateSection.shadowRoot.querySelectorAll( - "info-group" - ); - Assert.ok(infoGroups, "infoGroups found"); - Assert.equal( - infoGroups.length, - adjustedCerts.length, - "infoGroups must have the same length of adjustedCerts" - ); - - let tabName = certificateSection.shadowRoot.querySelector( - ".certificate-tabs" - ).children[0].innerText; - Assert.equal(tabName, expectedTabName, "Tab name should be the same"); - - function getElementByAttribute(source, property, target) { - for (let elem of source) { - if (elem.hasOwnProperty(property) && elem[property] === target) { - return elem; - } - } - return null; - } - - for (let infoGroup of infoGroups) { - let sectionTitle = infoGroup.shadowRoot.querySelector( - ".info-group-title" - ).innerText; - - let adjustedCertsElem = getElementByAttribute( - adjustedCerts, - "sectionTitle", - sectionTitle - ); - Assert.ok(adjustedCertsElem, "The element exists in adjustedCerts"); - - let infoItems = infoGroup.shadowRoot.querySelectorAll("info-item"); - Assert.equal( - infoItems.length, - adjustedCertsElem.sectionItems.length, - "sectionItems must be the same length" - ); - - let i = 0; - for (let infoItem of infoItems) { - let infoItemLabel = infoItem.shadowRoot - .querySelector("label") - .getAttribute("data-l10n-id"); - let infoItemInfo = infoItem.shadowRoot.children[2].innerText; - - let adjustedCertsElemLabel = adjustedCertsElem.sectionItems[i].label; - if (adjustedCertsElemLabel == null) { - adjustedCertsElemLabel = ""; - } - adjustedCertsElemLabel = adjustedCertsElemLabel - .replace(/\s+/g, "-") - .toLowerCase(); - - let adjustedCertsElemInfo = adjustedCertsElem.sectionItems[i].info; - if (adjustedCertsElemInfo == null) { - adjustedCertsElemInfo = ""; - } - - if (typeof adjustedCertsElemInfo !== "string") { - // there is a case where we have a boolean - adjustedCertsElemInfo = adjustedCertsElemInfo.toString(); - } - - Assert.ok( - infoItemLabel.includes(adjustedCertsElemLabel), - "data-l10n-id must contain the original label" - ); - - if ( - // we are skiping this cases because we are going to compare them - // with their UTC, e.g: timestampUTC - !( - adjustedCertsElemLabel === "timestamp" || - adjustedCertsElemLabel === "not-after" || - adjustedCertsElemLabel === "not-before" - ) - ) { - Assert.equal( - infoItemInfo, - adjustedCertsElemInfo, - "Info must be equal" - ); - } - - i++; - } - } - }); - }); -});