Bug 1588975 - Replace ShellExecuteExW with mozilla::ShellExecuteByExplorer. r=aklotz,asuth
authorToshihito Kikuchi <tkikuchi@mozilla.com>
Thu, 14 Nov 2019 19:47:29 +0000
changeset 502079 abcbfdf1a1efad1d852db7889ebda328c4e8b73d
parent 502078 36bef03d2b69c674ab63f0e0ea46819d456ab267
child 502080 9062e6ab980e537e00bcafd9ce16eb43d9a20366
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaklotz, asuth
bugs1588975, 1567614
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1588975 - Replace ShellExecuteExW with mozilla::ShellExecuteByExplorer. r=aklotz,asuth The launcher process turns on the `PreferSystem32Images` mitigation policy for the browser process. Since the mitigation policy is inherited, a process launched by the browser process also has `PreferSystem32Images`. If an application which does not support `PreferSystem32Images`, such as Skype for Business, is launched via a hyperlink, a custom uri, or a downloaded file, it would fail to launch. Bug 1567614 fixed this issue by introducing `mozilla::ShellExecuteByExplorer` to `nsMIMEInfoWin::LoadUriInternal`. This patch introduces `mozilla::ShellExecuteByExplorer` to two more places. 1. xul!nsLocalFile::Launch This is invoked when a user opens a file from the Download Library, or a user opens a downloaded file with the default application without saving it. 2. xul!nsMIMEInfoWin::LaunchWithFile This is invoked when a user opens a downloaded file with a custom application (configured in about:preference) without saving it. *Why does this patch change worker.js?* The mochitest dom/tests/browser/browser_test_new_window_from_content.js failed if it was executed after dom/serviceworkers/test/browser_download.js in the same batch. This was because browser_download.js launched Notepad to open fake_download.bin.txt, preventing a new window from being opened in the foreground in browser_test_new_window_from_content.js. The test browser_download.js can verify downloaded data without opening an associated application. So this patch adds the content-type to the response header in order not to open Notepad on Windows. Differential Revision: https://phabricator.services.mozilla.com/D52567
dom/serviceworkers/test/download/worker.js
uriloader/exthandler/win/nsMIMEInfoWin.cpp
widget/windows/ShellHeaderOnlyUtils.h
xpcom/io/nsLocalFileWin.cpp
--- a/dom/serviceworkers/test/download/worker.js
+++ b/dom/serviceworkers/test/download/worker.js
@@ -18,15 +18,17 @@ addEventListener("fetch", function(evt) 
   }
 
   // We should only get a single download fetch event. Automatically unregister.
   evt.respondWith(
     registration.unregister().then(function() {
       return new Response("service worker generated download", {
         headers: {
           "Content-Disposition": 'attachment; filename="fake_download.bin"',
+          // Prevent the default text editor from being launched
+          "Content-Type": "application/octet-stream",
           // fake encoding header that should have no effect
           "Content-Encoding": "gzip",
         },
       });
     })
   );
 });
--- a/uriloader/exthandler/win/nsMIMEInfoWin.cpp
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
@@ -35,16 +35,47 @@ nsresult nsMIMEInfoWin::LaunchDefaultWit
   // Launch the file, unless it is an executable.
   bool executable = true;
   aFile->IsExecutable(&executable);
   if (executable) return NS_ERROR_FAILURE;
 
   return aFile->Launch();
 }
 
+// Helper routine to call mozilla::ShellExecuteByExplorer
+static nsresult ShellExecuteWithIFile(const nsCOMPtr<nsIFile>& aExecutable,
+                                      const _variant_t& aArgs) {
+  nsresult rv;
+
+  nsAutoString execPath;
+  rv = aExecutable->GetTarget(execPath);
+  if (NS_FAILED(rv) || execPath.IsEmpty()) {
+    rv = aExecutable->GetPath(execPath);
+  }
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  _bstr_t execPathBStr(execPath.get());
+  _variant_t verb(L"open");
+  _variant_t workingDir;
+  _variant_t showCmd(SW_SHOWNORMAL);
+
+  // Ask Explorer to ShellExecute on our behalf, as some applications such as
+  // Skype for Business do not start correctly when inheriting our process's
+  // migitation policies.
+  mozilla::LauncherVoidResult shellExecuteOk = mozilla::ShellExecuteByExplorer(
+      execPathBStr, aArgs, verb, workingDir, showCmd);
+  if (shellExecuteOk.isErr()) {
+    return NS_ERROR_FILE_EXECUTION_FAILED;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsMIMEInfoWin::LaunchWithFile(nsIFile* aFile) {
   nsresult rv;
 
   // it doesn't make any sense to call this on protocol handlers
   NS_ASSERTION(mClass == eMIMEInfo,
                "nsMIMEInfoBase should have mClass == eMIMEInfo");
 
@@ -125,17 +156,17 @@ nsMIMEInfoWin::LaunchWithFile(nsIFile* a
                 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
               case ERROR_BAD_FORMAT:
                 return NS_ERROR_FILE_CORRUPTED;
             }
         }
         return NS_ERROR_FILE_EXECUTION_FAILED;
       }
     }
