Commit dee2de1f authored by Joel Hockey's avatar Joel Hockey Committed by Commit Bot

Crostini: remount persisted shared folders at startup

Note that this feature is being added to read any persisted shares,
however support to write shares as persisted will not be added
until the management UI where shares can be removed is completed.

* prefs: crostini.shared_paths will be a list of filesystem path strings.
* New function CrostiniSharePath::GetSharedPaths() reads from prefs.
* New fileManagerPrivate.getCrostiniSharedPaths executes
  callback to receive list of Entry.
* FileManager.setupCrostini_ loads persisted shares at startup.
* FileManagerApiTest.Crostini verifies reading from prefs and correct
  conversion of filesystem paths to FileEntry.
* New integration test for fileManagerPrivate.getCrostiniSharedPaths().


Bug: 878324
Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: I5c8ee8193eb2fda94e5505d79b449797fbab9449
Reviewed-on: https://chromium-review.googlesource.com/1220411Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Reviewed-by: default avatarLuciano Pacheco <lucmult@chromium.org>
Reviewed-by: default avatarNicholas Verne <nverne@chromium.org>
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592208}
parent 18126ed5
......@@ -12,16 +12,19 @@ namespace prefs {
// A boolean preference representing whether a user has opted in to use
// Crostini (Called "Linux Apps" in UI).
const char kCrostiniEnabled[] = "crostini.enabled";
const char kCrostiniRegistry[] = "crostini.registry";
const char kCrostiniMimeTypes[] = "crostini.mime_types";
const char kCrostiniRegistry[] = "crostini.registry";
// List of filesystem paths that are shared with the crostini container.
const char kCrostiniSharedPaths[] = "crostini.shared_paths";
// A boolean preference representing a user level enterprise policy to enable
// Crostini use.
const char kUserCrostiniAllowedByPolicy[] = "crostini.user_allowed_by_policy";
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kCrostiniEnabled, false);
registry->RegisterDictionaryPref(kCrostiniRegistry);
registry->RegisterDictionaryPref(kCrostiniMimeTypes);
registry->RegisterDictionaryPref(kCrostiniRegistry);
registry->RegisterListPref(kCrostiniSharedPaths);
registry->RegisterBooleanPref(kUserCrostiniAllowedByPolicy, true);
}
......
......@@ -11,8 +11,9 @@ namespace crostini {
namespace prefs {
extern const char kCrostiniEnabled[];
extern const char kCrostiniRegistry[];
extern const char kCrostiniMimeTypes[];
extern const char kCrostiniRegistry[];
extern const char kCrostiniSharedPaths[];
extern const char kUserCrostiniAllowedByPolicy[];
void RegisterProfilePrefs(PrefRegistrySimple* registry);
......
......@@ -7,11 +7,13 @@
#include "base/bind.h"
#include "base/optional.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/dbus/concierge/service.pb.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/seneschal_client.h"
#include "components/prefs/pref_service.h"
namespace crostini {
......@@ -28,6 +30,7 @@ void CrostiniSharePath::SharePath(
std::string vm_name,
std::string path,
base::OnceCallback<void(bool, std::string)> callback) {
// TODO(joelhockey): Save new path into prefs once management UI is ready.
base::Optional<vm_tools::concierge::VmInfo> vm_info =
crostini::CrostiniManager::GetForProfile(profile)->GetVmInfo(
std::move(vm_name));
......@@ -61,4 +64,14 @@ void CrostiniSharePath::OnSharePathResponse(
response.value().failure_reason());
}
std::vector<std::string> CrostiniSharePath::GetSharedPaths(Profile* profile) {
std::vector<std::string> result;
const base::ListValue* shared_paths =
profile->GetPrefs()->GetList(prefs::kCrostiniSharedPaths);
for (const auto& path : *shared_paths) {
result.emplace_back(path.GetString());
}
return result;
}
} // namespace crostini
......@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHARE_PATH_H_
#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHARE_PATH_H_
#include <vector>
#include "base/macros.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
......@@ -20,6 +22,7 @@ class CrostiniSharePath {
// Returns the singleton instance of CrostiniSharePath.
static CrostiniSharePath* GetInstance();
// Share specified path with vm.
void SharePath(Profile* profile,
std::string vm_name,
std::string path,
......@@ -29,6 +32,9 @@ class CrostiniSharePath {
base::OnceCallback<void(bool, std::string)> callback,
base::Optional<vm_tools::seneschal::SharePathResponse> response) const;
// Get list of all shared paths for the default crostini container.
std::vector<std::string> GetSharedPaths(Profile* profile);
private:
friend struct base::DefaultSingletonTraits<CrostiniSharePath>;
......
......@@ -28,6 +28,7 @@
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/concierge/service.pb.h"
#include "chromeos/dbus/cros_disks_client.h"
#include "chromeos/disks/disk.h"
#include "chromeos/disks/mock_disk_mount_manager.h"
......@@ -560,6 +561,17 @@ IN_PROC_BROWSER_TEST_F(FileManagerPrivateApiTest, Crostini) {
&downloads));
ASSERT_TRUE(base::CreateDirectory(downloads.AppendASCII("share_dir")));
// Setup prefs crostini.shared_paths.
base::FilePath shared1 = downloads.AppendASCII("shared1");
base::FilePath shared2 = downloads.AppendASCII("shared2");
ASSERT_TRUE(base::CreateDirectory(shared1));
ASSERT_TRUE(base::CreateDirectory(shared2));
base::ListValue shared_paths;
shared_paths.AppendString(shared1.value());
shared_paths.AppendString(shared2.value());
browser()->profile()->GetPrefs()->Set(crostini::prefs::kCrostiniSharedPaths,
shared_paths);
ASSERT_TRUE(RunComponentExtensionTest("file_browser/crostini_test"));
}
......
......@@ -719,6 +719,45 @@ void FileManagerPrivateInternalSharePathWithCrostiniFunction::SharePathCallback(
Respond(success ? NoArguments() : Error(failure_reason));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalGetCrostiniSharedPathsFunction::Run() {
Profile* profile = Profile::FromBrowserContext(browser_context());
file_manager::util::FileDefinitionList file_definition_list;
auto shared_paths =
crostini::CrostiniSharePath::GetInstance()->GetSharedPaths(profile);
for (const std::string& path : shared_paths) {
file_manager::util::FileDefinition file_definition;
// All shared paths should be directories. Even if this is not true, it
// is fine for foreground/js/crostini.js class to think so.
// We verify that the paths are in fact valid directories before calling
// seneschal/9p.
file_definition.is_directory = true;
if (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
profile, extension_id(), base::FilePath(path),
&file_definition.virtual_path)) {
file_definition_list.emplace_back(std::move(file_definition));
}
}
file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
profile, extension_id(),
file_definition_list, // Safe, since copied internally.
base::Bind(&FileManagerPrivateInternalGetCrostiniSharedPathsFunction::
OnConvertFileDefinitionListToEntryDefinitionList,
this));
return RespondLater();
}
void FileManagerPrivateInternalGetCrostiniSharedPathsFunction::
OnConvertFileDefinitionListToEntryDefinitionList(
std::unique_ptr<file_manager::util::EntryDefinitionList>
entry_definition_list) {
DCHECK(entry_definition_list);
Respond(OneArgument(file_manager::util::ConvertEntryDefinitionListToListValue(
*entry_definition_list)));
}
ExtensionFunction::ResponseAction
FileManagerPrivateInternalInstallLinuxPackageFunction::Run() {
using extensions::api::file_manager_private_internal::InstallLinuxPackage::
......@@ -909,16 +948,13 @@ void FileManagerPrivateInternalGetRecentFilesFunction::OnGetRecentFiles(
continue;
file_manager::util::FileDefinition file_definition;
const bool result =
file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
chrome_details_.GetProfile(), extension_id(), file.url().path(),
&file_definition.virtual_path);
if (!result)
continue;
// Recent file system only lists regular files, not directories.
file_definition.is_directory = false;
file_definition_list.emplace_back(std::move(file_definition));
if (file_manager::util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
chrome_details_.GetProfile(), extension_id(), file.url().path(),
&file_definition.virtual_path)) {
file_definition_list.emplace_back(std::move(file_definition));
}
}
file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
......
......@@ -272,11 +272,13 @@ class FileManagerPrivateIsCrostiniEnabledFunction
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivate.isCrostiniEnabled",
FILEMANAGERPRIVATE_ISCROSTINIENABLED)
FileManagerPrivateIsCrostiniEnabledFunction() = default;
protected:
~FileManagerPrivateIsCrostiniEnabledFunction() override = default;
ResponseAction Run() override;
DISALLOW_COPY_AND_ASSIGN(FileManagerPrivateIsCrostiniEnabledFunction);
};
// Implements the chrome.fileManagerPrivate.mountCrostini method.
......@@ -297,6 +299,7 @@ class FileManagerPrivateMountCrostiniFunction
private:
std::string source_path_;
std::string mount_label_;
DISALLOW_COPY_AND_ASSIGN(FileManagerPrivateMountCrostiniFunction);
};
// Implements the chrome.fileManagerPrivate.sharePathWithCrostini
......@@ -306,6 +309,7 @@ class FileManagerPrivateInternalSharePathWithCrostiniFunction
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivateInternal.sharePathWithCrostini",
FILEMANAGERPRIVATEINTERNAL_SHAREPATHWITHCROSTINI)
FileManagerPrivateInternalSharePathWithCrostiniFunction() = default;
protected:
~FileManagerPrivateInternalSharePathWithCrostiniFunction() override = default;
......@@ -313,6 +317,31 @@ class FileManagerPrivateInternalSharePathWithCrostiniFunction
private:
ResponseAction Run() override;
void SharePathCallback(bool success, std::string failure_reason);
DISALLOW_COPY_AND_ASSIGN(
FileManagerPrivateInternalSharePathWithCrostiniFunction);
};
// Implements the chrome.fileManagerPrivate.getCrostiniSharedPaths
// method. Returns list of file entries.
class FileManagerPrivateInternalGetCrostiniSharedPathsFunction
: public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION(
"fileManagerPrivateInternal.getCrostiniSharedPaths",
FILEMANAGERPRIVATEINTERNAL_GETCROSTINISHAREDPATHS)
FileManagerPrivateInternalGetCrostiniSharedPathsFunction() = default;
protected:
~FileManagerPrivateInternalGetCrostiniSharedPathsFunction() override =
default;
private:
ResponseAction Run() override;
void OnConvertFileDefinitionListToEntryDefinitionList(
std::unique_ptr<file_manager::util::EntryDefinitionList>
entry_definition_list);
DISALLOW_COPY_AND_ASSIGN(
FileManagerPrivateInternalGetCrostiniSharedPathsFunction);
};
// Implements the chrome.fileManagerPrivate.installLinuxPackage method.
......@@ -322,6 +351,7 @@ class FileManagerPrivateInternalInstallLinuxPackageFunction
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivateInternal.installLinuxPackage",
FILEMANAGERPRIVATEINTERNAL_INSTALLLINUXPACKAGE)
FileManagerPrivateInternalInstallLinuxPackageFunction() = default;
protected:
~FileManagerPrivateInternalInstallLinuxPackageFunction() override = default;
......@@ -330,6 +360,8 @@ class FileManagerPrivateInternalInstallLinuxPackageFunction
ResponseAction Run() override;
void OnInstallLinuxPackage(crostini::ConciergeClientResult result,
const std::string& failure_reason);
DISALLOW_COPY_AND_ASSIGN(
FileManagerPrivateInternalInstallLinuxPackageFunction);
};
// Implements the chrome.fileManagerPrivate.getCustomActions method.
......
......@@ -579,9 +579,11 @@ WRAPPED_INSTANTIATE_TEST_CASE_P(
TestCase("showToggleHiddenAndroidFoldersGearMenuItemsInMyFiles"),
TestCase("enableToggleHiddenAndroidFoldersShowsHiddenFiles")));
WRAPPED_INSTANTIATE_TEST_CASE_P(Crostini, /* crostini.js */
FilesAppBrowserTest,
::testing::Values(TestCase("mountCrostini")));
WRAPPED_INSTANTIATE_TEST_CASE_P(
Crostini, /* crostini.js */
FilesAppBrowserTest,
::testing::Values(TestCase("mountCrostini"),
TestCase("sharePathWithCrostini")));
WRAPPED_INSTANTIATE_TEST_CASE_P(
MyFiles, /* my_files.js */
......
......@@ -24,6 +24,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/file_manager/file_manager_test_util.h"
#include "chrome/browser/chromeos/file_manager/mount_test_util.h"
......@@ -40,6 +41,7 @@
#include "chromeos/chromeos_switches.h"
#include "chromeos/components/drivefs/drivefs_host.h"
#include "chromeos/components/drivefs/fake_drivefs.h"
#include "chromeos/dbus/concierge/service.pb.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cros_disks_client.h"
#include "components/drive/chromeos/file_system_interface.h"
......@@ -977,6 +979,7 @@ void FileManagerBrowserTestBase::SetUpCommandLine(
if (!IsGuestModeTest()) {
enabled_features.emplace_back(features::kCrostini);
enabled_features.emplace_back(features::kExperimentalCrostiniUI);
command_line->AppendSwitch(chromeos::switches::kCrostiniFiles);
}
if (IsDriveFsTest()) {
enabled_features.emplace_back(chromeos::features::kDriveFs);
......@@ -1347,7 +1350,13 @@ base::FilePath FileManagerBrowserTestBase::MaybeMountCrostini(
if (source_url.scheme() != "sshfs") {
return {};
}
// Mount crostini volume, and set VM now running for CrostiniManager.
CHECK(crostini_volume_->Mount(profile()));
crostini::CrostiniManager* crostini_manager =
crostini::CrostiniManager::GetForProfile(profile()->GetOriginalProfile());
vm_tools::concierge::VmInfo vm_info;
crostini_manager->AddRunningVmForTesting(kCrostiniDefaultVmName,
std::move(vm_info));
return crostini_volume_->mount_path();
}
......
......@@ -736,6 +736,10 @@ callback GetDirectorySizeCallback = void(double size);
// |entries| Recently modified entries.
callback GetRecentFilesCallback = void([instanceOf=Entry] object[] entries);
// |entries| Entries shared with crostini container.
callback GetCrostiniSharedPathsCallback =
void([instanceOf = Entry] object[] entries);
// |status| Result of starting the install
// |failure_reason| Reason for failure for a 'failed' status
callback InstallLinuxPackageCallback = void(
......@@ -1115,6 +1119,10 @@ interface Functions {
static void sharePathWithCrostini(
[instanceof=DirectoryEntry] object entry, SimpleCallback callback);
// Returns list of paths shared with crostini container.
[nocompile]
static void getCrostiniSharedPaths(GetCrostiniSharedPathsCallback callback);
// Starts installation of a Linux package.
[nocompile]
static void installLinuxPackage([instanceof=Entry] object entry,
......
......@@ -32,6 +32,7 @@ namespace fileManagerPrivateInternal {
callback ValidatePathNameLengthCallback = void(boolean result);
callback GetDirectorySizeCallback = void(double size);
callback GetRecentFilesCallback = void(EntryDescription[] entries);
callback GetCrostiniSharedPathsCallback = void(EntryDescription[] entries);
callback InstallLinuxPackageCallback =
void(fileManagerPrivate.InstallLinuxPackageResponse response,
optional DOMString failure_reason);
......@@ -102,6 +103,8 @@ namespace fileManagerPrivateInternal {
GetRecentFilesCallback callback);
static void sharePathWithCrostini(DOMString url,
SimpleCallback callback);
static void getCrostiniSharedPaths(
GetCrostiniSharedPathsCallback callback);
static void installLinuxPackage(DOMString url,
InstallLinuxPackageCallback callback);
static void getThumbnail(DOMString url,
......
......@@ -224,6 +224,16 @@ binding.registerCustomHook(function(bindingsAPI) {
url, callback);
});
apiFunctions.setHandleRequest(
'getCrostiniSharedPaths', function(callback) {
fileManagerPrivateInternal.getCrostiniSharedPaths(
function(entryDescriptions) {
callback(entryDescriptions.map(function(description) {
return GetExternalFileEntry(description);
}));
});
});
apiFunctions.setHandleRequest('installLinuxPackage', function(
entry, callback) {
var url = fileManagerPrivateNatives.GetEntryURL(entry);
......
......@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This api testing extension's ID. Files referenced as Entry will
// have this as part of their URL.
const TEST_EXTENSION_ID = 'pkplfbidichfdicaijlchgnapepdginl';
/**
* Get specified entry.
* @param {string} volumeType volume type for entry.
......@@ -47,4 +52,18 @@ chrome.test.runTests([
'Share with Linux only allowed for directories within Downloads.'));
});
},
function testGetCrostiniSharedPaths() {
const urlPrefix = 'filesystem:chrome-extension://' + TEST_EXTENSION_ID +
'/external/Downloads-user';
chrome.fileManagerPrivate.getCrostiniSharedPaths(
chrome.test.callbackPass((entries) => {
chrome.test.assertEq(2, entries.length);
chrome.test.assertEq(urlPrefix + '/shared1', entries[0].toURL());
chrome.test.assertTrue(entries[0].isDirectory);
chrome.test.assertEq('/shared1', entries[0].fullPath);
chrome.test.assertEq(urlPrefix + '/shared2', entries[1].toURL());
chrome.test.assertTrue(entries[1].isDirectory);
chrome.test.assertEq('/shared2', entries[1].fullPath);
}));
}
]);
......@@ -1339,6 +1339,7 @@ enum HistogramValue {
WEBVIEWINTERNAL_SETSPATIALNAVIGATIONENABLED = 1276,
WEBVIEWINTERNAL_ISSPATIALNAVIGATIONENABLED = 1277,
FILEMANAGERPRIVATEINTERNAL_GETTHUMBNAIL = 1278,
FILEMANAGERPRIVATEINTERNAL_GETCROSTINISHAREDPATHS = 1279,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
......
......@@ -944,6 +944,12 @@ chrome.fileManagerPrivate.mountCrostini = function(callback) {};
chrome.fileManagerPrivate.sharePathWithCrostini = function(
entry, callback) {};
/**
* Returns list of paths shared with the crostini container.
* @param {function(!Array<!Entry>)} callback
*/
chrome.fileManagerPrivate.getCrostiniSharedPaths = function(callback) {};
/**
* Begin installation of a Linux package.
* @param {!Entry} entry
......
......@@ -16764,6 +16764,7 @@ Called by update_net_error_codes.py.-->
<int value="1276" label="WEBVIEWINTERNAL_SETSPATIALNAVIGATIONENABLED"/>
<int value="1277" label="WEBVIEWINTERNAL_ISSPATIALNAVIGATIONENABLED"/>
<int value="1278" label="FILEMANAGERPRIVATEINTERNAL_GETTHUMBNAIL"/>
<int value="1279" label="FILEMANAGERPRIVATEINTERNAL_GETCROSTINISHAREDPATHS"/>
</enum>
<enum name="ExtensionIconState">
......@@ -13,20 +13,37 @@ const Crostini = {};
Crostini.SHARED_PATHS_ = {};
/**
* Add entry as a shared path.
* Registers an entry as a shared path.
* @param {!Entry} entry
* @param {!VolumeManager} volumeManager
*/
Crostini.addSharedPath = function(entry, volumeManager) {
const root = volumeManager.getLocationInfo(entry).rootType;
let paths = Crostini.SHARED_PATHS_[root];
Crostini.registerSharedPath = function(entry, volumeManager) {
const info = volumeManager.getLocationInfo(entry);
if (!info)
return;
let paths = Crostini.SHARED_PATHS_[info.rootType];
if (!paths) {
paths = {};
Crostini.SHARED_PATHS_[root] = paths;
Crostini.SHARED_PATHS_[info.rootType] = paths;
}
paths[entry.fullPath] = true;
};
/**
* Unregisters entry as a shared path.
* @param {!Entry} entry
* @param {!VolumeManager} volumeManager
*/
Crostini.unregisterSharedPath = function(entry, volumeManager) {
const info = volumeManager.getLocationInfo(entry);
if (!info)
return;
const paths = Crostini.SHARED_PATHS_[info.rootType];
if (paths) {
delete paths[entry.fullPath];
}
};
/**
* Returns true if entry is shared.
* @param {!Entry} entry
......
......@@ -18,12 +18,15 @@ function testIsPathShared() {
assertFalse(Crostini.isPathShared(foo1, volumeManager));
Crostini.addSharedPath(foo1, volumeManager);
Crostini.registerSharedPath(foo1, volumeManager);
assertFalse(Crostini.isPathShared(root, volumeManager));
assertTrue(Crostini.isPathShared(foo1, volumeManager));
assertTrue(Crostini.isPathShared(foobar1, volumeManager));
Crostini.addSharedPath(foobar2, volumeManager);
Crostini.registerSharedPath(foobar2, volumeManager);
assertFalse(Crostini.isPathShared(foo2, volumeManager));
assertTrue(Crostini.isPathShared(foobar2, volumeManager));
Crostini.unregisterSharedPath(foobar2, volumeManager);
assertFalse(Crostini.isPathShared(foobar2, volumeManager));
}
......@@ -1229,7 +1229,19 @@ FileManager.prototype = /** @struct */ {
str('LINUX_FILES_ROOT_LABEL'),
VolumeManagerCommon.RootType.CROSTINI, true)) :
null;
// Redraw the tree even if not enabled. This is required for testing.
this.directoryTree.redraw(false);
if (!enabled)
return;
// Load any existing shared paths.
chrome.fileManagerPrivate.getCrostiniSharedPaths((entries) => {
for (let i = 0; i < entries.length; i++) {
Crostini.registerSharedPath(entries[i], assert(this.volumeManager_));
}
});
});
};
......
......@@ -1664,7 +1664,7 @@ CommandHandler.COMMANDS_['share-with-linux'] = /** @type {Command} */ ({
'Error sharing with linux: ' +
chrome.runtime.lastError.message);
} else {
Crostini.addSharedPath(dir, assert(fileManager.volumeManager));
Crostini.registerSharedPath(dir, fileManager.volumeManager);
}
});
}
......
......@@ -74,6 +74,7 @@ js_library("check_select") {
js_library("crostini") {
deps = [
"../foreground/js:crostini",
"js:test_util",
"//ui/webui/resources/js:webui_resource_test",
]
......
......@@ -255,16 +255,15 @@ crostini.testSharePathCrostiniSuccess = (done) => {
.then(() => {
// Right-click 'photos' directory.
// Check 'Share with Linux' is shown in menu.
test.selectFile('photos');
assertTrue(
test.fakeMouseRightClick('#file-list li[selected]'),
test.fakeMouseRightClick('#file-list [file-name="photos"]'),
'right-click photos');
return test.waitForElement(
'#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"]:not([hidden]):not([disabled])');
})
.then(() => {
// Click on 'Start with Linux'.
// Click on 'Share with Linux'.
assertTrue(
test.fakeMouseClick(
'#file-context-menu [command="#share-with-linux"]'),
......@@ -280,8 +279,9 @@ crostini.testSharePathCrostiniSuccess = (done) => {
// Verify right-click menu with 'Share with Linux' is not shown for:
// * Files (not directory)
// * Any folder already shared
// * Root Downloads folder
// * Any folder outside of downloads (e.g. crostini or drive)
// * Any folder outside of downloads (e.g. crostini or orive)
crostini.testSharePathNotShown = (done) => {
const myFiles = '#directory-tree .tree-item [root-type-icon="my_files"]';
const downloads = '#file-list li [file-type-icon="downloads"]';
......@@ -289,14 +289,27 @@ crostini.testSharePathNotShown = (done) => {
const googleDrive = '#directory-tree .tree-item [volume-type-icon="drive"]';
const menuNoShareWithLinux = '#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"][hidden][disabled="disabled"]';
let alreadySharedPhotosDir;
test.setupAndWaitUntilReady()
.then(() => {
// Right-click 'hello.txt' file.
// Check 'Share with Linux' is not shown in menu.
test.selectFile('hello.txt');
assertTrue(
test.fakeMouseRightClick('#file-list li[selected]'),
test.fakeMouseRightClick('#file-list [file-name="hello.txt"]'),
'right-click hello.txt');
return test.waitForElement(menuNoShareWithLinux);
})
.then(() => {
// Set a folder as already shared.
alreadySharedPhotosDir =
mockVolumeManager
.getCurrentProfileVolumeInfo(
VolumeManagerCommon.VolumeType.DOWNLOADS)
.fileSystem.entries['/photos'];
Crostini.registerSharedPath(alreadySharedPhotosDir, mockVolumeManager);
assertTrue(
test.fakeMouseRightClick('#file-list [file-name="photos"]'),
'right-click hello.txt');
return test.waitForElement(menuNoShareWithLinux);
})
......@@ -323,9 +336,8 @@ crostini.testSharePathNotShown = (done) => {
})
.then(() => {
// Check 'Share with Linux' is not shown in menu.
test.selectFile('A');
assertTrue(
test.fakeMouseRightClick('#file-list li[selected]'),
test.fakeMouseRightClick('#file-list [file-name="A"]'),
'right-click directory A');
return test.waitForElement(menuNoShareWithLinux);
})
......@@ -340,9 +352,8 @@ crostini.testSharePathNotShown = (done) => {
})
.then(() => {
// Check 'Share with Linux' is not shown in menu.
test.selectFile('photos');
assertTrue(
test.fakeMouseRightClick('#file-list li[selected]'),
test.fakeMouseRightClick('#file-list [file-name="photos"]'),
'right-click photos');
return test.waitForElement(menuNoShareWithLinux);
})
......@@ -353,6 +364,9 @@ crostini.testSharePathNotShown = (done) => {
'#directory-tree .tree-item [root-type-icon="crostini"]');
})
.then(() => {
// Clear Crostini shared folders.
Crostini.unregisterSharedPath(
alreadySharedPhotosDir, mockVolumeManager);
done();
});
};
......@@ -89,6 +89,10 @@ chrome.fileManagerPrivate = {
}
setTimeout(callback, 0, results);
},
getCrostiniSharedPaths: (callback) => {
// Returns Entry[].
setTimeout(callback, 0, []);
},
getPreferences: (callback) => {
setTimeout(callback, 0, chrome.fileManagerPrivate.preferences_);
},
......
......@@ -9,6 +9,9 @@ loadTimeData.data = $GRDP;
// Extend with additional fields not found in grdp files.
Object.setPrototypeOf(loadTimeData.data_, {
'CHROMEOS_RELEASE_BOARD': 'unknown',
'GOOGLE_DRIVE_REDEEM_URL': 'http://www.google.com/intl/en/chrome/devices' +
'/goodies.html?utm_source=filesapp&utm_medium=banner&utm_campaign=gsg',
'HIDE_SPACE_INFO': false,
'UI_LOCALE': 'en_US',
'language': 'en-US',
......
......@@ -5,8 +5,8 @@
'use strict';
testcase.mountCrostini = function() {
const fake = '#directory-tree .tree-item [root-type-icon="crostini"]';
const real = '#directory-tree .tree-item [volume-type-icon="crostini"]';
const fakeLinuxFiles = '#directory-tree [root-type-icon="crostini"]';
const realLinxuFiles = '#directory-tree [volume-type-icon="crostini"]';
let appId;
StepsRunner.run([
......@@ -14,28 +14,86 @@ testcase.mountCrostini = function() {
setupAndWaitUntilReady(
null, RootPath.DOWNLOADS, this.next, [ENTRIES.hello], []);
},
// Add entries to crostini volume, but do not mount.
function(results) {
// Add entries to crostini volume, but do not mount.
appId = results.windowId;
addEntries(['crostini'], BASIC_CROSTINI_ENTRY_SET, this.next);
},
// Linux files fake root is shown.
function() {
// Linux files fake root is shown.
remoteCall.waitForElement(appId, fake).then(this.next);
remoteCall.waitForElement(appId, fakeLinuxFiles).then(this.next);
},
// Mount crostini, and ensure real root and files are shown.
function() {
// Mount crostini, and ensure real root and files are shown.
remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fake]);
remoteCall.waitForElement(appId, real).then(this.next);
remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fakeLinuxFiles]);
remoteCall.waitForElement(appId, realLinxuFiles).then(this.next);
},
function() {
const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
remoteCall.waitForFiles(appId, files).then(this.next);
},
// Unmount and ensure fake root is shown.
function() {
// Unmount and ensure fake root is shown.
remoteCall.callRemoteTestUtil('unmount', null, ['crostini']);
remoteCall.waitForElement(appId, fake).then(this.next);
remoteCall.waitForElement(appId, fakeLinuxFiles).then(this.next);
},
function() {
checkIfNoErrorsOccured(this.next);
},
]);
};
testcase.sharePathWithCrostini = function() {
const fakeLinuxFiles = '#directory-tree [root-type-icon="crostini"]';
const realLinuxFiles = '#directory-tree [volume-type-icon="crostini"]';
const downloads = '#directory-tree [volume-type-icon="downloads"]';
const photos = '#file-list [file-name="photos"]';
const menuShareWithLinux = '#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"]:not([hidden]):not([disabled])';
const menuNoShareWithLinux = '#file-context-menu:not([hidden]) ' +
'[command="#share-with-linux"][hidden][disabled="disabled"]';
let appId;
StepsRunner.run([
function() {
setupAndWaitUntilReady(
null, RootPath.DOWNLOADS, this.next, [ENTRIES.photos], []);
},
// Ensure fake Linux files root is shown.
function(results) {
appId = results.windowId;
remoteCall.waitForElement(appId, fakeLinuxFiles).then(this.next);
},
// Mount crostini, and ensure real root is shown.
function() {
remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fakeLinuxFiles]);
remoteCall.waitForElement(appId, realLinuxFiles).then(this.next);
},
// Go back to downloads, wait for photos dir to be shown.
function(results) {
remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [downloads]);
remoteCall.waitForElement(appId, photos).then(this.next);
},
// Right-click 'photos' directory, ensure 'Share with Linux' is shown.
function(results) {
remoteCall.callRemoteTestUtil('fakeMouseRightClick', appId, [photos]);
remoteCall.waitForElement(appId, menuShareWithLinux).then(this.next);
},
// Click on 'Share with Linux', ensure menu is closed.
function() {
remoteCall.callRemoteTestUtil(
'fakeMouseClick', appId,
['#file-context-menu [command="#share-with-linux"]'], this.next);
remoteCall.waitForElement(appId, '#file-context-menu[hidden]')
.then(this.next);
},
// Right-click 'photos' directory, ensure 'Share with Linux' is not shown.
function() {
remoteCall.callRemoteTestUtil(
'fakeMouseRightClick', appId, ['#file-list [file-name="photos"'],
this.next);
remoteCall.waitForElement(appId, menuNoShareWithLinux).then(this.next);
},
function() {
checkIfNoErrorsOccured(this.next);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment