Bug 632556 - Run nsIFile::Reveal/Launch asynchronously. r=bsmedberg

This commit is contained in:
Brian R. Bondy 2012-02-26 16:46:38 -05:00
Родитель 3bc1ae7585
Коммит 079540f7c6
2 изменённых файлов: 240 добавлений и 109 удалений

Просмотреть файл

@ -155,7 +155,8 @@ interface nsILocalFile : nsIFile
*
* Ask the operating system to open the folder which contains
* this file or folder. This routine only works on platforms which
* support the ability to open a folder...
* support the ability to open a folder and is run async on Windows.
* This routine must be called on the main.
*/
void reveal();
@ -164,7 +165,9 @@ interface nsILocalFile : nsIFile
*
* Ask the operating system to attempt to open the file.
* this really just simulates "double clicking" the file on your platform.
* This routine only works on platforms which support this functionality.
* This routine only works on platforms which support this functionality
* and is run async on Windows. This routine must be called on the
* main thread.
*/
void launch();

Просмотреть файл

@ -82,6 +82,8 @@
#include "SpecialSystemDirectory.h"
#include "nsTraceRefcntImpl.h"
#include "nsXPCOMCIDInternal.h"
#include "nsThreadUtils.h"
using namespace mozilla;
@ -110,6 +112,199 @@ unsigned char *_mbsstr( const unsigned char *str,
#define DRIVE_REMOTE 4
#endif
/**
* A runnable to dispatch back to the main thread when
* AsyncLocalFileWinOperation completes.
*/
class AsyncLocalFileWinDone : public nsRunnable
{
public:
AsyncLocalFileWinDone() :
mWorkerThread(do_GetCurrentThread())
{
// Objects of this type must only be created on worker threads
MOZ_ASSERT(!NS_IsMainThread());
}
NS_IMETHOD Run() {
// This event shuts down the worker thread and so must be main thread.
MOZ_ASSERT(NS_IsMainThread());
// If we don't destroy the thread when we're done with it, it will hang
// around forever... and that is bad!
mWorkerThread->Shutdown();
return NS_OK;
}
private:
nsCOMPtr<nsIThread> mWorkerThread;
};
/**
* A runnable to dispatch from the main thread when an async operation should
* be performed.
*/
class AsyncLocalFileWinOperation : public nsRunnable
{
public:
enum FileOp { RevealOp, LaunchOp };
AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation,
const nsAString &aResolvedPath) :
mOperation(aOperation),
mResolvedPath(aResolvedPath)
{
}
NS_IMETHOD Run() {
NS_ASSERTION(!NS_IsMainThread(),
"AsyncLocalFileWinOperation should not be run on the main thread!");
switch(mOperation) {
case RevealOp: {
Reveal();
}
break;
case LaunchOp: {
Launch();
}
break;
}
// Send the result back to the main thread so that it can shutdown
nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
NS_DispatchToMainThread(resultrunnable);
return NS_OK;
}
private:
// Reveals the path in explorer.
nsresult Reveal()
{
DWORD attributes = GetFileAttributesW(mResolvedPath.get());
if (INVALID_FILE_ATTRIBUTES == attributes) {
return NS_ERROR_FILE_INVALID_PATH;
}
HRESULT hr;
if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
// We have a directory so we should open the directory itself.
ITEMIDLIST *dir = ILCreateFromPathW(mResolvedPath.get());
if (!dir) {
return NS_ERROR_FAILURE;
}
const ITEMIDLIST* selection[] = { dir };
UINT count = ArrayLength(selection);
//Perform the open of the directory.
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
CoTaskMemFree(dir);
} else {
PRInt32 len = mResolvedPath.Length();
// We don't currently handle UNC long paths of the form \\?\ anywhere so
// this should be fine.
if (len > MAX_PATH) {
return NS_ERROR_FILE_INVALID_PATH;
}
WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 };
wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH);
PathRemoveFileSpecW(parentDirectoryPath);
// We have a file so we should open the parent directory.
ITEMIDLIST *dir = ILCreateFromPathW(parentDirectoryPath);
if (!dir) {
return NS_ERROR_FAILURE;
}
// Set the item in the directory to select to the file we want to reveal.
ITEMIDLIST *item = ILCreateFromPathW(mResolvedPath.get());
if (!item) {
CoTaskMemFree(dir);
return NS_ERROR_FAILURE;
}
const ITEMIDLIST* selection[] = { item };
UINT count = ArrayLength(selection);
//Perform the selection of the file.
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
CoTaskMemFree(dir);
CoTaskMemFree(item);
}
return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
}
// Launches the default shell operation for the file path
nsresult Launch()
{
// use the app registry name to launch a shell execute....
SHELLEXECUTEINFOW seinfo;
memset(&seinfo, 0, sizeof(seinfo));
seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
seinfo.fMask = NULL;
seinfo.hwnd = NULL;
seinfo.lpVerb = NULL;
seinfo.lpFile = mResolvedPath.get();
seinfo.lpParameters = NULL;
seinfo.lpDirectory = NULL;
seinfo.nShow = SW_SHOWNORMAL;
if (ShellExecuteExW(&seinfo))
return NS_OK;
DWORD r = GetLastError();
// if the file has no association, we launch windows'
// "what do you want to do" dialog
if (r == SE_ERR_NOASSOC) {
nsAutoString shellArg;
shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") +
mResolvedPath);
seinfo.lpFile = L"RUNDLL32.EXE";
seinfo.lpParameters = shellArg.get();
if (ShellExecuteExW(&seinfo))
return NS_OK;
r = GetLastError();
}
if (r < 32) {
switch (r) {
case 0:
case SE_ERR_OOM:
return NS_ERROR_OUT_OF_MEMORY;
case ERROR_FILE_NOT_FOUND:
return NS_ERROR_FILE_NOT_FOUND;
case ERROR_PATH_NOT_FOUND:
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
case ERROR_BAD_FORMAT:
return NS_ERROR_FILE_CORRUPTED;
case SE_ERR_ACCESSDENIED:
return NS_ERROR_FILE_ACCESS_DENIED;
case SE_ERR_ASSOCINCOMPLETE:
case SE_ERR_NOASSOC:
return NS_ERROR_UNEXPECTED;
case SE_ERR_DDEBUSY:
case SE_ERR_DDEFAIL:
case SE_ERR_DDETIMEOUT:
return NS_ERROR_NOT_AVAILABLE;
case SE_ERR_DLLNOTFOUND:
return NS_ERROR_FAILURE;
case SE_ERR_SHARE:
return NS_ERROR_FILE_IS_LOCKED;
default:
return NS_ERROR_FILE_EXECUTION_FAILED;
}
}
return NS_OK;
}
// Stores the path to perform the operation on
nsString mResolvedPath;
// Stores the operation that will be performed on the thread
AsyncLocalFileWinOperation::FileOp mOperation;
};
class nsDriveEnumerator : public nsISimpleEnumerator
{
public:
@ -2796,129 +2991,62 @@ nsLocalFile::SetFileAttributesWin(PRUint32 aAttribs)
NS_IMETHODIMP
nsLocalFile::Reveal()
{
// make sure mResolvedPath is set
bool isDirectory = false;
nsresult rv = IsDirectory(&isDirectory);
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
return rv;
// This API should be main thread only
MOZ_ASSERT(NS_IsMainThread());
HRESULT hr;
if (isDirectory) {
// We have a directory so we should open the directory itself.
ITEMIDLIST *dir = ILCreateFromPathW(mResolvedPath.get());
if (!dir) {
return NS_ERROR_FAILURE;
// make sure mResolvedPath is set
nsresult rv = Resolve();
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
return rv;
}
const ITEMIDLIST* selection[] = { dir };
UINT count = ArrayLength(selection);
//Perform the open of the directory.
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
CoTaskMemFree(dir);
}
else {
// Obtain the parent path of the item we are revealing.
nsCOMPtr<nsIFile> parentDirectory;
rv = GetParent(getter_AddRefs(parentDirectory));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString parentDirectoryPath;
rv = parentDirectory->GetPath(parentDirectoryPath);
NS_ENSURE_SUCCESS(rv, rv);
// We have a file so we should open the parent directory.
ITEMIDLIST *dir = ILCreateFromPathW(parentDirectoryPath.get());
if (!dir) {
return NS_ERROR_FAILURE;
// To create a new thread, get the thread manager
nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
nsCOMPtr<nsIThread> mythread;
rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
if (NS_FAILED(rv)) {
return rv;
}
// Set the item in the directory to select to the file we want to reveal.
ITEMIDLIST *item = ILCreateFromPathW(mResolvedPath.get());
if (!item) {
CoTaskMemFree(dir);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIRunnable> runnable =
new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp,
mResolvedPath);
const ITEMIDLIST* selection[] = { item };
UINT count = ArrayLength(selection);
//Perform the selection of the file.
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
CoTaskMemFree(dir);
CoTaskMemFree(item);
}
if (SUCCEEDED(hr)) {
// After the dispatch, the result runnable will shut down the worker
// thread, so we can let it go.
mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
return NS_OK;
}
else {
return NS_ERROR_FAILURE;
}
}
NS_IMETHODIMP
nsLocalFile::Launch()
{
const nsString &path = mWorkingPath;
// This API should be main thread only
MOZ_ASSERT(NS_IsMainThread());
// use the app registry name to launch a shell execute....
SHELLEXECUTEINFOW seinfo;
memset(&seinfo, 0, sizeof(seinfo));
seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
seinfo.fMask = NULL;
seinfo.hwnd = NULL;
seinfo.lpVerb = NULL;
seinfo.lpFile = path.get();
seinfo.lpParameters = NULL;
seinfo.lpDirectory = NULL;
seinfo.nShow = SW_SHOWNORMAL;
// make sure mResolvedPath is set
nsresult rv = Resolve();
if (NS_FAILED(rv))
return rv;
if (ShellExecuteExW(&seinfo))
return NS_OK;
DWORD r = GetLastError();
// if the file has no association, we launch windows' "what do you want to do" dialog
if (r == SE_ERR_NOASSOC) {
nsAutoString shellArg;
shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") + path);
seinfo.lpFile = L"RUNDLL32.EXE";
seinfo.lpParameters = shellArg.get();
if (ShellExecuteExW(&seinfo))
return NS_OK;
r = GetLastError();
}
if (r < 32) {
switch (r) {
case 0:
case SE_ERR_OOM:
return NS_ERROR_OUT_OF_MEMORY;
case ERROR_FILE_NOT_FOUND:
return NS_ERROR_FILE_NOT_FOUND;
case ERROR_PATH_NOT_FOUND:
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
case ERROR_BAD_FORMAT:
return NS_ERROR_FILE_CORRUPTED;
case SE_ERR_ACCESSDENIED:
return NS_ERROR_FILE_ACCESS_DENIED;
case SE_ERR_ASSOCINCOMPLETE:
case SE_ERR_NOASSOC:
return NS_ERROR_UNEXPECTED;
case SE_ERR_DDEBUSY:
case SE_ERR_DDEFAIL:
case SE_ERR_DDETIMEOUT:
return NS_ERROR_NOT_AVAILABLE;
case SE_ERR_DLLNOTFOUND:
return NS_ERROR_FAILURE;
case SE_ERR_SHARE:
return NS_ERROR_FILE_IS_LOCKED;
default:
return NS_ERROR_FILE_EXECUTION_FAILED;
}
// To create a new thread, get the thread manager
nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
nsCOMPtr<nsIThread> mythread;
rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIRunnable> runnable =
new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp,
mResolvedPath);
// After the dispatch, the result runnable will shut down the worker
// thread, so we can let it go.
mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
return NS_OK;
}
nsresult
NS_NewLocalFile(const nsAString &path, bool followLinks, nsILocalFile* *result)
{