-    return LaunchWithIProcess(executable, path);
+    return ShellExecuteWithIFile(executable, _variant_t(path.get()));
   }
 
   return NS_ERROR_INVALID_ARG;
 }
 
 NS_IMETHODIMP
 nsMIMEInfoWin::GetHasDefaultHandler(bool* _retval) {
   // We have a default application if we have a description
--- a/widget/windows/ShellHeaderOnlyUtils.h
+++ b/widget/windows/ShellHeaderOnlyUtils.h
@@ -135,17 +135,17 @@ inline LauncherVoidResult ShellExecuteBy
   // shellapi.h macros interfere with the correct naming of the method being
   // called on IShellDispatch2. Temporarily remove that definition.
 #if defined(ShellExecute)
 #  define MOZ_REDEFINE_SHELLEXECUTE
 #  undef ShellExecute
 #endif  // defined(ShellExecute)
 
   // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to execute.
-  hr = shellDisp->ShellExecute(aPath, aArgs, aVerb, aWorkingDir, aShowCmd);
+  hr = shellDisp->ShellExecute(aPath, aArgs, aWorkingDir, aVerb, aShowCmd);
   if (FAILED(hr)) {
     return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
   // Restore the macro that was removed prior to IShellDispatch2::ShellExecute
 #if defined(MOZ_REDEFINE_SHELLEXECUTE)
 #  if defined(UNICODE)
 #    define ShellExecute ShellExecuteW
--- a/xpcom/io/nsLocalFileWin.cpp
+++ b/xpcom/io/nsLocalFileWin.cpp
@@ -52,16 +52,17 @@
 #include "nsXPCOMCIDInternal.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
 #include "nsIWindowMediator.h"
 #include "mozIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsIWidget.h"
+#include "mozilla/ShellHeaderOnlyUtils.h"
 #include "mozilla/WidgetUtils.h"
 
 using namespace mozilla;
 
 #define CHECK_mWorkingPath()                                     \
   do {                                                           \
     if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
   } while (0)
@@ -3036,45 +3037,55 @@ nsLocalFile::Launch() {
 
   // make sure mResolvedPath is set
   nsresult rv = Resolve();
   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;
+  _bstr_t execPath(mResolvedPath.get());
+
+  _variant_t args;
+  _variant_t verb(L"open");
+  _variant_t workingDir;
+  _variant_t showCmd(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;
+    workingDir = workingDirectory;
   } else {
     NS_WARNING("Could not set working directory for launched file.");
   }
 
-  if (ShellExecuteExW(&seinfo)) {
+  // Ask Explorer to ShellExecute on our behalf, as some applications such as
+  // Skype for Business do not start correctly when inheriting our process's
+  // migitation policies.
+  mozilla::LauncherVoidResult shellExecuteOk = mozilla::ShellExecuteByExplorer(
+      execPath, args, verb, workingDir, showCmd);
+  if (shellExecuteOk.isOk()) {
     return NS_OK;
   }
-  DWORD r = GetLastError();
+  DWORD r = shellExecuteOk.inspectErr().AsWin32Error().value();
   // if the file has no association, we launch windows'
   // "what do you want to do" dialog
   if (r == SE_ERR_NOASSOC) {
+    SHELLEXECUTEINFOW seinfo;
+    memset(&seinfo, 0, sizeof(seinfo));
+    seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+    seinfo.fMask = SEE_MASK_ASYNCOK;
+    seinfo.hwnd = GetMostRecentNavigatorHWND();
+    seinfo.lpVerb = nullptr;
+    seinfo.lpDirectory = workingDirectory;
+    seinfo.nShow = SW_SHOWNORMAL;
+
     nsAutoString shellArg;
     shellArg.AssignLiteral(u"shell32.dll,OpenAs_RunDLL ");
     shellArg.Append(mResolvedPath);
     seinfo.lpFile = L"RUNDLL32.EXE";
     seinfo.lpParameters = shellArg.get();
     if (ShellExecuteExW(&seinfo)) {
       return NS_OK;
     }