Bug 1258009: Move nsLocalFile::Launch back to the main thread on Win32, but pass SEE_MASK_ASYNCOK and a parent HWND; r=jimm a=ritu

This is as much a perf issue as it is a UX issue. We should be passing a HWND to
ShellExecuteEx because it can show UI, and that UI should have a proper
parent-child relationship with the Mozilla window. We should do that on the
main thread because of the GUI stuff. OTOH, we want the ShellExecuteEx call to
be a lightweight as possible, hence the SEE_MASK_ASYNCOK flag.

MozReview-Commit-ID: 7VLkWTRWPoe
This commit is contained in:
Aaron Klotz 2016-05-05 16:52:18 -06:00
parent 4d491cc957
commit 3300e98910

View File

@ -53,6 +53,12 @@
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "nsIWindowMediator.h"
#include "mozIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsIWidget.h"
#include "mozilla/WidgetUtils.h"
using namespace mozilla;
#define CHECK_mWorkingPath() \
@ -74,9 +80,36 @@ using namespace mozilla;
#define DRIVE_REMOTE 4
#endif
static HWND
GetMostRecentNavigatorHWND()
{
nsresult rv;
nsCOMPtr<nsIWindowMediator> winMediator(
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return nullptr;
}
nsCOMPtr<mozIDOMWindowProxy> navWin;
rv = winMediator->GetMostRecentWindow(MOZ_UTF16("navigator:browser"),
getter_AddRefs(navWin));
if (NS_FAILED(rv) || !navWin) {
return nullptr;
}
nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win);
if (!widget) {
return nullptr;
}
return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
}
/**
* A runnable to dispatch back to the main thread when
* AsyncLocalFileWinOperation completes.
* AsyncRevealOperation completes.
*/
class AsyncLocalFileWinDone : public nsRunnable
{
@ -107,37 +140,28 @@ private:
* A runnable to dispatch from the main thread when an async operation should
* be performed.
*/
class AsyncLocalFileWinOperation : public nsRunnable
class AsyncRevealOperation : public nsRunnable
{
public:
enum FileOp { RevealOp, LaunchOp };
AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation,
const nsAString& aResolvedPath) :
mOperation(aOperation),
mResolvedPath(aResolvedPath)
explicit AsyncRevealOperation(const nsAString& aResolvedPath)
: mResolvedPath(aResolvedPath)
{
}
NS_IMETHOD Run()
{
MOZ_ASSERT(!NS_IsMainThread(),
"AsyncLocalFileWinOperation should not be run on the main thread!");
"AsyncRevealOperation should not be run on the main thread!");
CoInitialize(nullptr);
switch (mOperation) {
case RevealOp: {
Reveal();
}
break;
case LaunchOp: {
Launch();
}
break;
bool doCoUninitialize = SUCCEEDED(
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
Reveal();
if (doCoUninitialize) {
CoUninitialize();
}
CoUninitialize();
// Send the result back to the main thread so that it can shutdown
// Send the result back to the main thread so that this thread can be
// cleanly shut down
nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
NS_DispatchToMainThread(resultrunnable);
return NS_OK;
@ -206,82 +230,6 @@ private:
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.hwnd = nullptr;
seinfo.lpVerb = nullptr;
seinfo.lpFile = mResolvedPath.get();
seinfo.lpParameters = nullptr;
seinfo.lpDirectory = nullptr;
seinfo.nShow = SW_SHOWNORMAL;
// Use the directory of the file we're launching as the working
// directory. That way if we have a self extracting EXE it won't
// suggest to extract to the install directory.
WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
if (PathRemoveFileSpecW(workingDirectory)) {
seinfo.lpDirectory = workingDirectory;
} else {
NS_WARNING("Could not set working directory for launched file.");
}
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.AssignLiteral(MOZ_UTF16("shell32.dll,OpenAs_RunDLL "));
shellArg.Append(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 operation that will be performed on the thread
AsyncLocalFileWinOperation::FileOp mOperation;
// Stores the path to perform the operation on
nsString mResolvedPath;
};
@ -3313,9 +3261,7 @@ nsLocalFile::Reveal()
return rv;
}
nsCOMPtr<nsIRunnable> runnable =
new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp,
mResolvedPath);
nsCOMPtr<nsIRunnable> runnable = new AsyncRevealOperation(mResolvedPath);
// After the dispatch, the result runnable will shut down the worker
// thread, so we can let it go.
@ -3335,21 +3281,74 @@ nsLocalFile::Launch()
return rv;
}
// 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;
// use the app registry name to launch a shell execute....
SHELLEXECUTEINFOW seinfo;
memset(&seinfo, 0, sizeof(seinfo));
seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
seinfo.fMask = SEE_MASK_ASYNCOK;
seinfo.hwnd = GetMostRecentNavigatorHWND();
seinfo.lpVerb = nullptr;
seinfo.lpFile = mResolvedPath.get();
seinfo.lpParameters = nullptr;
seinfo.lpDirectory = nullptr;
seinfo.nShow = SW_SHOWNORMAL;
// Use the directory of the file we're launching as the working
// directory. That way if we have a self extracting EXE it won't
// suggest to extract to the install directory.
WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
if (PathRemoveFileSpecW(workingDirectory)) {
seinfo.lpDirectory = workingDirectory;
} else {
NS_WARNING("Could not set working directory for launched file.");
}
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);
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.AssignLiteral(MOZ_UTF16("shell32.dll,OpenAs_RunDLL "));
shellArg.Append(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;
}