Add chrome.fileSystem.GetVolumeList().
This CL adds a method to list available volumes. It's available only to kiosk
apps running in kiosk session and to some whitelited component apps/extensions.
[email protected] # chrome_extensions.js reviewed at cr/90486340.
TEST=browser_tests: *KioskTest*GetVolumeList*,
*FileSystemApiTestForRequestFileSystem*GetVolumeList*
BUG=440674
Review URL: https://codereview.chromium.org/1029803004
Cr-Commit-Position: refs/heads/master@{#324040}
diff --git a/chrome/browser/chromeos/login/kiosk_browsertest.cc b/chrome/browser/chromeos/login/kiosk_browsertest.cc
index 890a1b7..b4f1aa887 100644
--- a/chrome/browser/chromeos/login/kiosk_browsertest.cc
+++ b/chrome/browser/chromeos/login/kiosk_browsertest.cc
@@ -111,6 +111,13 @@
// detail/bmbpicmpniaclbbpdkfglgipkkebnbjf
const char kTestLocalFsKioskApp[] = "bmbpicmpniaclbbpdkfglgipkkebnbjf";
+// An app to test local access to file systems via the
+// chrome.fileSystem.requestFileSystem API.
+// Webstore data json is in
+// chrome/test/data/chromeos/app_mode/webstore/inlineinstall/
+// detail/aaedpojejpghjkedenggihopfhfijcko
+const char kTestGetVolumeListKioskApp[] = "aaedpojejpghjkedenggihopfhfijcko";
+
// Fake usb stick mount path.
const char kFakeUsbMountPathUpdatePass[] =
"chromeos/app_mode/external_update/update_pass";
@@ -1252,6 +1259,17 @@
OobeScreenWaiter(OobeDisplay::SCREEN_ERROR_MESSAGE).Wait();
}
+// Verifies available volumes for kiosk apps in kiosk session.
+IN_PROC_BROWSER_TEST_F(KioskTest, GetVolumeList) {
+ set_test_app_id(kTestGetVolumeListKioskApp);
+ set_test_app_version("0.1");
+ set_test_crx_file(test_app_id() + ".crx");
+
+ extensions::ResultCatcher catcher;
+ StartAppLaunchFromLoginScreen(SimulateNetworkOnlineClosure());
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
// Verifies that an enterprise device does not auto-launch kiosk mode when cros
// settings are untrusted.
IN_PROC_BROWSER_TEST_F(KioskTest, NoEnterpriseAutoLaunchWhenUntrusted) {
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc
index 68c4cd70..38da52a 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_api.cc
@@ -5,12 +5,14 @@
#include "chrome/browser/extensions/api/file_system/file_system_api.h"
#include <set>
+#include <vector>
#include "apps/saved_files_service.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
@@ -350,6 +352,70 @@
break;
}
}
+
+ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile,
+ content::RenderViewHost* host)
+ : profile_(profile), host_(host) {
+ DCHECK(profile_);
+ DCHECK(host_);
+}
+
+ConsentProviderDelegate::~ConsentProviderDelegate() {
+}
+
+// static
+void ConsentProviderDelegate::SetAutoDialogButtonForTest(
+ ui::DialogButton button) {
+ g_auto_dialog_button_for_test = button;
+}
+
+void ConsentProviderDelegate::ShowDialog(
+ const extensions::Extension& extension,
+ base::WeakPtr<file_manager::Volume> volume,
+ bool writable,
+ const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
+ content::WebContents* const foreground_contents =
+ GetWebContentsForRenderViewHost(profile_, host_);
+ // If there is no web contents handle, then the method is most probably
+ // executed from a background page. Find an app window to host the dialog.
+ content::WebContents* const web_contents =
+ foreground_contents ? foreground_contents
+ : GetWebContentsForAppId(profile_, extension.id());
+ if (!web_contents) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
+ return;
+ }
+
+ // Short circuit the user consent dialog for tests. This is far from a pretty
+ // code design.
+ if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, g_auto_dialog_button_for_test /* result */));
+ return;
+ }
+
+ RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
+ writable, base::Bind(callback));
+}
+
+bool ConsentProviderDelegate::IsAutoLaunched(
+ const extensions::Extension& extension) {
+ chromeos::KioskAppManager::App app_info;
+ return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
+ app_info.was_auto_launched_with_zero_delay;
+}
+
+bool ConsentProviderDelegate::IsWhitelistedComponent(
+ const extensions::Extension& extension) {
+ for (const auto& whitelisted_id : kRequestFileSystemComponentWhitelist) {
+ if (extension.id().compare(whitelisted_id) == 0)
+ return true;
+ }
+ return false;
+}
+
#endif
} // namespace file_system_api
@@ -1135,9 +1201,14 @@
return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
}
+ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
+ NOTIMPLEMENTED();
+ return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
+}
#else
+
FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
- : chrome_details_(this), consent_provider_(this) {
+ : chrome_details_(this) {
}
FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {
@@ -1149,8 +1220,12 @@
EXTENSION_FUNCTION_VALIDATE(params);
// Only kiosk apps in kiosk sessions can use this API.
- // Additionally whitelisted component extensions and apps.
- if (!consent_provider_.IsGrantable(*extension()))
+ // Additionally it is enabled for whitelisted component extensions and apps.
+ file_system_api::ConsentProviderDelegate consent_provider_delegate(
+ chrome_details_.GetProfile(), render_view_host());
+ file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
+
+ if (!consent_provider.IsGrantable(*extension()))
return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
using file_manager::VolumeManager;
@@ -1187,68 +1262,13 @@
if (writable && (volume->is_read_only()))
return RespondNow(Error(kSecurityError));
- consent_provider_.RequestConsent(
+ consent_provider.RequestConsent(
*extension(), volume, writable,
base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
volume, writable));
return RespondLater();
}
-// static
-void FileSystemRequestFileSystemFunction::SetAutoDialogButtonForTest(
- ui::DialogButton button) {
- g_auto_dialog_button_for_test = button;
-}
-
-void FileSystemRequestFileSystemFunction::ShowDialog(
- const extensions::Extension& extension,
- base::WeakPtr<file_manager::Volume> volume,
- bool writable,
- const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
- content::WebContents* const foreground_contents =
- GetWebContentsForRenderViewHost(chrome_details_.GetProfile(),
- render_view_host());
- // If there is no web contents handle, then the method is most probably
- // executed from a background page. Find an app window to host the dialog.
- content::WebContents* const web_contents =
- foreground_contents ? foreground_contents
- : GetWebContentsForAppId(chrome_details_.GetProfile(),
- extension_id());
- if (!web_contents) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
- return;
- }
-
- // Short circuit the user consent dialog for tests. This is far from a pretty
- // code design.
- if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::Bind(callback, g_auto_dialog_button_for_test /* result */));
- return;
- }
-
- RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
- writable, base::Bind(callback));
-}
-
-bool FileSystemRequestFileSystemFunction::IsAutoLaunched(
- const extensions::Extension& extension) {
- chromeos::KioskAppManager::App app_info;
- return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
- app_info.was_auto_launched_with_zero_delay;
-}
-
-bool FileSystemRequestFileSystemFunction::IsWhitelistedComponent(
- const extensions::Extension& extension) {
- for (const auto& whitelisted_id : kRequestFileSystemComponentWhitelist) {
- if (extension.id().compare(whitelisted_id) == 0)
- return true;
- }
- return false;
-}
-
void FileSystemRequestFileSystemFunction::OnConsentReceived(
base::WeakPtr<file_manager::Volume> volume,
bool writable,
@@ -1351,6 +1371,44 @@
SetResult(dict);
SendResponse(true);
}
+
+FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
+ : chrome_details_(this) {
+}
+
+FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {
+}
+
+ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
+ // Only kiosk apps in kiosk sessions can use this API.
+ // Additionally it is enabled for whitelisted component extensions and apps.
+ file_system_api::ConsentProviderDelegate consent_provider_delegate(
+ chrome_details_.GetProfile(), render_view_host());
+ file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
+
+ if (!consent_provider.IsGrantable(*extension()))
+ return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
+
+ using file_manager::VolumeManager;
+ VolumeManager* const volume_manager =
+ VolumeManager::Get(chrome_details_.GetProfile());
+ DCHECK(volume_manager);
+
+ using extensions::api::file_system::Volume;
+ const auto& volume_list = volume_manager->GetVolumeList();
+ std::vector<linked_ptr<Volume>> result_volume_list;
+ // Convert volume_list to result_volume_list.
+ for (const auto& volume : volume_list) {
+ const linked_ptr<Volume> result_volume(new Volume);
+ result_volume->volume_id = volume->volume_id();
+ result_volume->writable = !volume->is_read_only();
+ result_volume_list.push_back(result_volume);
+ }
+
+ return RespondNow(
+ ArgumentList(extensions::api::file_system::GetVolumeList::Results::Create(
+ result_volume_list).Pass()));
+}
#endif
} // namespace extensions
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.h b/chrome/browser/extensions/api/file_system/file_system_api.h
index 2445c19..7ae7c386 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.h
+++ b/chrome/browser/extensions/api/file_system/file_system_api.h
@@ -48,8 +48,8 @@
#if defined(OS_CHROMEOS)
// Requests consent for the chrome.fileSystem.requestFileSystem() method.
// Interaction with UI and environmental checks (kiosk mode, whitelist) are
-// provided by a delegate: FileSystemRequestFileSystemFunction. For testing,
-// it is TestingConsentProviderDelegate.
+// provided by a delegate: ConsentProviderDelegate. For testing, it is
+// TestingConsentProviderDelegate.
class ConsentProvider {
public:
enum Consent { CONSENT_GRANTED, CONSENT_REJECTED, CONSENT_IMPOSSIBLE };
@@ -97,6 +97,35 @@
DISALLOW_COPY_AND_ASSIGN(ConsentProvider);
};
+
+// Handles interaction with user as well as environment checks (whitelists,
+// context of running extensions) for ConsentProvider.
+class ConsentProviderDelegate : public ConsentProvider::DelegateInterface {
+ public:
+ ConsentProviderDelegate(Profile* profile, content::RenderViewHost* host);
+ ~ConsentProviderDelegate();
+
+ private:
+ friend ScopedSkipRequestFileSystemDialog;
+
+ // Sets a fake result for the user consent dialog. If ui::DIALOG_BUTTON_NONE
+ // then disabled.
+ static void SetAutoDialogButtonForTest(ui::DialogButton button);
+
+ // ConsentProvider::DelegateInterface overrides:
+ void ShowDialog(const extensions::Extension& extension,
+ base::WeakPtr<file_manager::Volume> volume,
+ bool writable,
+ const file_system_api::ConsentProvider::ShowDialogCallback&
+ callback) override;
+ bool IsAutoLaunched(const extensions::Extension& extension) override;
+ bool IsWhitelistedComponent(const extensions::Extension& extension) override;
+
+ Profile* const profile_;
+ content::RenderViewHost* const host_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConsentProviderDelegate);
+};
#endif
} // namespace file_system_api
@@ -315,15 +344,26 @@
protected:
~FileSystemRequestFileSystemFunction() override {}
- // AsyncExtensionFunction overrides.
+ // UIThreadExtensionFunction overrides.
+ ExtensionFunction::ResponseAction Run() override;
+};
+
+// Stub for non Chrome OS operating systems.
+class FileSystemGetVolumeListFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("fileSystem.getVolumeList",
+ FILESYSTEM_GETVOLUMELIST);
+
+ protected:
+ ~FileSystemGetVolumeListFunction() override {}
+
+ // UIThreadExtensionFunction overrides.
ExtensionFunction::ResponseAction Run() override;
};
#else
// Requests a file system for the specified volume id.
-class FileSystemRequestFileSystemFunction
- : public UIThreadExtensionFunction,
- public file_system_api::ConsentProvider::DelegateInterface {
+class FileSystemRequestFileSystemFunction : public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("fileSystem.requestFileSystem",
FILESYSTEM_REQUESTFILESYSTEM)
@@ -332,25 +372,10 @@
protected:
~FileSystemRequestFileSystemFunction() override;
- // AsyncExtensionFunction overrides.
+ // UIThreadExtensionFunction overrides.
ExtensionFunction::ResponseAction Run() override;
private:
- friend ScopedSkipRequestFileSystemDialog;
-
- // Sets a fake result for the user consent dialog. If ui::DIALOG_BUTTON_NONE
- // then disabled.
- static void SetAutoDialogButtonForTest(ui::DialogButton button);
-
- // ConsentProvider::DelegateInterface overrides:
- void ShowDialog(const extensions::Extension& extension,
- base::WeakPtr<file_manager::Volume> volume,
- bool writable,
- const file_system_api::ConsentProvider::ShowDialogCallback&
- callback) override;
- bool IsAutoLaunched(const extensions::Extension& extension) override;
- bool IsWhitelistedComponent(const extensions::Extension& extension) override;
-
// Called when a user grants or rejects permissions for the file system
// access.
void OnConsentReceived(base::WeakPtr<file_manager::Volume> volume,
@@ -358,7 +383,23 @@
file_system_api::ConsentProvider::Consent result);
ChromeExtensionFunctionDetails chrome_details_;
- file_system_api::ConsentProvider consent_provider_;
+};
+
+// Requests a list of available volumes.
+class FileSystemGetVolumeListFunction : public UIThreadExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("fileSystem.getVolumeList",
+ FILESYSTEM_GETVOLUMELIST);
+ FileSystemGetVolumeListFunction();
+
+ protected:
+ ~FileSystemGetVolumeListFunction() override;
+
+ // UIThreadExtensionFunction overrides.
+ ExtensionFunction::ResponseAction Run() override;
+
+ private:
+ ChromeExtensionFunctionDetails chrome_details_;
};
#endif
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index f221c56..40c1991 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -44,10 +44,11 @@
class ScopedSkipRequestFileSystemDialog {
public:
explicit ScopedSkipRequestFileSystemDialog(ui::DialogButton button) {
- FileSystemRequestFileSystemFunction::SetAutoDialogButtonForTest(button);
+ file_system_api::ConsentProviderDelegate::SetAutoDialogButtonForTest(
+ button);
}
~ScopedSkipRequestFileSystemDialog() {
- FileSystemRequestFileSystemFunction::SetAutoDialogButtonForTest(
+ file_system_api::ConsentProviderDelegate::SetAutoDialogButtonForTest(
ui::DIALOG_BUTTON_NONE);
}
@@ -413,4 +414,17 @@
<< message_;
}
+IN_PROC_BROWSER_TEST_F(FileSystemApiTestForRequestFileSystem, GetVolumeList) {
+ EnterKioskSession();
+ ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/get_volume_list"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemApiTestForRequestFileSystem,
+ GetVolumeList_NotKioskSession) {
+ ASSERT_TRUE(RunPlatformAppTest(
+ "api_test/file_system/get_volume_list_not_kiosk_session"))
+ << message_;
+}
+
} // namespace extensions
diff --git a/chrome/common/extensions/api/file_system.idl b/chrome/common/extensions/api/file_system.idl
index a39dd01..ed4aa1a 100644
--- a/chrome/common/extensions/api/file_system.idl
+++ b/chrome/common/extensions/api/file_system.idl
@@ -83,7 +83,7 @@
boolean? acceptsMultiple;
};
- dictionary RequestFileSystemOptions {
+ [nodoc] dictionary RequestFileSystemOptions {
// The ID of the requested volume.
DOMString volumeId;
@@ -92,7 +92,14 @@
boolean? writable;
};
-// Change to an entry within a tracked directory.
+ // Represents a mounted volume, which can be accessed via <code>chrome.
+ // fileSystem.requestFileSystem</code>.
+ [nodoc] dictionary Volume {
+ DOMString volumeId;
+ boolean writable;
+ };
+
+ // Change to an entry within a tracked directory.
[nodoc] dictionary ChildChange {
[instanceOf=Entry] object entry;
ChildChangeType type;
@@ -124,7 +131,8 @@
[nodoc] callback GetObservedEntriesCallback = void (
[instanceOf=Entry] object[] entries);
[nodoc] callback RequestFileSystemCallback = void(
- [instanceOf=FileSystem] optional object fileSystem);
+ [instanceOf=FileSystem] object fileSystem);
+ [nodoc] callback GetVolumeListCallback = void(Volume[] volumes);
interface Functions {
// Get the display path of an Entry object. The display path is based on
@@ -175,6 +183,11 @@
[nodoc] static void requestFileSystem(RequestFileSystemOptions options,
RequestFileSystemCallback callback);
+ // Returns a list of volumes available for <code>requestFileSystem()</code>.
+ // The <code>"fileSystem": {"requestFileSystem"}</code> manifest permission
+ // is required. Available to kiosk apps running in the kiosk session only.
+ [nodoc] static void getVolumeList(GetVolumeListCallback callback);
+
// Observes a directory entry. Emits an event if the tracked directory is
// changed (including the list of files on it), or removed. If <code>
// recursive</code> is set to true, then also all accessible subdirectories
diff --git a/chrome/renderer/resources/extensions/file_system_custom_bindings.js b/chrome/renderer/resources/extensions/file_system_custom_bindings.js
index 5553ae93..62a9ad68 100644
--- a/chrome/renderer/resources/extensions/file_system_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_system_custom_bindings.js
@@ -94,6 +94,16 @@
[fileSystem]);
});
+ apiFunctions.setCustomCallback('getVolumeList',
+ function(name, request, callback, response) {
+ var volumeList = response || null;
+ sendRequest.safeCallbackApply(
+ 'fileSystem.getVolumeList',
+ request,
+ callback,
+ [volumeList]);
+ });
+
// TODO(benwells): Remove these deprecated versions of the functions.
fileSystem.getWritableFileEntry = function() {
console.log("chrome.fileSystem.getWritableFileEntry is deprecated");
diff --git a/chrome/test/data/chromeos/app_mode/get_volume_list/key.pem b/chrome/test/data/chromeos/app_mode/get_volume_list/key.pem
new file mode 100644
index 0000000..2fb7246
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/get_volume_list/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxDEYtIfEohWkM
+MFDQsQTYRfVNI7byeeceWdtShDMPJumwgsnLO7uGytFLotki2sbZFys+kvkppTYI
+RuyZw3Bk5lC5L85Wn5EeMTxFbdcBbauYcqHCNiD2WAbeBVp/PZjTvwIT7/cDxuUo
+QBL2n70IfmxdP5WDvX7Bb+YoFtiEZM5T2si10rXVfwChcMx6WDJfCakltAca+E29
+ZfzRZEvgiVKycwiWe/mVc4REQ7GR9B7B7CYAMYfKN6AYWRJ31UuGoPxSyEAbS5BO
+j054mnpVuoRt6DEElU24X99Mz2imx221yRykNeB2o1YVq/sbB/jtiINon4mx09IC
+hyH/TV+ZAgMBAAECggEAfaQkOOsZZJQoVAoFj9PPiFs9FRz/O1ve8974kbpXMa1/
+sU9fPOaK0cEkffR2+xEeg/i5K5LJVxBzI7SROx1CqZf4OTL/zuE17qMqDmtAZTca
+yviualBXW/pkBTLvYdSom7u1Ecj9FqUTAllWG8aIM3rkE9iHlhHn3gY24sQzqt9g
+Acs1eVk+mbHl0DbrRGIrd8+ruBbawG9z3rxEpNc+s9Y1EDQnB6WSA0X92aB1Ilny
+G048dhylfTksH6M5Di3fXRtmSDKi1jV4SV+zRJMDP986UpDV3UkkaeJ/B4SZdrkg
+g90skXmnwvBr4bXvdB7IenTMJbZrRxuMZ3h3dW5m8QKBgQDiUgV6C3XLTuAdu6T3
+UQh0oL6pZzqv7ZMr0i9JLWmT2/JrqTI6OpBFOXE9yllkG7bwfzV7WTcs5ybbXWHv
+TvS/VcDaOOeokerz8JZNXwqIaQO9HrvevMZHZQHQtJzNw2u4Q25vi58q4vB0smPj
+anFy3GfAvPPPSRvfc6rBawex3wKBgQDIRBa/ey+gPnGe34+pPWffmH5H5b1LKNjm
+ZJMXazJAASCvNTzkIrzGkDwhtXA+QRI+XuTy+oHPap0rBSsu9BWlVzy4mCp3eWQN
+yWWuIrWHvUYwnDIcFU42MpEntKWIOIfQgEKWHfLkAlCT6vHdYCUbbaOverYX0mNW
+Chx6CxHNhwKBgQDUVoAs8XOjPG2pd9Re9fgo9GfuKJw3U38xLhKPZbwYrdPUjvpB
+B5E0YaCNiLw14IrTOYbEJABQcM9UIVkxXbLjkWFPXPR8g+sc1C0wimsncN/BIITD
+hfnCIlKBrfMwWplGWH3UyfqcEi/oTTbKt6OZUJFHlABsCvvLuooKzpB5oQKBgDxH
+MkmkPGuRIAXf6I/aKb/FWI0ve1B6FP8T2qo7274kGMBj19YbFpL1qwPCZux2DZW0
+Xlk8SYIy5ueiAKN7WGCR53bwZifb49+6dN57GASpVc0f1n1ZdFcf1U0MNJ7R1R9O
+27vve8JhZ/t9xhsJ62FcGN6ioth8vOWS2YtqdYtVAoGBANFPCmGbukdqO67btQ+h
+mqfX+KrIo1RGttHwx7FBH2o8n4Za0o7LX86CMMDWQhp4DDNOIDpv4ypVUN0/gAJH
+EQ2Hsy2wjqcxfc/1DlDPShX9UB+0dLce10FjOsMchLAzTHB7COC4NRA7lY3iqhFx
+4AeBbnvdJAaigqw7ehkKuHht
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/chromeos/app_mode/get_volume_list/src/background.js b/chrome/test/data/chromeos/app_mode/get_volume_list/src/background.js
new file mode 100644
index 0000000..c9447e8c
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/get_volume_list/src/background.js
@@ -0,0 +1,15 @@
+// Copyright 2015 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.
+
+chrome.test.runTests([
+ function getVolumeList() {
+ chrome.fileSystem.getVolumeList(
+ chrome.test.callbackPass(function(volumeList) {
+ // Drive is not exposed in kiosk session.
+ chrome.test.assertEq(1, volumeList.length);
+ chrome.test.assertEq('downloads:Downloads', volumeList[0].volumeId);
+ chrome.test.assertTrue(volumeList[0].writable);
+ }));
+ }
+]);
diff --git a/chrome/test/data/chromeos/app_mode/get_volume_list/src/manifest.json b/chrome/test/data/chromeos/app_mode/get_volume_list/src/manifest.json
new file mode 100644
index 0000000..18879f8fb
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/get_volume_list/src/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "chrome.fileSystem.getVolumeList test for kiosk apps in the kiosk session",
+ "version": "0.1",
+ "description": "Tests available volumes to kiosk apps running in the kiosk session. Especially Drive must not be exposed.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "kiosk_only": true,
+ "kiosk_enabled": true,
+ "permissions": [
+ {
+ "fileSystem": ["requestFileSystem"]
+ }
+ ]
+}
diff --git a/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx
new file mode 100644
index 0000000..b6180d6
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx
Binary files differ
diff --git a/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx.mock-http-headers b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx.mock-http-headers
new file mode 100644
index 0000000..707bb1f
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/webstore/downloads/aaedpojejpghjkedenggihopfhfijcko.crx.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-chrome-extension
diff --git a/chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/aaedpojejpghjkedenggihopfhfijcko b/chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/aaedpojejpghjkedenggihopfhfijcko
new file mode 100644
index 0000000..3e5878d
--- /dev/null
+++ b/chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/aaedpojejpghjkedenggihopfhfijcko
@@ -0,0 +1,11 @@
+{
+ "id": "aaedpojejpghjkedenggihopfhfijcko",
+ "users": "1234",
+ "average_rating": 1.0,
+ "rating_count": 999,
+ "verified_site": "chrome.google.com",
+ "localized_name": "chrome.fileSystem.getVolumeList test for kiosk apps in the kiosk session",
+ "localized_description": "Tests available volumes to kiosk apps running in the kiosk session. Especially Drive must not be exposed.",
+ "icon_url": "webstore/inlineinstall/detail/app_1_green16x16.png",
+ "manifest": "{ \"name\": \"chrome.fileSystem.getVolumeList test for kiosk apps in the kiosk session\", \"version\": \"0.1\", \"description\": \"Tests available volumes to kiosk apps running in the kiosk session. Especially Drive must not be exposed.\", \"app\": { \"background\": { \"scripts\": [\"background.js\"] } }, \"kiosk_only\": true, \"kiosk_enabled\": true, \"permissions\": [ { \"fileSystem\": [\"requestFileSystem\"] } ] }"
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list/background.js b/chrome/test/data/extensions/api_test/file_system/get_volume_list/background.js
new file mode 100644
index 0000000..44c89ab0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list/background.js
@@ -0,0 +1,26 @@
+// Copyright 2015 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.
+
+chrome.test.runTests([
+ function getVolumeList() {
+ chrome.fileSystem.getVolumeList(
+ chrome.test.callbackPass(function(volumeList) {
+ // Drive is not available in a real kiosk session. Hhowever, this test
+ // runs in a normal session (with a user marked as kiosk user) since
+ // executing a real real kiosk session is tests is very complicated.
+ // Whether Drive is available in the real kiosk session is tested
+ // separetely in: chrome/browser/chromeos/login/kiosk_browsertest.cc.
+ chrome.test.assertEq(4, volumeList.length);
+ chrome.test.assertEq('downloads:Downloads', volumeList[0].volumeId);
+ chrome.test.assertTrue(volumeList[0].writable);
+ chrome.test.assertEq('drive:drive-user', volumeList[1].volumeId);
+ chrome.test.assertTrue(volumeList[1].writable);
+
+ chrome.test.assertEq('testing:read-only', volumeList[2].volumeId);
+ chrome.test.assertFalse(volumeList[2].writable);
+ chrome.test.assertEq('testing:writable', volumeList[3].volumeId);
+ chrome.test.assertTrue(volumeList[3].writable);
+ }));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list/manifest.json b/chrome/test/data/extensions/api_test/file_system/get_volume_list/manifest.json
new file mode 100644
index 0000000..af3d483
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "chrome.fileSystem.getVolumeList test",
+ "version": "0.1",
+ "description": "Test for getting a list of available volumes via chrome.fileSystem.getVolumeList.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "kiosk_only": true,
+ "kiosk_enabled": true,
+ "permissions": [
+ {
+ "fileSystem": ["requestFileSystem"]
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/background.js b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/background.js
new file mode 100644
index 0000000..dcca20f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/background.js
@@ -0,0 +1,14 @@
+// Copyright 2015 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.
+
+chrome.test.runTests([
+ function getVolumeList() {
+ chrome.fileSystem.getVolumeList(
+ chrome.test.callbackFail('Operation only supported for kiosk apps ' +
+ 'running in a kiosk session.',
+ function(volumeList) {
+ chrome.test.assertFalse(!!volumeList);
+ }));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/manifest.json b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/manifest.json
new file mode 100644
index 0000000..3a37053
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/get_volume_list_not_kiosk_session/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "chrome.fileSystem.getVolumeList not in kiosk session test",
+ "version": "0.1",
+ "description": "Test for getting a list of available volumes via chrome.fileSystem.getVolumeList when not running in the kiosk session.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {
+ "fileSystem": ["requestFileSystem"]
+ }
+ ]
+}
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 09707ce..3eb1b99 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1056,6 +1056,7 @@
LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS,
DATAREDUCTIONPROXY_CLEARDATASAVINGS,
BLUETOOTHPRIVATE_SETDISCOVERYFILTER,
+ FILESYSTEM_GETVOLUMELIST,
// Last entry: Add new entries above and ensure to update
// tools/metrics/histograms/histograms.xml.
ENUM_BOUNDARY
diff --git a/third_party/closure_compiler/externs/chrome_extensions.js b/third_party/closure_compiler/externs/chrome_extensions.js
index 713f7d1..40a89bf5 100644
--- a/third_party/closure_compiler/externs/chrome_extensions.js
+++ b/third_party/closure_compiler/externs/chrome_extensions.js
@@ -6994,6 +6994,21 @@
/**
+ * @see http://developer.chrome.com/apps/fileSystem.html#method-getVolumeList
+ * @constructor
+ */
+chrome.fileSystem.Volume = function() {};
+
+
+/** @type {string} */
+chrome.fileSystem.Volume.prototype.volumeId;
+
+
+/** @type {boolean} */
+chrome.fileSystem.Volume.prototype.writable;
+
+
+/**
* @param {!chrome.fileSystem.ChooseEntryOptions|
* function(Entry=, !Array.<!FileEntry>=)} optionsOrCallback The
* options for the file prompt or the callback.
@@ -7032,13 +7047,21 @@
/**
* @param {!chrome.fileSystem.RequestFileSystemOptions} options Options for the
* request.
- * @param {function(!FileSystem=)} callback A completion callback.
+ * @param {function(FileSystem)} callback A completion callback.
* @see http://developer.chrome.com/apps/fileSystem.html#method-requestFileSystem
*/
chrome.fileSystem.requestFileSystem = function(options, callback) {};
/**
+ * @param {function(Array<!chrome.fileSystem.Volume>)} callback A completion
+ * callback.
+ * @see http://developer.chrome.com/apps/fileSystem.html#method-getVolumeList
+ */
+chrome.fileSystem.getVolumeList = function(callback) {};
+
+
+/**
* @const
* @see https://developer.chrome.com/apps/syncFileSystem
*/
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index bd419fd..5d22fb2 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -50228,6 +50228,7 @@
<int value="995" label="LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS"/>
<int value="996" label="DATAREDUCTIONPROXY_CLEARDATASAVINGS"/>
<int value="997" label="BLUETOOTHPRIVATE_SETDISCOVERYFILTER"/>
+ <int value="998" label="FILESYSTEM_GETVOLUMELIST"/>
</enum>
<enum name="ExtensionInstallCause" type="int">