Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 4d218d0

Browse files
committed
Bug 1258009: Move nsLocalFile::Launch back to the main thread on Win32, but pass SEE_MASK_ASYNCOK and a parent HWND; r=jimm
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 --HG-- extra : rebase_source : ce16bc0c926a299d9b9103ad0697e3cd07b9157d
1 parent 3c76d5a commit 4d218d0

1 file changed

Lines changed: 112 additions & 113 deletions

File tree

xpcom/io/nsLocalFileWin.cpp

Lines changed: 112 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
#include "nsThreadUtils.h"
5454
#include "nsXULAppAPI.h"
5555

56+
#include "nsIWindowMediator.h"
57+
#include "mozIDOMWindow.h"
58+
#include "nsPIDOMWindow.h"
59+
#include "nsIWidget.h"
60+
#include "mozilla/WidgetUtils.h"
61+
5662
using namespace mozilla;
5763

5864
#define CHECK_mWorkingPath() \
@@ -74,9 +80,36 @@ using namespace mozilla;
7480
#define DRIVE_REMOTE 4
7581
#endif
7682

83+
static HWND
84+
GetMostRecentNavigatorHWND()
85+
{
86+
nsresult rv;
87+
nsCOMPtr<nsIWindowMediator> winMediator(
88+
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
89+
if (NS_FAILED(rv)) {
90+
return nullptr;
91+
}
92+
93+
nsCOMPtr<mozIDOMWindowProxy> navWin;
94+
rv = winMediator->GetMostRecentWindow(MOZ_UTF16("navigator:browser"),
95+
getter_AddRefs(navWin));
96+
if (NS_FAILED(rv) || !navWin) {
97+
return nullptr;
98+
}
99+
100+
nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
101+
nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win);
102+
if (!widget) {
103+
return nullptr;
104+
}
105+
106+
return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
107+
}
108+
109+
77110
/**
78111
* A runnable to dispatch back to the main thread when
79-
* AsyncLocalFileWinOperation completes.
112+
* AsyncRevealOperation completes.
80113
*/
81114
class AsyncLocalFileWinDone : public Runnable
82115
{
@@ -107,37 +140,28 @@ class AsyncLocalFileWinDone : public Runnable
107140
* A runnable to dispatch from the main thread when an async operation should
108141
* be performed.
109142
*/
110-
class AsyncLocalFileWinOperation : public Runnable
143+
class AsyncRevealOperation : public Runnable
111144
{
112145
public:
113-
enum FileOp { RevealOp, LaunchOp };
114-
115-
AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation,
116-
const nsAString& aResolvedPath) :
117-
mOperation(aOperation),
118-
mResolvedPath(aResolvedPath)
146+
explicit AsyncRevealOperation(const nsAString& aResolvedPath)
147+
: mResolvedPath(aResolvedPath)
119148
{
120149
}
121150

122151
NS_IMETHOD Run()
123152
{
124153
MOZ_ASSERT(!NS_IsMainThread(),
125-
"AsyncLocalFileWinOperation should not be run on the main thread!");
154+
"AsyncRevealOperation should not be run on the main thread!");
126155

127-
CoInitialize(nullptr);
128-
switch (mOperation) {
129-
case RevealOp: {
130-
Reveal();
131-
}
132-
break;
133-
case LaunchOp: {
134-
Launch();
135-
}
136-
break;
156+
bool doCoUninitialize = SUCCEEDED(
157+
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
158+
Reveal();
159+
if (doCoUninitialize) {
160+
CoUninitialize();
137161
}
138-
CoUninitialize();
139162

140-
// Send the result back to the main thread so that it can shutdown
163+
// Send the result back to the main thread so that this thread can be
164+
// cleanly shut down
141165
nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
142166
NS_DispatchToMainThread(resultrunnable);
143167
return NS_OK;
@@ -206,82 +230,6 @@ class AsyncLocalFileWinOperation : public Runnable
206230
return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
207231
}
208232

209-
// Launches the default shell operation for the file path
210-
nsresult Launch()
211-
{
212-
// use the app registry name to launch a shell execute....
213-
SHELLEXECUTEINFOW seinfo;
214-
memset(&seinfo, 0, sizeof(seinfo));
215-
seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
216-
seinfo.hwnd = nullptr;
217-
seinfo.lpVerb = nullptr;
218-
seinfo.lpFile = mResolvedPath.get();
219-
seinfo.lpParameters = nullptr;
220-
seinfo.lpDirectory = nullptr;
221-
seinfo.nShow = SW_SHOWNORMAL;
222-
223-
// Use the directory of the file we're launching as the working
224-
// directory. That way if we have a self extracting EXE it won't
225-
// suggest to extract to the install directory.
226-
WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
227-
wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
228-
if (PathRemoveFileSpecW(workingDirectory)) {
229-
seinfo.lpDirectory = workingDirectory;
230-
} else {
231-
NS_WARNING("Could not set working directory for launched file.");
232-
}
233-
234-
if (ShellExecuteExW(&seinfo)) {
235-
return NS_OK;
236-
}
237-
DWORD r = GetLastError();
238-
// if the file has no association, we launch windows'
239-
// "what do you want to do" dialog
240-
if (r == SE_ERR_NOASSOC) {
241-
nsAutoString shellArg;
242-
shellArg.AssignLiteral(MOZ_UTF16("shell32.dll,OpenAs_RunDLL "));
243-
shellArg.Append(mResolvedPath);
244-
seinfo.lpFile = L"RUNDLL32.EXE";
245-
seinfo.lpParameters = shellArg.get();
246-
if (ShellExecuteExW(&seinfo)) {
247-
return NS_OK;
248-
}
249-
r = GetLastError();
250-
}
251-
if (r < 32) {
252-
switch (r) {
253-
case 0:
254-
case SE_ERR_OOM:
255-
return NS_ERROR_OUT_OF_MEMORY;
256-
case ERROR_FILE_NOT_FOUND:
257-
return NS_ERROR_FILE_NOT_FOUND;
258-
case ERROR_PATH_NOT_FOUND:
259-
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
260-
case ERROR_BAD_FORMAT:
261-
return NS_ERROR_FILE_CORRUPTED;
262-
case SE_ERR_ACCESSDENIED:
263-
return NS_ERROR_FILE_ACCESS_DENIED;
264-
case SE_ERR_ASSOCINCOMPLETE:
265-
case SE_ERR_NOASSOC:
266-
return NS_ERROR_UNEXPECTED;
267-
case SE_ERR_DDEBUSY:
268-
case SE_ERR_DDEFAIL:
269-
case SE_ERR_DDETIMEOUT:
270-
return NS_ERROR_NOT_AVAILABLE;
271-
case SE_ERR_DLLNOTFOUND:
272-
return NS_ERROR_FAILURE;
273-
case SE_ERR_SHARE:
274-
return NS_ERROR_FILE_IS_LOCKED;
275-
default:
276-
return NS_ERROR_FILE_EXECUTION_FAILED;
277-
}
278-
}
279-
return NS_OK;
280-
}
281-
282-
// Stores the operation that will be performed on the thread
283-
AsyncLocalFileWinOperation::FileOp mOperation;
284-
285233
// Stores the path to perform the operation on
286234
nsString mResolvedPath;
287235
};
@@ -3313,9 +3261,7 @@ nsLocalFile::Reveal()
33133261
return rv;
33143262
}
33153263

