0

Files: Add 'system_internal' Volume type and 'hidden' property

These will be used by a new hidden Volume used to store cached Web Share
files. Hidden volumes are accessible through the chrome.fileSystem API
and through C++ file APIs, but are not exposed to the user-facing File
Manager.

Bug: 1239483
Change-Id: I6821dba4d3ad3be90f25fa7ac453e9ab60bda5a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3288603
Reviewed-by: Luciano Pacheco <lucmult@chromium.org>
Commit-Queue: Tim Sergeant <tsergeant@chromium.org>
Cr-Commit-Position: refs/heads/main@{#944849}
This commit is contained in:
Tim Sergeant
2021-11-24 05:57:27 +00:00
committed by Chromium LUCI CQ
parent 8265aa3b14
commit 30f1d6fc16
15 changed files with 145 additions and 19 deletions
chrome
browser
common
test
data
extensions
api_test
file_browser
mount_test
third_party/closure_compiler/externs
tools/metrics/histograms
ui/file_manager

@ -311,6 +311,8 @@ WRAPPED_INSTANTIATE_TEST_SUITE_P(
// TestCase("fileDisplayWithoutDriveThenDisable")
// .DontMountVolumes()
// .FilesSwa(),
TestCase("fileDisplayWithHiddenVolume"),
TestCase("fileDisplayWithHiddenVolume").FilesSwa(),
TestCase("fileDisplayMountWithFakeItemSelected"),
TestCase("fileDisplayUnmountDriveWithSharedWithMeSelected"),
TestCase("fileDisplayUnmountRemovableRoot"),

@ -36,6 +36,7 @@
#include "base/json/json_value_converter.h"
#include "base/json/json_writer.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
@ -1480,6 +1481,30 @@ class MediaViewTestVolume : public DocumentsProviderTestVolume {
}
};
// An internal volume which is hidden from file manager.
class HiddenTestVolume : public FakeTestVolume {
public:
HiddenTestVolume()
: FakeTestVolume("internal_test",
VolumeType::VOLUME_TYPE_SYSTEM_INTERNAL,
chromeos::DeviceType::DEVICE_TYPE_UNKNOWN) {}
HiddenTestVolume(const HiddenTestVolume&) = delete;
HiddenTestVolume& operator=(const HiddenTestVolume&) = delete;
bool Mount(Profile* profile) override {
if (!MountSetup(profile))
return false;
// Expose the mount point with the given volume and device type.
VolumeManager::Get(profile)->AddVolumeForTesting(
root_path(), volume_type_, device_type_, read_only_,
/*device_path=*/base::FilePath(),
/*drive_label=*/"", /*file_system_type=*/"", /*hidden=*/true);
base::RunLoop().RunUntilIdle();
return true;
}
};
class MockSmbFsMounter : public smbfs::SmbFsMounter {
public:
MOCK_METHOD(void,
@ -1909,6 +1934,8 @@ void FileManagerBrowserTestBase::SetUpOnMainThread() {
smbfs_volume_ = std::make_unique<SmbfsTestVolume>();
hidden_volume_ = std::make_unique<HiddenTestVolume>();
display_service_ =
std::make_unique<NotificationDisplayServiceTester>(profile());
@ -2548,6 +2575,12 @@ void FileManagerBrowserTestBase::OnCommand(const std::string& name,
return;
}
if (name == "mountHidden") {
DCHECK(hidden_volume_);
ASSERT_TRUE(hidden_volume_->Mount(profile()));
return;
}
if (name == "setDriveEnabled") {
absl::optional<bool> enabled = value.FindBoolKey("enabled");
ASSERT_TRUE(enabled.has_value());
@ -2703,7 +2736,9 @@ void FileManagerBrowserTestBase::OnCommand(const std::string& name,
if (name == "getVolumesCount") {
file_manager::VolumeManager* volume_manager = VolumeManager::Get(profile());
*output = base::NumberToString(volume_manager->GetVolumeList().size());
*output = base::NumberToString(base::ranges::count_if(
volume_manager->GetVolumeList(),
[](const auto& volume) { return !volume->hidden(); }));
return;
}

@ -49,6 +49,7 @@ class RemovableTestVolume;
class DocumentsProviderTestVolume;
class MediaViewTestVolume;
class SmbfsTestVolume;
class HiddenTestVolume;
class FileManagerBrowserTestBase : public content::DevToolsAgentHostObserver,
public extensions::ExtensionApiTest {
@ -224,6 +225,7 @@ class FileManagerBrowserTestBase : public content::DevToolsAgentHostObserver,
std::unique_ptr<MediaViewTestVolume> media_view_videos_;
std::unique_ptr<MediaViewTestVolume> media_view_audio_;
std::unique_ptr<SmbfsTestVolume> smbfs_volume_;
std::unique_ptr<HiddenTestVolume> hidden_volume_;
drive::DriveIntegrationServiceFactory::FactoryCallback
create_drive_integration_service_;

@ -153,6 +153,8 @@ std::string VolumeTypeToString(VolumeType type) {
return "crostini";
case VOLUME_TYPE_SMB:
return "smb";
case VOLUME_TYPE_SYSTEM_INTERNAL:
return "system_internal";
case NUM_VOLUME_TYPE:
break;
}
@ -222,8 +224,8 @@ Volume::Volume()
is_read_only_removable_device_(false),
has_media_(false),
configurable_(false),
watchable_(false) {
}
watchable_(false),
hidden_(false) {}
Volume::~Volume() = default;
@ -445,7 +447,8 @@ std::unique_ptr<Volume> Volume::CreateForTesting(
bool read_only,
const base::FilePath& device_path,
const std::string& drive_label,
const std::string& file_system_type) {
const std::string& file_system_type,
bool hidden) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = volume_type;
volume->device_type_ = device_type;
@ -460,6 +463,7 @@ std::unique_ptr<Volume> Volume::CreateForTesting(
if (volume_type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) {
volume->file_system_type_ = file_system_type;
}
volume->hidden_ = hidden;
return volume;
}
@ -768,12 +772,13 @@ void VolumeManager::AddVolumeForTesting(const base::FilePath& path,
bool read_only,
const base::FilePath& device_path,
const std::string& drive_label,
const std::string& file_system_type) {
const std::string& file_system_type,
bool hidden) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoMountEvent(
chromeos::MOUNT_ERROR_NONE,
Volume::CreateForTesting(path, volume_type, device_type, read_only,
device_path, drive_label, file_system_type));
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForTesting(path, volume_type, device_type,
read_only, device_path, drive_label,
file_system_type, hidden));
}
void VolumeManager::AddVolumeForTesting(std::unique_ptr<Volume> volume) {

@ -64,6 +64,8 @@ enum VolumeType {
VOLUME_TYPE_ANDROID_FILES,
VOLUME_TYPE_DOCUMENTS_PROVIDER,
VOLUME_TYPE_SMB,
VOLUME_TYPE_SYSTEM_INTERNAL, // Internal volume which is never exposed to
// users.
// The enum values must be kept in sync with FileManagerVolumeType in
// tools/metrics/histograms/enums.xml. Since enums for histograms are
// append-only (for keeping the number consistent across versions), new values
@ -134,7 +136,8 @@ class Volume : public base::SupportsWeakPtr<Volume> {
bool read_only,
const base::FilePath& device_path,
const std::string& drive_label,
const std::string& file_system_type = "");
const std::string& file_system_type = "",
bool hidden = false);
static std::unique_ptr<Volume> CreateForTesting(
const base::FilePath& device_path,
const base::FilePath& mount_path);
@ -180,6 +183,7 @@ class Volume : public base::SupportsWeakPtr<Volume> {
const ash::file_system_provider::IconSet& icon_set() const {
return icon_set_;
}
bool hidden() const { return hidden_; }
private:
Volume();
@ -264,6 +268,10 @@ class Volume : public base::SupportsWeakPtr<Volume> {
// Device label of a physical removable device. Removable partitions
// belonging to the same device share the same device label.
std::string drive_label_;
// True if the volume is hidden and never shown to the user through File
// Manager.
bool hidden_;
};
// Manages Volumes for file manager. Example of Volumes:
@ -374,7 +382,8 @@ class VolumeManager : public KeyedService,
bool read_only,
const base::FilePath& device_path = base::FilePath(),
const std::string& drive_label = "",
const std::string& file_system_type = "");
const std::string& file_system_type = "",
bool hidden = false);
// For testing purposes, adds the volume info to the volume manager.
void AddVolumeForTesting(std::unique_ptr<Volume> volume);

@ -497,6 +497,10 @@ void VolumeToVolumeMetadata(
case VOLUME_TYPE_SMB:
volume_metadata->volume_type = file_manager_private::VOLUME_TYPE_SMB;
break;
case VOLUME_TYPE_SYSTEM_INTERNAL:
volume_metadata->volume_type =
file_manager_private::VOLUME_TYPE_SYSTEM_INTERNAL;
break;
case NUM_VOLUME_TYPE:
NOTREACHED();
break;
@ -535,6 +539,7 @@ void VolumeToVolumeMetadata(
volume_metadata->is_read_only = volume.is_read_only();
volume_metadata->has_media = volume.has_media();
volume_metadata->hidden = volume.hidden();
switch (volume.mount_condition()) {
case chromeos::disks::MOUNT_CONDITION_NONE:

@ -49,7 +49,7 @@ namespace fileManagerPrivate {
// Type of the mounted volume.
enum VolumeType { drive, downloads, removable, archive, provided, mtp,
media_view, crostini, android_files, documents_provider,
testing, smb };
testing, smb, system_internal };
// Device type. Available if this is removable volume.
enum DeviceType { usb, sd, optical, mobile, unknown };
@ -593,6 +593,9 @@ dictionary VolumeMetadata {
// The path on the remote host where this volume is mounted, for crostini this
// is the user's homedir (/home/<username>).
DOMString? remoteMountPath;
// Flag that specifies whether the volume is hidden from the user.
boolean hidden;
};
// Payload data for mount event.

@ -20,7 +20,8 @@ var expectedVolume1 = {
profile: {profileId: '', displayName: '', isCurrentProfile: true},
diskFileSystemType: 'exfat',
iconSet: {},
driveLabel: 'drive_label1'
driveLabel: 'drive_label1',
hidden: false
};
var expectedVolume2 = {
@ -42,7 +43,8 @@ var expectedVolume2 = {
profile: {profileId: '', displayName: '', isCurrentProfile: true},
diskFileSystemType: 'exfat',
iconSet: {},
driveLabel: 'drive_label2'
driveLabel: 'drive_label2',
hidden: false
};
var expectedVolume3 = {
@ -62,7 +64,8 @@ var expectedVolume3 = {
profile: {profileId: '', displayName: '', isCurrentProfile: true},
diskFileSystemType: 'exfat',
iconSet: {},
driveLabel: 'drive_label3'
driveLabel: 'drive_label3',
hidden: false
};
var expectedDownloadsVolume = {
@ -78,7 +81,8 @@ var expectedDownloadsVolume = {
profile: {profileId: '', displayName: '', isCurrentProfile: true},
diskFileSystemType: '',
iconSet: {},
driveLabel: ''
driveLabel: '',
hidden: false
};
var expectedArchiveVolume = {
@ -95,7 +99,8 @@ var expectedArchiveVolume = {
profile: {profileId: '', displayName: '', isCurrentProfile: true},
diskFileSystemType: '',
iconSet: {},
driveLabel: ''
driveLabel: '',
hidden: false
};
var expectedProvidedVolume = {
@ -117,7 +122,8 @@ var expectedProvidedVolume = {
icon16x16Url: 'chrome://resources/testing-provider-id-16.jpg',
icon32x32Url: 'chrome://resources/testing-provider-id-32.jpg'
},
driveLabel: ''
driveLabel: '',
hidden: false
};
// List of expected mount points.

@ -21,6 +21,7 @@ chrome.fileManagerPrivate.VolumeType = {
DOCUMENTS_PROVIDER: 'documents_provider',
TESTING: 'testing',
SMB: 'smb',
SYSTEM_INTERNAL: 'system_internal',
};
/** @enum {string} */

@ -37007,6 +37007,8 @@ Called by update_permissions_policy_enum.py.-->
</enum>
<enum name="FileManagerVolumeType">
<!-- Keep this in sync with the JS value: QuickViewUma.VolumeType. -->
<int value="0" label="Google Drive"/>
<int value="1" label="Download Folder"/>
<int value="2" label="Removable Disk"/>
@ -37018,6 +37020,7 @@ Called by update_permissions_policy_enum.py.-->
<int value="8" label="Android Files"/>
<int value="9" label="Android Documents Provider"/>
<int value="10" label="SMB File Share"/>
<int value="11" label="System internal"/>
</enum>
<enum name="FileManagerZipHandlerType">

@ -158,13 +158,14 @@ export class VolumeManagerImpl extends EventTarget {
try {
console.warn('Getting volumes');
const volumeMetadataList = await new Promise(
let volumeMetadataList = await new Promise(
resolve => chrome.fileManagerPrivate.getVolumeMetadataList(resolve));
if (!volumeMetadataList) {
console.error('Cannot get volumes');
finishInitialization();
return;
}
volumeMetadataList = volumeMetadataList.filter(volume => !volume.hidden);
console.debug(`There are ${volumeMetadataList.length} volumes`);
let counter = 0;
@ -236,6 +237,12 @@ export class VolumeManagerImpl extends EventTarget {
case 'success':
case VolumeManagerCommon.VolumeError.UNKNOWN_FILESYSTEM:
case VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM: {
if (volumeMetadata.hidden) {
console.debug(`Mount discarded for hidden volume: '${volumeId}'`);
this.finishRequest_(requestKey, status);
return;
}
console.debug(`Mounted '${sourcePath}' as '${volumeId}'`);
const volumeInfo =
await volumeManagerUtil.createVolumeInfo(volumeMetadata);

@ -235,6 +235,7 @@ VolumeManagerCommon.VolumeType = {
ANDROID_FILES: 'android_files',
MY_FILES: 'my_files',
SMB: 'smb',
SYSTEM_INTERNAL: 'system_internal',
TRASH: 'trash',
};

@ -19,6 +19,11 @@ export function testRootTypeFromVolumeTypeBijection() {
return;
}
// System Internal volumes do not have a corresponding root.
if (volumeType == VolumeManagerCommon.VolumeType.SYSTEM_INTERNAL) {
return;
}
const rootType = VolumeManagerCommon.getRootTypeFromVolumeType(volumeType);
assertTrue(
volumeType == VolumeManagerCommon.getVolumeTypeFromRootType(rootType));

@ -135,4 +135,5 @@ QuickViewUma.VolumeType = [
VolumeManagerCommon.VolumeType.ANDROID_FILES,
VolumeManagerCommon.VolumeType.DOCUMENTS_PROVIDER,
VolumeManagerCommon.VolumeType.SMB,
VolumeManagerCommon.VolumeType.SYSTEM_INTERNAL,
];

@ -704,6 +704,47 @@ testcase.fileDisplayWithoutDriveThenDisable = async () => {
await remoteCall.waitForElement(appId, ['[root-type-icon=\'drive\']']);
};
/**
* Tests that mounting a hidden Volume does not mount the volume in file
* manager.
*/
testcase.fileDisplayWithHiddenVolume = async () => {
const initialVolumeCount = await sendTestMessage({name: 'getVolumesCount'});
const appId =
await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
// Get the directory tree elements.
const dirTreeQuery = ['#directory-tree [dir-type]'];
const elementsBefore = await remoteCall.callRemoteTestUtil(
'queryAllElements', appId, dirTreeQuery);
const visibleElementsBefore = [];
for (const element of elementsBefore) {
if (!element.hidden) { // Ignore hidden elements.
visibleElementsBefore.push(element.attributes['entry-label']);
}
}
// Mount a hidden volume.
await sendTestMessage({name: 'mountHidden'});
const elementsAfter = await remoteCall.callRemoteTestUtil(
'queryAllElements', appId, dirTreeQuery);
const visibleElementsAfter = [];
for (const element of elementsAfter) {
if (!element.hidden) { // Ignore hidden elements.
visibleElementsAfter.push(element.attributes['entry-label']);
}
}
// The directory tree should NOT display the hidden volume.
chrome.test.assertEq(elementsBefore, elementsAfter);
// The hidden volume should not be counted in the number of volumes.
chrome.test.assertEq(
initialVolumeCount, await sendTestMessage({name: 'getVolumesCount'}));
};
/**
* Tests Files app resisting the urge to switch to Downloads when mounts change.
* re-enabling Drive.