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:

committed by
Chromium LUCI CQ

parent
8265aa3b14
commit
30f1d6fc16
chrome
browser
ash
file_manager
chromeos
extensions
file_manager
common
extensions
test
data
extensions
api_test
file_browser
mount_test
third_party/closure_compiler/externs
tools/metrics/histograms
ui/file_manager
file_manager
background
common
foreground
integration_tests
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.
|
||||
|
Reference in New Issue
Block a user