Bug 1173390 - Remove the majority of the old directory picker implementation to prepare for the new implementation under bug 1164310. r=baku

This commit is contained in:
Jonathan Watt
2015-06-06 12:41:47 +01:00
parent f03b5d4ff5
commit 794cbe4f93
3 changed files with 0 additions and 458 deletions

View File

@@ -327,167 +327,6 @@ UploadLastDir::ContentPrefCallback::HandleError(nsresult error)
namespace {
/**
* This enumerator returns File objects after wrapping a single
* nsIFile representing a directory. It enumerates the files under that
* directory and its subdirectories as a flat list of files, ignoring/skipping
* over symbolic links.
*
* The enumeration involves I/O, so this class must NOT be used on the main
* thread or else the main thread could be blocked for a very long time.
*
* This enumerator does not walk the directory tree breadth-first, but it also
* is not guaranteed to walk it depth-first either (since it uses
* nsIFile::GetDirectoryEntries, which is not guaranteed to group a directory's
* subdirectories at the beginning of the list that it returns).
*/
class DirPickerRecursiveFileEnumerator final
: public nsISimpleEnumerator
{
~DirPickerRecursiveFileEnumerator() {}
public:
NS_DECL_ISUPPORTS
explicit DirPickerRecursiveFileEnumerator(nsIFile* aTopDir)
: mTopDir(aTopDir)
{
MOZ_ASSERT(!NS_IsMainThread(), "This class blocks on I/O!");
#ifdef DEBUG
{
bool isDir;
aTopDir->IsDirectory(&isDir);
MOZ_ASSERT(isDir);
}
#endif
if (NS_FAILED(aTopDir->GetParent(getter_AddRefs(mTopDirsParent)))) {
// This just means that the name of the picked directory won't be
// included in the File.path string.
mTopDirsParent = aTopDir;
}
nsCOMPtr<nsISimpleEnumerator> entries;
if (NS_SUCCEEDED(mTopDir->GetDirectoryEntries(getter_AddRefs(entries))) &&
entries) {
mDirEnumeratorStack.AppendElement(entries);
LookupAndCacheNext();
}
}
NS_IMETHOD
GetNext(nsISupports** aResult) override
{
MOZ_ASSERT(!NS_IsMainThread(),
"Walking the directory tree involves I/O, so using this "
"enumerator can block a thread for a long time!");
if (!mNextFile) {
return NS_ERROR_FAILURE;
}
// The parent for this object will be set on the main thread.
nsRefPtr<File> domFile = File::CreateFromFile(nullptr, mNextFile);
nsCString relDescriptor;
nsresult rv =
mNextFile->GetRelativeDescriptor(mTopDirsParent, relDescriptor);
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF8toUTF16 path(relDescriptor);
nsAutoString leafName;
mNextFile->GetLeafName(leafName);
MOZ_ASSERT(leafName.Length() <= path.Length());
int32_t length = path.Length() - leafName.Length();
MOZ_ASSERT(length >= 0);
if (length > 0) {
// Note that we leave the trailing "/" on the path.
BlobImplFile* blobImpl = static_cast<BlobImplFile*>(domFile->Impl());
MOZ_ASSERT(blobImpl);
blobImpl->SetPath(Substring(path, 0, uint32_t(length)));
}
nsCOMPtr<nsIDOMBlob> blob = domFile.get();
blob.forget(aResult);
LookupAndCacheNext();
return NS_OK;
}
NS_IMETHOD
HasMoreElements(bool* aResult) override
{
*aResult = !!mNextFile;
return NS_OK;
}
private:
void
LookupAndCacheNext()
{
for (;;) {
if (mDirEnumeratorStack.IsEmpty()) {
mNextFile = nullptr;
break;
}
nsISimpleEnumerator* currentDirEntries =
mDirEnumeratorStack.LastElement();
bool hasMore;
DebugOnly<nsresult> rv = currentDirEntries->HasMoreElements(&hasMore);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (!hasMore) {
mDirEnumeratorStack.RemoveElementAt(mDirEnumeratorStack.Length() - 1);
continue;
}
nsCOMPtr<nsISupports> entry;
rv = currentDirEntries->GetNext(getter_AddRefs(entry));
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
MOZ_ASSERT(file);
bool isLink, isSpecial;
file->IsSymlink(&isLink);
file->IsSpecial(&isSpecial);
if (isLink || isSpecial) {
continue;
}
bool isDir;
file->IsDirectory(&isDir);
if (isDir) {
nsCOMPtr<nsISimpleEnumerator> subDirEntries;
rv = file->GetDirectoryEntries(getter_AddRefs(subDirEntries));
MOZ_ASSERT(NS_SUCCEEDED(rv) && subDirEntries);
mDirEnumeratorStack.AppendElement(subDirEntries);
continue;
}
#ifdef DEBUG
{
bool isFile;
file->IsFile(&isFile);
MOZ_ASSERT(isFile);
}
#endif
mNextFile.swap(file);
return;
}
}
private:
nsCOMPtr<nsIFile> mTopDir;
nsCOMPtr<nsIFile> mTopDirsParent; // May be mTopDir if no parent
nsCOMPtr<nsIFile> mNextFile;
nsTArray<nsCOMPtr<nsISimpleEnumerator> > mDirEnumeratorStack;
};
NS_IMPL_ISUPPORTS(DirPickerRecursiveFileEnumerator, nsISimpleEnumerator)
/**
* This may return nullptr if aDomFile's implementation of
* File::mozFullPathInternal does not successfully return a non-empty
@@ -518,119 +357,6 @@ DOMFileToLocalFile(File* aDomFile)
} // anonymous namespace
class DirPickerFileListBuilderTask final
: public nsRunnable
{
public:
DirPickerFileListBuilderTask(HTMLInputElement* aInput, nsIFile* aTopDir)
: mPreviousFileListLength(0)
, mInput(aInput)
, mTopDir(aTopDir)
, mFileListLength(0)
, mCanceled(false)
{}
NS_IMETHOD Run() {
if (!NS_IsMainThread()) {
// Build up list of File objects on this dedicated thread:
nsCOMPtr<nsISimpleEnumerator> iter =
new DirPickerRecursiveFileEnumerator(mTopDir);
bool hasMore = true;
nsCOMPtr<nsISupports> tmp;
while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
iter->GetNext(getter_AddRefs(tmp));
nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(tmp);
MOZ_ASSERT(domBlob);
mFileList.AppendElement(static_cast<File*>(domBlob.get()));
mFileListLength = mFileList.Length();
if (mCanceled) {
MOZ_ASSERT(!mInput, "This is bad - how did this happen?");
// There's no point dispatching to the main thread (that doesn't
// guarantee that we'll be destroyed there).
return NS_OK;
}
}
return NS_DispatchToMainThread(this);
}
// Now back on the main thread, set the list on our HTMLInputElement:
if (mCanceled || mFileList.IsEmpty()) {
return NS_OK;
}
MOZ_ASSERT(mInput->mDirPickerFileListBuilderTask,
"But we aren't canceled!");
if (mInput->mProgressTimer) {
mInput->mProgressTimerIsActive = false;
mInput->mProgressTimer->Cancel();
}
mInput->MaybeDispatchProgressEvent(true); // Last progress event.
mInput->mDirPickerFileListBuilderTask = nullptr; // Now null out.
if (mCanceled) { // The last progress event may have canceled us
return NS_OK;
}
// Recreate File with the correct parent object.
nsCOMPtr<nsIGlobalObject> global = mInput->OwnerDoc()->GetScopeObject();
for (uint32_t i = 0; i < mFileList.Length(); ++i) {
MOZ_ASSERT(!mFileList[i]->GetParentObject());
mFileList[i] = File::Create(global, mFileList[i]->Impl());
MOZ_ASSERT(mFileList[i]);
}
// The text control frame (if there is one) isn't going to send a change
// event because it will think this is done by a script.
// So, we can safely send one by ourself.
mInput->SetFiles(mFileList, true);
nsresult rv =
nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
NS_LITERAL_STRING("change"), true,
false);
// Clear mInput to make sure that it can't lose its last strong ref off the
// main thread (which may happen if our dtor runs off the main thread)!
mInput = nullptr;
return rv;
}
void Cancel()
{
MOZ_ASSERT(NS_IsMainThread() && !mCanceled);
// Clear mInput to make sure that it can't lose its last strong ref off the
// main thread (which may happen if our dtor runs off the main thread)!
mInput = nullptr;
mCanceled = true;
}
uint32_t GetFileListLength() const
{
return mFileListLength;
}
/**
* The number of files added to the FileList at the time the last progress
* event was fired.
*
* This is only read/set by HTMLInputElement on the main thread. The reason
* that this member is stored here rather than on HTMLInputElement is so that
* we don't increase the size of HTMLInputElement for something that's rarely
* used.
*/
uint32_t mPreviousFileListLength;
private:
nsRefPtr<HTMLInputElement> mInput;
nsCOMPtr<nsIFile> mTopDir;
nsTArray<nsRefPtr<File>> mFileList;
// We access the list length on both threads, so we need the indirection of
// this atomic member to make the access thread safe:
mozilla::Atomic<uint32_t> mFileListLength;
mozilla::Atomic<bool> mCanceled;
};
NS_IMETHODIMP
HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
@@ -641,47 +367,9 @@ HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
return NS_OK;
}
mInput->CancelDirectoryPickerScanIfRunning();
int16_t mode;
mFilePicker->GetMode(&mode);
if (mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder)) {
// Directory picking is different, since we still need to do more I/O to
// build up the list of File objects. Since this may block for a
// long time, we need to build the list off on another dedicated thread to
// avoid blocking any other activities that the browser is carrying out.
// The user selected this directory, so we always save this dir, even if
// no files are found under it.
nsCOMPtr<nsIFile> pickedDir;
mFilePicker->GetFile(getter_AddRefs(pickedDir));
#ifdef DEBUG
{
bool isDir;
pickedDir->IsDirectory(&isDir);
MOZ_ASSERT(isDir);
}
#endif
HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
mInput->OwnerDoc(), pickedDir);
nsCOMPtr<nsIEventTarget> target
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
NS_ASSERTION(target, "Must have stream transport service");
mInput->StartProgressEventTimer();
// DirPickerFileListBuilderTask takes care of calling SetFiles() and
// dispatching the "change" event.
mInput->mDirPickerFileListBuilderTask =
new DirPickerFileListBuilderTask(mInput.get(), pickedDir.get());
return target->Dispatch(mInput->mDirPickerFileListBuilderTask,
NS_DISPATCH_NORMAL);
}
// Collect new selected filenames
nsTArray<nsRefPtr<File>> newFiles;
if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
@@ -1164,7 +852,6 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
, mCanShowInvalidUI(true)
, mHasRange(false)
, mIsDraggingRange(false)
, mProgressTimerIsActive(false)
, mNumberControlSpinnerIsSpinning(false)
, mNumberControlSpinnerSpinsUp(false)
, mPickerRunning(false)
@@ -1268,7 +955,6 @@ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement)
nsIImageLoadingContent,
imgIOnloadBlocker,
nsIDOMNSEditableElement,
nsITimerCallback,
nsIConstraintValidation)
NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
@@ -2785,58 +2471,6 @@ HTMLInputElement::GetFiles()
return mFileList;
}
void
HTMLInputElement::OpenDirectoryPicker(ErrorResult& aRv)
{
if (mType != NS_FORM_INPUT_FILE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
}
InitFilePicker(FILE_PICKER_DIRECTORY);
}
void
HTMLInputElement::CancelDirectoryPickerScanIfRunning()
{
if (!mDirPickerFileListBuilderTask) {
return;
}
if (mProgressTimer) {
mProgressTimerIsActive = false;
mProgressTimer->Cancel();
}
mDirPickerFileListBuilderTask->Cancel();
mDirPickerFileListBuilderTask = nullptr;
}
void
HTMLInputElement::StartProgressEventTimer()
{
if (!mProgressTimer) {
mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
}
if (mProgressTimer) {
mProgressTimerIsActive = true;
mProgressTimer->Cancel();
mProgressTimer->InitWithCallback(this, kProgressEventInterval,
nsITimer::TYPE_ONE_SHOT);
}
}
// nsITimerCallback's only method
NS_IMETHODIMP
HTMLInputElement::Notify(nsITimer* aTimer)
{
if (mProgressTimer == aTimer) {
mProgressTimerIsActive = false;
MaybeDispatchProgressEvent(false);
return NS_OK;
}
// Just in case some JS user wants to QI to nsITimerCallback and play with us...
NS_WARNING("Unexpected timer!");
return NS_ERROR_INVALID_POINTER;
}
/* static */ void
HTMLInputElement::HandleNumberControlSpin(void* aData)
{
@@ -2857,65 +2491,6 @@ HTMLInputElement::HandleNumberControlSpin(void* aData)
}
}
void
HTMLInputElement::MaybeDispatchProgressEvent(bool aFinalProgress)
{
nsRefPtr<HTMLInputElement> kungFuDeathGrip;
if (aFinalProgress && mProgressTimerIsActive) {
// mProgressTimer may hold the last reference to us, so take another strong
// ref to make sure we don't die under Cancel() and leave this method
// running on deleted memory.
kungFuDeathGrip = this;
mProgressTimerIsActive = false;
mProgressTimer->Cancel();
}
uint32_t fileListLength = mDirPickerFileListBuilderTask->GetFileListLength();
if (mProgressTimerIsActive ||
fileListLength == mDirPickerFileListBuilderTask->mPreviousFileListLength) {
return;
}
if (!aFinalProgress) {
StartProgressEventTimer();
}
mDirPickerFileListBuilderTask->mPreviousFileListLength = fileListLength;
DispatchProgressEvent(NS_LITERAL_STRING(PROGRESS_STR),
false,
mDirPickerFileListBuilderTask->mPreviousFileListLength,
0);
}
void
HTMLInputElement::DispatchProgressEvent(const nsAString& aType,
bool aLengthComputable,
uint64_t aLoaded, uint64_t aTotal)
{
NS_ASSERTION(!aType.IsEmpty(), "missing event type");
ProgressEventInit init;
init.mBubbles = false;
init.mCancelable = true; // XXXkhuey why?
init.mLengthComputable = aLengthComputable;
init.mLoaded = aLoaded;
init.mTotal = (aTotal == UINT64_MAX) ? 0 : aTotal;
nsRefPtr<ProgressEvent> event =
ProgressEvent::Constructor(this, aType, init);
event->SetTrusted(true);
bool doDefaultAction;
nsresult rv = DispatchEvent(event, &doDefaultAction);
if (NS_SUCCEEDED(rv) && !doDefaultAction) {
CancelDirectoryPickerScanIfRunning();
}
}
nsresult
HTMLInputElement::UpdateFileList()
{