/* -*- Mode: C++; tab-width: 8; 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 "gtest/gtest.h" #include #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/File.h" #include "mozilla/dom/GetFilesHelper.h" #include "mozilla/dom/UnionTypes.h" #include "mozilla/ErrorResult.h" #include "mozilla/gtest/MozAssertions.h" #include "mozilla/media/MediaUtils.h" #include "mozilla/SpinEventLoopUntil.h" #include "nsIFile.h" #include "SpecialSystemDirectory.h" using namespace mozilla; using namespace mozilla::dom; nsCOMPtr MakeFileFromPathSegments( const nsTArray& aPathSegments) { nsCOMPtr file; MOZ_ALWAYS_SUCCEEDS(GetSpecialSystemDirectory(OS_CurrentWorkingDirectory, getter_AddRefs(file))); for (const auto* pathSegment : aPathSegments) { MOZ_ALWAYS_SUCCEEDS( file->AppendRelativePath(NS_ConvertASCIItoUTF16(pathSegment))); } return file; } nsresult AppendFileOrDirectory(nsTArray& aDirectories, const nsTArray& aPathSegments) { bool exists = false; int retryCount = 5; nsCOMPtr file = MakeFileFromPathSegments(aPathSegments); while (retryCount--) { if (NS_SUCCEEDED(file->Exists(&exists))) { break; } // May require retrying (bug 1963029) std::this_thread::sleep_for(std::chrono::milliseconds(100)); } NS_ENSURE_TRUE(exists, NS_ERROR_FILE_NOT_FOUND); bool isDir; MOZ_ALWAYS_SUCCEEDS(file->IsDirectory(&isDir)); if (isDir) { // We just need to iterate over the directory, so use the junk scope RefPtr directory = Directory::Create(xpc::NativeGlobal(xpc::PrivilegedJunkScope()), file); NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); OwningFileOrDirectory* owningDirectory = aDirectories.EmplaceBack(); owningDirectory->SetAsDirectory() = directory; } else { OwningFileOrDirectory* owningDirectory = aDirectories.EmplaceBack(); RefPtr fileObject = File::CreateFromFile( xpc::NativeGlobal(xpc::PrivilegedJunkScope()), file); owningDirectory->SetAsFile() = fileObject; } return NS_OK; } struct BoolStruct { bool mValue = false; }; class FilesCallback : public GetFilesCallback { public: FilesCallback(std::atomic& aGotResponse, const nsTArray& aExpectedPaths) : mGotResponse(aGotResponse), mExpectedPaths(aExpectedPaths.Clone()) {} // ------------------- // GetFilesCallback // ------------------- void Callback(nsresult aStatus, const FallibleTArray>& aBlobImpls) override { EXPECT_EQ(aBlobImpls.Length(), mExpectedPaths.Length()); for (const auto& blob : aBlobImpls) { nsString path; ErrorResult error; blob->GetMozFullPathInternal(path, error); ASSERT_EQ(error.StealNSResult(), NS_OK); ASSERT_TRUE(mExpectedPaths.Contains(path)); } mGotResponse = true; } private: std::atomic& mGotResponse; nsTArray mExpectedPaths; }; nsTArray GetExpectedPaths( const nsTArray>& aPathSegmentsArray) { nsTArray expectedPaths(aPathSegmentsArray.Length()); for (const auto& pathSegments : aPathSegmentsArray) { auto file = MakeFileFromPathSegments(pathSegments); nsString expectedPath; MOZ_ALWAYS_SUCCEEDS(file->GetPath(expectedPath)); expectedPaths.AppendElement(expectedPath); } return expectedPaths; } void ExpectGetFilesHelperResponse( RefPtr aHelper, const nsTArray>& aPathSegmentsArray) { nsTArray expectedPaths = GetExpectedPaths(aPathSegmentsArray); std::atomic gotCallbackResponse = false; std::atomic gotMozPromiseResponse = false; RefPtr callback = MakeRefPtr(gotCallbackResponse, expectedPaths); aHelper->AddCallback(callback); auto mozPromise = MakeRefPtr(__func__); aHelper->AddMozPromise(mozPromise, xpc::NativeGlobal(xpc::PrivilegedJunkScope())); mozPromise->Then( GetMainThreadSerialEventTarget(), __func__, [&gotMozPromiseResponse, &expectedPaths](const nsTArray>& aFiles) { EXPECT_EQ(aFiles.Length(), expectedPaths.Length()); for (const auto& file : aFiles) { nsString path; ErrorResult error; file->GetMozFullPathInternal(path, error); ASSERT_EQ(error.StealNSResult(), NS_OK); ASSERT_TRUE(expectedPaths.Contains(path)); } gotMozPromiseResponse = true; }, []() { FAIL() << "MozPromise got rejected!"; }); // Make timedOut a RefPtr so if we get a response after this function // has finished we can safely check that (and don't start accessing stack // values that don't exist anymore) RefPtr timedOut = MakeRefPtr>(); RefPtr timer = NS_NewCancelableRunnableFunction("GetFilesHelper timeout", [&] { if (!gotCallbackResponse.load() || !gotMozPromiseResponse.load()) { timedOut->mValue = true; } }); constexpr uint32_t kTimeout = 10000; NS_DelayedDispatchToCurrentThread(do_AddRef(timer), kTimeout); mozilla::SpinEventLoopUntil( "Waiting for GetFilesHelper result"_ns, [&, timedOut]() { return (gotCallbackResponse.load() && gotMozPromiseResponse.load()) || timedOut->mValue; }); timer->Cancel(); EXPECT_TRUE(gotCallbackResponse); EXPECT_TRUE(gotMozPromiseResponse); EXPECT_FALSE(timedOut->mValue); } TEST(GetFilesHelper, TestSingleDirectory) { nsTArray directories; ASSERT_NS_SUCCEEDED( AppendFileOrDirectory(directories, {"getfiles", "inner2"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, true, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner2", "fileinner2.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); } TEST(GetFilesHelper, TestSingleNestedDirectory) { nsTArray directories; ASSERT_NS_SUCCEEDED( AppendFileOrDirectory(directories, {"getfiles", "inner1"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, true, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "fileinner1.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "inner", "fileinnerinner1.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); } TEST(GetFilesHelper, TestSingleNestedDirectoryNoRecursion) { nsTArray directories; ASSERT_NS_SUCCEEDED( AppendFileOrDirectory(directories, {"getfiles", "inner1"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, false, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "fileinner1.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); } TEST(GetFilesHelper, TestSingleDirectoryWithMultipleNestedChildren) { nsTArray directories; ASSERT_NS_SUCCEEDED(AppendFileOrDirectory(directories, {"getfiles"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, true, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement(nsTArray{"getfiles", "file1.txt"}); pathSegmentsArray.AppendElement(nsTArray{"getfiles", "file2.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "fileinner1.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "inner", "fileinnerinner1.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner2", "fileinner2.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); } TEST(GetFilesHelper, TestSingleFile) { nsTArray directories; ASSERT_NS_SUCCEEDED(AppendFileOrDirectory( directories, {"getfiles", "inner1", "fileinner1.txt"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, true, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "fileinner1.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); } TEST(GetFilesHelper, TestMultipleFiles) { nsTArray directories; ASSERT_NS_SUCCEEDED(AppendFileOrDirectory( directories, {"getfiles", "inner1", "fileinner1.txt"})); ASSERT_NS_SUCCEEDED(AppendFileOrDirectory( directories, {"getfiles", "inner2", "fileinner2.txt"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, true, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "fileinner1.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner2", "fileinner2.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); } // Content Analysis can use GetFilesHelper with multiple directories, // so make sure that works. TEST(GetFilesHelper, TestMultipleDirectories) { nsTArray directories; ASSERT_NS_SUCCEEDED( AppendFileOrDirectory(directories, {"getfiles", "inner1"})); ASSERT_NS_SUCCEEDED( AppendFileOrDirectory(directories, {"getfiles", "inner2"})); ErrorResult error; RefPtr helper = GetFilesHelper::Create(directories, true, error); ASSERT_EQ(error.StealNSResult(), NS_OK); nsTArray> pathSegmentsArray; pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "fileinner1.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner1", "inner", "fileinnerinner1.txt"}); pathSegmentsArray.AppendElement( nsTArray{"getfiles", "inner2", "fileinner2.txt"}); ExpectGetFilesHelperResponse(helper, pathSegmentsArray); }