Bug 1674428 - Part 2. Add method to collect files that is selected WebKitDirectory. r=dom-core,sefeng

When using Android 13 or later, there is no permission to enumerate
directories if on user storage. So `GetFilesHelper` and etc won't work on
Android.

We have to use content resolver APIs to enumerate files in directory.
So I have to add a method to collect files in FilePicker and return
DOM Files.

Differential Revision: https://phabricator.services.mozilla.com/D228761
This commit is contained in:
Makoto Kato
2025-03-07 12:44:07 +00:00
parent 3a1ce7ffd2
commit b441849346
10 changed files with 166 additions and 10 deletions

View File

@@ -576,6 +576,36 @@ HTMLInputElement::nsFilePickerShownCallback::Done(
if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
mInput->HasAttr(nsGkAtoms::webkitdirectory)) {
#ifdef MOZ_WIDGET_ANDROID
// Android 13 or later cannot enumerate files into user directory due to
// no permission. So we store file list into file picker.
FallibleTArray<RefPtr<BlobImpl>> filesInWebKitDirectory;
nsCOMPtr<nsISimpleEnumerator> iter;
if (NS_SUCCEEDED(
mFilePicker->GetDomFilesInWebKitDirectory(getter_AddRefs(iter))) &&
iter) {
nsCOMPtr<nsISupports> supports;
bool loop = true;
while (NS_SUCCEEDED(iter->HasMoreElements(&loop)) && loop) {
iter->GetNext(getter_AddRefs(supports));
if (supports) {
RefPtr<BlobImpl> file = static_cast<File*>(supports.get())->Impl();
MOZ_ASSERT(file);
if (!filesInWebKitDirectory.AppendElement(file, fallible)) {
return nsresult::NS_ERROR_OUT_OF_MEMORY;
}
}
}
}
if (!filesInWebKitDirectory.IsEmpty()) {
dispatchChangeEventCallback->Callback(NS_OK, filesInWebKitDirectory);
return NS_OK;
}
#endif
ErrorResult error;
GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
if (NS_WARN_IF(error.Failed())) {

View File

@@ -45,12 +45,13 @@ FilePickerParent::~FilePickerParent() = default;
// 2. The stream transport thread stat()s the file in Run() and then dispatches
// the same runnable on the main thread.
// 3. The main thread sends the results over IPC.
FilePickerParent::IORunnable::IORunnable(FilePickerParent* aFPParent,
nsTArray<nsCOMPtr<nsIFile>>&& aFiles,
bool aIsDirectory)
FilePickerParent::IORunnable::IORunnable(
FilePickerParent* aFPParent, nsTArray<nsCOMPtr<nsIFile>>&& aFiles,
nsTArray<RefPtr<BlobImpl>>&& aFilesInWebKitDirectory, bool aIsDirectory)
: mozilla::Runnable("dom::FilePickerParent::IORunnable"),
mFilePickerParent(aFPParent),
mFiles(std::move(aFiles)),
mFilesInWebKitDirectory(std::move(aFilesInWebKitDirectory)),
mIsDirectory(aIsDirectory) {
MOZ_ASSERT_IF(aIsDirectory, mFiles.Length() == 1);
}
@@ -73,7 +74,8 @@ FilePickerParent::IORunnable::Run() {
// results.
if (NS_IsMainThread()) {
if (mFilePickerParent) {
mFilePickerParent->SendFilesOrDirectories(mResults);
mFilePickerParent->SendFilesOrDirectories(mResults,
mFilesInWebKitDirectory);
}
return NS_OK;
}
@@ -128,7 +130,8 @@ FilePickerParent::IORunnable::Run() {
void FilePickerParent::IORunnable::Destroy() { mFilePickerParent = nullptr; }
void FilePickerParent::SendFilesOrDirectories(
const nsTArray<BlobImplOrString>& aData) {
const nsTArray<BlobImplOrString>& aData,
const nsTArray<RefPtr<BlobImpl>>& aFilesInWebKitDirectory) {
ContentParent* parent = BrowserParent::GetFrom(Manager())->Manager();
if (mMode == nsIFilePicker::modeGetFolder) {
@@ -146,8 +149,20 @@ void FilePickerParent::SendFilesOrDirectories(
fss->GrantAccessToContentProcess(parent->ChildID(),
aData[0].mDirectoryPath);
nsTArray<IPCBlob> ipcBlobs;
for (const auto& blob : aFilesInWebKitDirectory) {
IPCBlob ipcBlob;
nsresult rv = IPCBlobUtils::Serialize(blob, ipcBlob);
if (NS_WARN_IF(NS_FAILED(rv))) {
break;
}
ipcBlobs.AppendElement(ipcBlob);
}
InputDirectory input;
input.directoryPath() = aData[0].mDirectoryPath;
input.blobsInWebKitDirectory() = std::move(ipcBlobs);
Unused << Send__delete__(this, input, mResult);
return;
}
@@ -208,9 +223,32 @@ void FilePickerParent::Done(nsIFilePicker::ResultCode aResult) {
return;
}
nsTArray<RefPtr<BlobImpl>> blobsInWebKitDirectory;
#ifdef MOZ_WIDGET_ANDROID
if (mMode == nsIFilePicker::modeGetFolder) {
nsCOMPtr<nsISimpleEnumerator> iter;
if (NS_SUCCEEDED(
mFilePicker->GetDomFilesInWebKitDirectory(getter_AddRefs(iter)))) {
nsCOMPtr<nsISupports> supports;
bool loop = true;
while (NS_SUCCEEDED(iter->HasMoreElements(&loop)) && loop) {
iter->GetNext(getter_AddRefs(supports));
if (supports) {
RefPtr<BlobImpl> file = static_cast<File*>(supports.get())->Impl();
MOZ_ASSERT(file);
blobsInWebKitDirectory.AppendElement(file);
}
}
}
}
#endif
MOZ_ASSERT(!mRunnable);
mRunnable = new IORunnable(this, std::move(files),
mMode == nsIFilePicker::modeGetFolder);
mRunnable =
new IORunnable(this, std::move(files), std::move(blobsInWebKitDirectory),
mMode == nsIFilePicker::modeGetFolder);
// Dispatch to background thread to do I/O:
if (!mRunnable->Dispatch()) {

View File

@@ -43,7 +43,9 @@ class FilePickerParent : public PFilePickerParent {
enum { eBlobImpl, eDirectoryPath } mType;
};
void SendFilesOrDirectories(const nsTArray<BlobImplOrString>& aData);
void SendFilesOrDirectories(
const nsTArray<BlobImplOrString>& aData,
const nsTArray<RefPtr<BlobImpl>>& aFilesInWebKitDirectory);
mozilla::ipc::IPCResult RecvOpen(
const int16_t& aSelectedType, const bool& aAddToRecentDocs,
@@ -79,11 +81,14 @@ class FilePickerParent : public PFilePickerParent {
nsTArray<nsCOMPtr<nsIFile>> mFiles;
nsTArray<BlobImplOrString> mResults;
nsCOMPtr<nsIEventTarget> mEventTarget;
nsTArray<RefPtr<BlobImpl>> mFilesInWebKitDirectory;
bool mIsDirectory;
public:
IORunnable(FilePickerParent* aFPParent,
nsTArray<nsCOMPtr<nsIFile>>&& aFiles, bool aIsDirectory);
nsTArray<nsCOMPtr<nsIFile>>&& aFiles,
nsTArray<RefPtr<BlobImpl>>&& aFilesInWebKitDirectory,
bool aIsDirectory);
bool Dispatch();
NS_IMETHOD Run() override;

View File

@@ -18,7 +18,10 @@ namespace mozilla {
namespace dom {
struct InputBlobs { IPCBlob[] blobs; };
struct InputDirectory { nsString directoryPath; };
struct InputDirectory {
nsString directoryPath;
IPCBlob[] blobsInWebKitDirectory;
};
union MaybeInputData
{
InputBlobs;

View File

@@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
@@ -70,6 +72,7 @@ export var MockFilePicker = {
this.mode = null;
this.returnData = [];
this.returnValue = null;
this.returnDataForWebKitDirs = [];
this.showCallback = null;
this.afterOpenCallback = null;
this.shown = false;
@@ -132,6 +135,22 @@ export var MockFilePicker = {
this.internalFileData({ domDirectory: directory, nsIFile: file }),
];
this.pendingPromises = [];
this.returnDataForWebKitDirs = [];
if (AppConstants.platform === "android") {
for (const filename of ["/foo.txt", "/subdir/bar.txt"]) {
let fileInDir = lazy.FileUtils.File(aPath + filename);
this.window.File.createFromNsIFile(file, {
existenceCheck: false,
}).then(domFile => {
this.returnDataForWebKitDirs.push(
this.internalFileData({ nsIFile: fileInDir, domFile })
);
});
// promise might be added into pendinngPromise, but it causes
// InvalidStateError.
}
}
},
setFiles(files) {
@@ -243,6 +262,18 @@ MockFilePickerInstance.prototype = {
get domFileOrDirectoryEnumerator() {
return this.getFiles(true);
},
*getDomFilesInWebKitDirectory() {
for (let d of MockFilePicker.returnDataForWebKitDirs) {
yield d.domFile;
}
},
get domFilesInWebKitDirectory() {
if (AppConstants.platform !== "android") {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
return this.getDomFilesInWebKitDirectory();
},
open(aFilePickerShownCallback) {
MockFilePicker.showing = true;
Services.tm.dispatchToMainThread(() => {

View File

@@ -472,3 +472,8 @@ nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
retIter.forget(aValue);
return NS_OK;
}
NS_IMETHODIMP
nsBaseFilePicker::GetDomFilesInWebKitDirectory(nsISimpleEnumerator** aValue) {
return NS_ERROR_NOT_IMPLEMENTED;
}

View File

@@ -50,6 +50,8 @@ class nsBaseFilePicker : public nsIFilePicker {
NS_IMETHOD GetDomFileOrDirectory(nsISupports** aValue) override;
NS_IMETHOD GetDomFileOrDirectoryEnumerator(
nsISimpleEnumerator** aValue) override;
NS_IMETHOD GetDomFilesInWebKitDirectory(
nsISimpleEnumerator** aValue) override;
protected:
virtual ~nsBaseFilePicker();

View File

@@ -182,6 +182,25 @@ mozilla::ipc::IPCResult nsFilePickerProxy::Recv__delete__(
OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
element->SetAsDirectory() = directory;
const nsTArray<IPCBlob>& blobs =
aData.get_InputDirectory().blobsInWebKitDirectory();
for (const auto& blob : blobs) {
RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blob);
NS_ENSURE_TRUE(blobImpl, IPC_OK());
if (!blobImpl->IsFile()) {
return IPC_OK();
}
RefPtr<File> file = File::Create(inner->AsGlobal(), blobImpl);
if (NS_WARN_IF(!file)) {
return IPC_OK();
}
OwningFileOrDirectory* element = mFilesInWebKitDirectory.AppendElement();
element->SetAsFile() = file;
}
}
if (mCallback) {
@@ -283,3 +302,16 @@ nsresult nsFilePickerProxy::ResolveSpecialDirectory(
// even meaningful here, so we just accept anything.
return NS_OK;
}
NS_IMETHODIMP
nsFilePickerProxy::GetDomFilesInWebKitDirectory(
nsISimpleEnumerator** aDomfiles) {
#ifdef MOZ_WIDGET_ANDROID
RefPtr<SimpleEnumerator> enumerator =
new SimpleEnumerator(mFilesInWebKitDirectory);
enumerator.forget(aDomfiles);
return NS_OK;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

View File

@@ -53,6 +53,8 @@ class nsFilePickerProxy : public nsBaseFilePicker,
NS_IMETHOD GetDomFileOrDirectory(nsISupports** aValue) override;
NS_IMETHOD GetDomFileOrDirectoryEnumerator(
nsISimpleEnumerator** aValue) override;
NS_IMETHOD GetDomFilesInWebKitDirectory(
nsISimpleEnumerator** aValue) override;
NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
@@ -70,6 +72,7 @@ class nsFilePickerProxy : public nsBaseFilePicker,
nsTArray<mozilla::dom::OwningFileOrDirectory> mFilesOrDirectories;
nsCOMPtr<nsIFilePickerShownCallback> mCallback;
nsTArray<mozilla::dom::OwningFileOrDirectory> mFilesInWebKitDirectory;
int16_t mSelectedType;
nsString mFile;

View File

@@ -229,6 +229,13 @@ interface nsIFilePicker : nsISupports
* Not used by Firefox Desktop.
*/
attribute nsIFilePicker_CaptureTarget capture;
/**
* Get the enumerator for the DOM files that is selected by directory picker
*
* @return Returns the files/directories
*/
readonly attribute nsISimpleEnumerator domFilesInWebKitDirectory;
};
[scriptable, function, uuid(0d79adad-b244-49A5-9997-2a8cad93fc44)]