3316-
nsCOMPtr<nsIRunnable> runnable =
3317-
new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp,
3318-
mResolvedPath);
3264+
nsCOMPtr<nsIRunnable> runnable = new AsyncRevealOperation(mResolvedPath);
33193265

33203266
// After the dispatch, the result runnable will shut down the worker
33213267
// thread, so we can let it go.
@@ -3335,21 +3281,74 @@ nsLocalFile::Launch()
33353281
return rv;
33363282
}
33373283

3338-
// To create a new thread, get the thread manager
3339-
nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
3340-
nsCOMPtr<nsIThread> mythread;
3341-
rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
3342-
if (NS_FAILED(rv)) {
3343-
return rv;
3284+
// use the app registry name to launch a shell execute....
3285+
SHELLEXECUTEINFOW seinfo;
3286+
memset(&seinfo, 0, sizeof(seinfo));
3287+
seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
3288+
seinfo.fMask = SEE_MASK_ASYNCOK;
3289+
seinfo.hwnd = GetMostRecentNavigatorHWND();
3290+
seinfo.lpVerb = nullptr;
3291+
seinfo.lpFile = mResolvedPath.get();
3292+
seinfo.lpParameters = nullptr;
3293+
seinfo.lpDirectory = nullptr;
3294+
seinfo.nShow = SW_SHOWNORMAL;
3295+
3296+
// Use the directory of the file we're launching as the working
3297+
// directory. That way if we have a self extracting EXE it won't
3298+
// suggest to extract to the install directory.
3299+
WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
3300+
wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
3301+
if (PathRemoveFileSpecW(workingDirectory)) {
3302+
seinfo.lpDirectory = workingDirectory;
3303+
} else {
3304+
NS_WARNING("Could not set working directory for launched file.");
33443305
}
33453306

3346-
nsCOMPtr<nsIRunnable> runnable =
3347-
new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp,
3348-
mResolvedPath);
3349-
3350-
// After the dispatch, the result runnable will shut down the worker
3351-
// thread, so we can let it go.
3352-
mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
3307+
if (ShellExecuteExW(&seinfo)) {
3308+
return NS_OK;
3309+
}
3310+
DWORD r = GetLastError();
3311+
// if the file has no association, we launch windows'
3312+
// "what do you want to do" dialog
3313+
if (r == SE_ERR_NOASSOC) {
3314+
nsAutoString shellArg;
3315+
shellArg.AssignLiteral(MOZ_UTF16("shell32.dll,OpenAs_RunDLL "));
3316+
shellArg.Append(mResolvedPath);
3317+
seinfo.lpFile = L"RUNDLL32.EXE";
3318+
seinfo.lpParameters = shellArg.get();
3319+
if (ShellExecuteExW(&seinfo)) {
3320+
return NS_OK;
3321+
}
3322+
r = GetLastError();
3323+
}
3324+
if (r < 32) {
3325+
switch (r) {
3326+
case 0:
3327+
case SE_ERR_OOM:
3328+
return NS_ERROR_OUT_OF_MEMORY;
3329+
case ERROR_FILE_NOT_FOUND:
3330+
return NS_ERROR_FILE_NOT_FOUND;
3331+
case ERROR_PATH_NOT_FOUND:
3332+
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
3333+
case ERROR_BAD_FORMAT:
3334+
return NS_ERROR_FILE_CORRUPTED;
3335+
case SE_ERR_ACCESSDENIED:
3336+
return NS_ERROR_FILE_ACCESS_DENIED;
3337+
case SE_ERR_ASSOCINCOMPLETE:
3338+
case SE_ERR_NOASSOC:
3339+
return NS_ERROR_UNEXPECTED;
3340+
case SE_ERR_DDEBUSY:
3341+
case SE_ERR_DDEFAIL:
3342+
case SE_ERR_DDETIMEOUT:
3343+
return NS_ERROR_NOT_AVAILABLE;
3344+
case SE_ERR_DLLNOTFOUND:
3345+
return NS_ERROR_FAILURE;
3346+
case SE_ERR_SHARE:
3347+
return NS_ERROR_FILE_IS_LOCKED;
3348+
default:
3349+
return NS_ERROR_FILE_EXECUTION_FAILED;
3350+
}
3351+
}
33533352
return NS_OK;
33543353
}
33553354

0 commit comments

Comments
 (0)