// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "third_party/blink/renderer/core/fileapi/file.h" #include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" #include "third_party/blink/public/mojom/file/file_utilities.mojom-blink.h" #include "third_party/blink/public/platform/platform.h" #include "third_party/blink/renderer/platform/blob/testing/fake_blob.h" #include "third_party/blink/renderer/platform/file_metadata.h" #include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" namespace blink { namespace { class MockBlob : public FakeBlob { public: static void Create(File* file, base::Time modified_time) { mojo::PendingRemote remote; PostCrossThreadTask( *base::ThreadPool::CreateSingleThreadTaskRunner({}), FROM_HERE, CrossThreadBindOnce( [](const String& uuid, mojo::PendingReceiver receiver, base::Time modified_time) { mojo::MakeSelfOwnedReceiver( std::make_unique(uuid, modified_time), std::move(receiver)); }, file->Uuid(), remote.InitWithNewPipeAndPassReceiver(), modified_time)); file->GetBlobDataHandle()->SetBlobRemoteForTesting(std::move(remote)); } MockBlob(const String& uuid, base::Time modified_time) : FakeBlob(uuid), modified_time_(modified_time) {} void Clone(mojo::PendingReceiver receiver) override { mojo::MakeSelfOwnedReceiver( std::make_unique(uuid_, modified_time_), std::move(receiver)); } void CaptureSnapshot(CaptureSnapshotCallback callback) override { std::move(callback).Run( /*size=*/0, NullableTimeToOptionalTime(modified_time_)); } private: base::Time modified_time_; }; void ExpectTimestampIsNow(const File& file) { const base::Time now = base::Time::Now(); const base::TimeDelta delta_now = now - base::Time::UnixEpoch(); // Because lastModified() applies floor() internally, we should compare // integral millisecond values. EXPECT_GE(file.lastModified(), delta_now.InMilliseconds()); EXPECT_GE(file.LastModifiedTime(), now); } } // namespace TEST(FileTest, NativeFileWithoutTimestamp) { auto* const file = MakeGarbageCollected("/native/path"); MockBlob::Create(file, base::Time()); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ("/native/path", file->GetPath()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); ExpectTimestampIsNow(*file); } TEST(FileTest, NativeFileWithUnixEpochTimestamp) { auto* const file = MakeGarbageCollected("/native/path"); MockBlob::Create(file, base::Time::UnixEpoch()); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ(0, file->lastModified()); EXPECT_EQ(base::Time::UnixEpoch(), file->LastModifiedTime()); } TEST(FileTest, NativeFileWithApocalypseTimestamp) { auto* const file = MakeGarbageCollected("/native/path"); MockBlob::Create(file, base::Time::Max()); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ((base::Time::Max() - base::Time::UnixEpoch()).InMilliseconds(), file->lastModified()); EXPECT_EQ(base::Time::Max(), file->LastModifiedTime()); } TEST(FileTest, BlobBackingFileWithoutTimestamp) { auto* const file = MakeGarbageCollected("name", base::nullopt, BlobDataHandle::Create()); EXPECT_FALSE(file->HasBackingFile()); EXPECT_TRUE(file->GetPath().IsEmpty()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); ExpectTimestampIsNow(*file); } TEST(FileTest, BlobBackingFileWithWindowsEpochTimestamp) { auto* const file = MakeGarbageCollected("name", base::Time(), BlobDataHandle::Create()); EXPECT_FALSE(file->HasBackingFile()); EXPECT_TRUE(file->GetPath().IsEmpty()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_EQ((base::Time() - base::Time::UnixEpoch()).InMilliseconds(), file->lastModified()); EXPECT_EQ(base::Time(), file->LastModifiedTime()); } TEST(FileTest, BlobBackingFileWithUnixEpochTimestamp) { const scoped_refptr blob_data_handle = BlobDataHandle::Create(); auto* const file = MakeGarbageCollected("name", base::Time::UnixEpoch(), blob_data_handle); EXPECT_FALSE(file->HasBackingFile()); EXPECT_TRUE(file->GetPath().IsEmpty()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_EQ(INT64_C(0), file->lastModified()); EXPECT_EQ(base::Time::UnixEpoch(), file->LastModifiedTime()); } TEST(FileTest, BlobBackingFileWithApocalypseTimestamp) { constexpr base::Time kMaxTime = base::Time::Max(); auto* const file = MakeGarbageCollected("name", kMaxTime, BlobDataHandle::Create()); EXPECT_FALSE(file->HasBackingFile()); EXPECT_TRUE(file->GetPath().IsEmpty()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_EQ((kMaxTime - base::Time::UnixEpoch()).InMilliseconds(), file->lastModified()); EXPECT_EQ(kMaxTime, file->LastModifiedTime()); } TEST(FileTest, fileSystemFileWithNativeSnapshot) { FileMetadata metadata; metadata.platform_path = "/native/snapshot"; File* const file = File::CreateForFileSystemFile("name", metadata, File::kIsUserVisible); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ("/native/snapshot", file->GetPath()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); } TEST(FileTest, fileSystemFileWithNativeSnapshotAndSize) { FileMetadata metadata; metadata.length = 1024ll; metadata.platform_path = "/native/snapshot"; File* const file = File::CreateForFileSystemFile("name", metadata, File::kIsUserVisible); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ("/native/snapshot", file->GetPath()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); } TEST(FileTest, FileSystemFileWithWindowsEpochTimestamp) { FileMetadata metadata; metadata.length = INT64_C(1025); metadata.modification_time = base::Time(); metadata.platform_path = "/native/snapshot"; File* const file = File::CreateForFileSystemFile("name", metadata, File::kIsUserVisible); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ("/native/snapshot", file->GetPath()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_EQ(UINT64_C(1025), file->size()); EXPECT_EQ((base::Time() - base::Time::UnixEpoch()).InMilliseconds(), file->lastModified()); EXPECT_EQ(base::Time(), file->LastModifiedTime()); } TEST(FileTest, FileSystemFileWithUnixEpochTimestamp) { FileMetadata metadata; metadata.length = INT64_C(1025); metadata.modification_time = base::Time::UnixEpoch(); metadata.platform_path = "/native/snapshot"; File* const file = File::CreateForFileSystemFile("name", metadata, File::kIsUserVisible); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ("/native/snapshot", file->GetPath()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_EQ(UINT64_C(1025), file->size()); EXPECT_EQ(INT64_C(0), file->lastModified()); EXPECT_EQ(base::Time::UnixEpoch(), file->LastModifiedTime()); } TEST(FileTest, FileSystemFileWithApocalypseTimestamp) { constexpr base::Time kMaxTime = base::Time::Max(); FileMetadata metadata; metadata.length = INT64_C(1025); metadata.modification_time = kMaxTime; metadata.platform_path = "/native/snapshot"; File* const file = File::CreateForFileSystemFile("name", metadata, File::kIsUserVisible); EXPECT_TRUE(file->HasBackingFile()); EXPECT_EQ("/native/snapshot", file->GetPath()); EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_EQ(UINT64_C(1025), file->size()); EXPECT_EQ((kMaxTime - base::Time::UnixEpoch()).InMilliseconds(), file->lastModified()); EXPECT_EQ(kMaxTime, file->LastModifiedTime()); } TEST(FileTest, fileSystemFileWithoutNativeSnapshot) { KURL url("filesystem:http://example.com/isolated/hash/non-native-file"); FileMetadata metadata; metadata.length = 0; File* const file = File::CreateForFileSystemFile(url, metadata, File::kIsUserVisible); EXPECT_FALSE(file->HasBackingFile()); EXPECT_TRUE(file->GetPath().IsEmpty()); EXPECT_EQ(url, file->FileSystemURL()); } TEST(FileTest, hsaSameSource) { auto* const native_file_a1 = MakeGarbageCollected("/native/pathA"); auto* const native_file_a2 = MakeGarbageCollected("/native/pathA"); auto* const native_file_b = MakeGarbageCollected("/native/pathB"); const scoped_refptr blob_data_a = BlobDataHandle::Create(); const scoped_refptr blob_data_b = BlobDataHandle::Create(); const base::Time kEpoch = base::Time::UnixEpoch(); auto* const blob_file_a1 = MakeGarbageCollected("name", kEpoch, blob_data_a); auto* const blob_file_a2 = MakeGarbageCollected("name", kEpoch, blob_data_a); auto* const blob_file_b = MakeGarbageCollected("name", kEpoch, blob_data_b); KURL url_a("filesystem:http://example.com/isolated/hash/non-native-file-A"); KURL url_b("filesystem:http://example.com/isolated/hash/non-native-file-B"); FileMetadata metadata; metadata.length = 0; File* const file_system_file_a1 = File::CreateForFileSystemFile(url_a, metadata, File::kIsUserVisible); File* const file_system_file_a2 = File::CreateForFileSystemFile(url_a, metadata, File::kIsUserVisible); File* const file_system_file_b = File::CreateForFileSystemFile(url_b, metadata, File::kIsUserVisible); EXPECT_FALSE(native_file_a1->HasSameSource(*blob_file_a1)); EXPECT_FALSE(blob_file_a1->HasSameSource(*file_system_file_a1)); EXPECT_FALSE(file_system_file_a1->HasSameSource(*native_file_a1)); EXPECT_TRUE(native_file_a1->HasSameSource(*native_file_a1)); EXPECT_TRUE(native_file_a1->HasSameSource(*native_file_a2)); EXPECT_FALSE(native_file_a1->HasSameSource(*native_file_b)); EXPECT_TRUE(blob_file_a1->HasSameSource(*blob_file_a1)); EXPECT_TRUE(blob_file_a1->HasSameSource(*blob_file_a2)); EXPECT_FALSE(blob_file_a1->HasSameSource(*blob_file_b)); EXPECT_TRUE(file_system_file_a1->HasSameSource(*file_system_file_a1)); EXPECT_TRUE(file_system_file_a1->HasSameSource(*file_system_file_a2)); EXPECT_FALSE(file_system_file_a1->HasSameSource(*file_system_file_b)); } } // namespace blink