1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsWindowsShellService.h"
7 
8 #include "BinaryPath.h"
9 #include "imgIContainer.h"
10 #include "imgIRequest.h"
11 #include "mozilla/RefPtr.h"
12 #include "nsComponentManagerUtils.h"
13 #include "nsIContent.h"
14 #include "nsIImageLoadingContent.h"
15 #include "nsIOutputStream.h"
16 #include "nsIPrefService.h"
17 #include "nsIStringBundle.h"
18 #include "nsNetUtil.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsShellService.h"
21 #include "nsDirectoryServiceUtils.h"
22 #include "nsAppDirectoryServiceDefs.h"
23 #include "nsDirectoryServiceDefs.h"
24 #include "nsIWindowsRegKey.h"
25 #include "nsUnicharUtils.h"
26 #include "nsIURLFormatter.h"
27 #include "nsXULAppAPI.h"
28 #include "mozilla/WindowsVersion.h"
29 #include "mozilla/dom/Element.h"
30 #include "mozilla/dom/Promise.h"
31 #include "mozilla/ErrorResult.h"
32 #include "mozilla/gfx/2D.h"
33 #include "WindowsDefaultBrowser.h"
34 #include "WindowsUserChoice.h"
35 #include "nsLocalFile.h"
36 
37 #include <windows.h>
38 #include <shellapi.h>
39 #include <propvarutil.h>
40 #include <propkey.h>
41 
42 #ifdef __MINGW32__
43 // MinGW-w64 headers are missing PropVariantToString.
44 #  include <propsys.h>
45 PSSTDAPI PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);
46 #endif
47 
48 #include <objbase.h>
49 #include <shlobj.h>
50 #include <knownfolders.h>
51 #include "WinUtils.h"
52 #include "mozilla/widget/WinTaskbar.h"
53 
54 #include <mbstring.h>
55 
56 #undef ACCESS_READ
57 
58 #ifndef MAX_BUF
59 #  define MAX_BUF 4096
60 #endif
61 
62 #define REG_SUCCEEDED(val) (val == ERROR_SUCCESS)
63 
64 #define REG_FAILED(val) (val != ERROR_SUCCESS)
65 
66 #ifdef DEBUG
67 #  define NS_ENSURE_HRESULT(hres, ret)                    \
68     do {                                                  \
69       HRESULT result = hres;                              \
70       if (MOZ_UNLIKELY(FAILED(result))) {                 \
71         mozilla::SmprintfPointer msg = mozilla::Smprintf( \
72             "NS_ENSURE_HRESULT(%s, %s) failed with "      \
73             "result 0x%" PRIX32,                          \
74             #hres, #ret, static_cast<uint32_t>(result));  \
75         NS_WARNING(msg.get());                            \
76         return ret;                                       \
77       }                                                   \
78     } while (false)
79 #else
80 #  define NS_ENSURE_HRESULT(hres, ret) \
81     if (MOZ_UNLIKELY(FAILED(hres))) return ret
82 #endif
83 
84 using mozilla::IsWin8OrLater;
85 using namespace mozilla;
86 
NS_IMPL_ISUPPORTS(nsWindowsShellService,nsIToolkitShellService,nsIShellService,nsIWindowsShellService)87 NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIToolkitShellService,
88                   nsIShellService, nsIWindowsShellService)
89 
90 static nsresult OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName,
91                                   HKEY* aKey) {
92   const nsString& flatName = PromiseFlatString(aKeyName);
93 
94   DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey);
95   switch (res) {
96     case ERROR_SUCCESS:
97       break;
98     case ERROR_ACCESS_DENIED:
99       return NS_ERROR_FILE_ACCESS_DENIED;
100     case ERROR_FILE_NOT_FOUND:
101       return NS_ERROR_NOT_AVAILABLE;
102   }
103 
104   return NS_OK;
105 }
106 
GetHelperPath(nsAutoString & aPath)107 nsresult GetHelperPath(nsAutoString& aPath) {
108   nsresult rv;
109   nsCOMPtr<nsIProperties> directoryService =
110       do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
111   NS_ENSURE_SUCCESS(rv, rv);
112 
113   nsCOMPtr<nsIFile> appHelper;
114   rv = directoryService->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
115                              getter_AddRefs(appHelper));
116   NS_ENSURE_SUCCESS(rv, rv);
117 
118   rv = appHelper->SetNativeLeafName("uninstall"_ns);
119   NS_ENSURE_SUCCESS(rv, rv);
120 
121   rv = appHelper->AppendNative("helper.exe"_ns);
122   NS_ENSURE_SUCCESS(rv, rv);
123 
124   rv = appHelper->GetPath(aPath);
125 
126   aPath.Insert(L'"', 0);
127   aPath.Append(L'"');
128   return rv;
129 }
130 
LaunchHelper(nsAutoString & aPath)131 nsresult LaunchHelper(nsAutoString& aPath) {
132   STARTUPINFOW si = {sizeof(si), 0};
133   PROCESS_INFORMATION pi = {0};
134 
135   if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE, 0,
136                       nullptr, nullptr, &si, &pi)) {
137     return NS_ERROR_FAILURE;
138   }
139 
140   CloseHandle(pi.hProcess);
141   CloseHandle(pi.hThread);
142   return NS_OK;
143 }
144 
IsPathDefaultForClass(const RefPtr<IApplicationAssociationRegistration> & pAAR,wchar_t * exePath,LPCWSTR aClassName)145 static bool IsPathDefaultForClass(
146     const RefPtr<IApplicationAssociationRegistration>& pAAR, wchar_t* exePath,
147     LPCWSTR aClassName) {
148   LPWSTR registeredApp;
149   bool isProtocol = *aClassName != L'.';
150   ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION;
151   HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE,
152                                          &registeredApp);
153   if (FAILED(hr)) {
154     return false;
155   }
156 
157   nsAutoString regAppName(registeredApp);
158   CoTaskMemFree(registeredApp);
159 
160   // Make sure the application path for this progID is this installation.
161   regAppName.AppendLiteral("\\shell\\open\\command");
162   HKEY theKey;
163   nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, regAppName, &theKey);
164   if (NS_FAILED(rv)) {
165     return false;
166   }
167 
168   wchar_t cmdFromReg[MAX_BUF] = L"";
169   DWORD len = sizeof(cmdFromReg);
170   DWORD res = ::RegQueryValueExW(theKey, nullptr, nullptr, nullptr,
171                                  (LPBYTE)cmdFromReg, &len);
172   ::RegCloseKey(theKey);
173   if (REG_FAILED(res)) {
174     return false;
175   }
176 
177   nsAutoString pathFromReg(cmdFromReg);
178   nsLocalFile::CleanupCmdHandlerPath(pathFromReg);
179 
180   return _wcsicmp(exePath, pathFromReg.Data()) == 0;
181 }
182 
183 NS_IMETHODIMP
IsDefaultBrowser(bool aForAllTypes,bool * aIsDefaultBrowser)184 nsWindowsShellService::IsDefaultBrowser(bool aForAllTypes,
185                                         bool* aIsDefaultBrowser) {
186   *aIsDefaultBrowser = false;
187 
188   RefPtr<IApplicationAssociationRegistration> pAAR;
189   HRESULT hr = CoCreateInstance(
190       CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC,
191       IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR));
192   if (FAILED(hr)) {
193     return NS_OK;
194   }
195 
196   wchar_t exePath[MAXPATHLEN] = L"";
197   nsresult rv = BinaryPath::GetLong(exePath);
198 
199   if (NS_FAILED(rv)) {
200     return NS_OK;
201   }
202 
203   *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L"http");
204   if (*aIsDefaultBrowser && aForAllTypes) {
205     *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L".html");
206   }
207   return NS_OK;
208 }
209 
210 NS_IMETHODIMP
IsDefaultHandlerFor(const nsAString & aFileExtensionOrProtocol,bool * aIsDefaultHandlerFor)211 nsWindowsShellService::IsDefaultHandlerFor(
212     const nsAString& aFileExtensionOrProtocol, bool* aIsDefaultHandlerFor) {
213   *aIsDefaultHandlerFor = false;
214 
215   RefPtr<IApplicationAssociationRegistration> pAAR;
216   HRESULT hr = CoCreateInstance(
217       CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC,
218       IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR));
219   if (FAILED(hr)) {
220     return NS_OK;
221   }
222 
223   wchar_t exePath[MAXPATHLEN] = L"";
224   nsresult rv = BinaryPath::GetLong(exePath);
225 
226   if (NS_FAILED(rv)) {
227     return NS_OK;
228   }
229 
230   const nsString& flatClass = PromiseFlatString(aFileExtensionOrProtocol);
231 
232   *aIsDefaultHandlerFor = IsPathDefaultForClass(pAAR, exePath, flatClass.get());
233   return NS_OK;
234 }
235 
LaunchControlPanelDefaultsSelectionUI()236 nsresult nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI() {
237   IApplicationAssociationRegistrationUI* pAARUI;
238   HRESULT hr = CoCreateInstance(
239       CLSID_ApplicationAssociationRegistrationUI, NULL, CLSCTX_INPROC,
240       IID_IApplicationAssociationRegistrationUI, (void**)&pAARUI);
241   if (SUCCEEDED(hr)) {
242     mozilla::UniquePtr<wchar_t[]> appRegName;
243     GetAppRegName(appRegName);
244     hr = pAARUI->LaunchAdvancedAssociationUI(appRegName.get());
245     pAARUI->Release();
246   }
247   return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
248 }
249 
LaunchControlPanelDefaultPrograms()250 nsresult nsWindowsShellService::LaunchControlPanelDefaultPrograms() {
251   return ::LaunchControlPanelDefaultPrograms() ? NS_OK : NS_ERROR_FAILURE;
252 }
253 
254 NS_IMETHODIMP
CheckAllProgIDsExist(bool * aResult)255 nsWindowsShellService::CheckAllProgIDsExist(bool* aResult) {
256   *aResult = false;
257   nsAutoString aumid;
258   if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) {
259     return NS_OK;
260   }
261   *aResult =
262       CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) &&
263       CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get());
264   return NS_OK;
265 }
266 
267 NS_IMETHODIMP
CheckBrowserUserChoiceHashes(bool * aResult)268 nsWindowsShellService::CheckBrowserUserChoiceHashes(bool* aResult) {
269   *aResult = ::CheckBrowserUserChoiceHashes();
270   return NS_OK;
271 }
272 
273 NS_IMETHODIMP
CanSetDefaultBrowserUserChoice(bool * aResult)274 nsWindowsShellService::CanSetDefaultBrowserUserChoice(bool* aResult) {
275   *aResult = false;
276 // If the WDBA is not available, this could never succeed.
277 #ifdef MOZ_DEFAULT_BROWSER_AGENT
278   bool progIDsExist = false;
279   bool hashOk = false;
280   *aResult = NS_SUCCEEDED(CheckAllProgIDsExist(&progIDsExist)) &&
281              progIDsExist &&
282              NS_SUCCEEDED(CheckBrowserUserChoiceHashes(&hashOk)) && hashOk;
283 #endif
284   return NS_OK;
285 }
286 
LaunchModernSettingsDialogDefaultApps()287 nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() {
288   return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE;
289 }
290 
InvokeHTTPOpenAsVerb()291 nsresult nsWindowsShellService::InvokeHTTPOpenAsVerb() {
292   nsCOMPtr<nsIURLFormatter> formatter(
293       do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
294   if (!formatter) {
295     return NS_ERROR_UNEXPECTED;
296   }
297 
298   nsString urlStr;
299   nsresult rv = formatter->FormatURLPref(u"app.support.baseURL"_ns, urlStr);
300   if (NS_FAILED(rv)) {
301     return rv;
302   }
303   if (!StringBeginsWith(urlStr, u"https://"_ns)) {
304     return NS_ERROR_FAILURE;
305   }
306   urlStr.AppendLiteral("win10-default-browser");
307 
308   SHELLEXECUTEINFOW seinfo = {sizeof(SHELLEXECUTEINFOW)};
309   seinfo.lpVerb = L"openas";
310   seinfo.lpFile = urlStr.get();
311   seinfo.nShow = SW_SHOWNORMAL;
312   if (!ShellExecuteExW(&seinfo)) {
313     return NS_ERROR_FAILURE;
314   }
315   return NS_OK;
316 }
317 
LaunchHTTPHandlerPane()318 nsresult nsWindowsShellService::LaunchHTTPHandlerPane() {
319   OPENASINFO info;
320   info.pcszFile = L"http";
321   info.pcszClass = nullptr;
322   info.oaifInFlags =
323       OAIF_FORCE_REGISTRATION | OAIF_URL_PROTOCOL | OAIF_REGISTER_EXT;
324 
325   HRESULT hr = SHOpenWithDialog(nullptr, &info);
326   if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))) {
327     return NS_OK;
328   }
329   return NS_ERROR_FAILURE;
330 }
331 
332 NS_IMETHODIMP
SetDefaultBrowser(bool aClaimAllTypes,bool aForAllUsers)333 nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes,
334                                          bool aForAllUsers) {
335   // If running from within a package, don't attempt to set default with
336   // the helper, as it will not work and will only confuse our package's
337   // virtualized registry.
338   nsresult rv = NS_OK;
339   if (!widget::WinUtils::HasPackageIdentity()) {
340     nsAutoString appHelperPath;
341     if (NS_FAILED(GetHelperPath(appHelperPath))) return NS_ERROR_FAILURE;
342 
343     if (aForAllUsers) {
344       appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal");
345     } else {
346       appHelperPath.AppendLiteral(" /SetAsDefaultAppUser");
347     }
348 
349     rv = LaunchHelper(appHelperPath);
350   }
351 
352   if (NS_SUCCEEDED(rv) && IsWin8OrLater()) {
353     if (aClaimAllTypes) {
354       if (IsWin10OrLater()) {
355         rv = LaunchModernSettingsDialogDefaultApps();
356       } else {
357         rv = LaunchControlPanelDefaultsSelectionUI();
358       }
359       // The above call should never really fail, but just in case
360       // fall back to showing the HTTP association screen only.
361       if (NS_FAILED(rv)) {
362         if (IsWin10OrLater()) {
363           rv = InvokeHTTPOpenAsVerb();
364         } else {
365           rv = LaunchHTTPHandlerPane();
366         }
367       }
368     } else {
369       // Windows 10 blocks attempts to load the
370       // HTTP Handler association dialog.
371       if (IsWin10OrLater()) {
372         rv = LaunchModernSettingsDialogDefaultApps();
373       } else {
374         rv = LaunchHTTPHandlerPane();
375       }
376 
377       // The above call should never really fail, but just in case
378       // fall back to showing control panel for all defaults
379       if (NS_FAILED(rv)) {
380         rv = LaunchControlPanelDefaultsSelectionUI();
381       }
382     }
383   }
384 
385   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
386   if (prefs) {
387     (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
388     // Reset the number of times the dialog should be shown
389     // before it is silenced.
390     (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
391   }
392 
393   return rv;
394 }
395 
WriteBitmap(nsIFile * aFile,imgIContainer * aImage)396 static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage) {
397   nsresult rv;
398 
399   RefPtr<gfx::SourceSurface> surface = aImage->GetFrame(
400       imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE);
401   NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
402 
403   // For either of the following formats we want to set the biBitCount member
404   // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap
405   // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored
406   // for the BI_RGB value we use for the biCompression member.
407   MOZ_ASSERT(surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8 ||
408              surface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
409 
410   RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
411   NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
412 
413   int32_t width = dataSurface->GetSize().width;
414   int32_t height = dataSurface->GetSize().height;
415   int32_t bytesPerPixel = 4 * sizeof(uint8_t);
416   uint32_t bytesPerRow = bytesPerPixel * width;
417 
418   // initialize these bitmap structs which we will later
419   // serialize directly to the head of the bitmap file
420   BITMAPINFOHEADER bmi;
421   bmi.biSize = sizeof(BITMAPINFOHEADER);
422   bmi.biWidth = width;
423   bmi.biHeight = height;
424   bmi.biPlanes = 1;
425   bmi.biBitCount = (WORD)bytesPerPixel * 8;
426   bmi.biCompression = BI_RGB;
427   bmi.biSizeImage = bytesPerRow * height;
428   bmi.biXPelsPerMeter = 0;
429   bmi.biYPelsPerMeter = 0;
430   bmi.biClrUsed = 0;
431   bmi.biClrImportant = 0;
432 
433   BITMAPFILEHEADER bf;
434   bf.bfType = 0x4D42;  // 'BM'
435   bf.bfReserved1 = 0;
436   bf.bfReserved2 = 0;
437   bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
438   bf.bfSize = bf.bfOffBits + bmi.biSizeImage;
439 
440   // get a file output stream
441   nsCOMPtr<nsIOutputStream> stream;
442   rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile);
443   NS_ENSURE_SUCCESS(rv, rv);
444 
445   gfx::DataSourceSurface::MappedSurface map;
446   if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
447     return NS_ERROR_FAILURE;
448   }
449 
450   // write the bitmap headers and rgb pixel data to the file
451   rv = NS_ERROR_FAILURE;
452   if (stream) {
453     uint32_t written;
454     stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written);
455     if (written == sizeof(BITMAPFILEHEADER)) {
456       stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written);
457       if (written == sizeof(BITMAPINFOHEADER)) {
458         // write out the image data backwards because the desktop won't
459         // show bitmaps with negative heights for top-to-bottom
460         uint32_t i = map.mStride * height;
461         do {
462           i -= map.mStride;
463           stream->Write(((const char*)map.mData) + i, bytesPerRow, &written);
464           if (written == bytesPerRow) {
465             rv = NS_OK;
466           } else {
467             rv = NS_ERROR_FAILURE;
468             break;
469           }
470         } while (i != 0);
471       }
472     }
473 
474     stream->Close();
475   }
476 
477   dataSurface->Unmap();
478 
479   return rv;
480 }
481 
482 NS_IMETHODIMP
SetDesktopBackground(dom::Element * aElement,int32_t aPosition,const nsACString & aImageName)483 nsWindowsShellService::SetDesktopBackground(dom::Element* aElement,
484                                             int32_t aPosition,
485                                             const nsACString& aImageName) {
486   if (!aElement || !aElement->IsHTMLElement(nsGkAtoms::img)) {
487     // XXX write background loading stuff!
488     return NS_ERROR_NOT_AVAILABLE;
489   }
490 
491   nsresult rv;
492   nsCOMPtr<nsIImageLoadingContent> imageContent =
493       do_QueryInterface(aElement, &rv);
494   if (!imageContent) return rv;
495 
496   // get the image container
497   nsCOMPtr<imgIRequest> request;
498   rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
499                                 getter_AddRefs(request));
500   if (!request) return rv;
501 
502   nsCOMPtr<imgIContainer> container;
503   rv = request->GetImage(getter_AddRefs(container));
504   if (!container) return NS_ERROR_FAILURE;
505 
506   // get the file name from localized strings
507   nsCOMPtr<nsIStringBundleService> bundleService(
508       do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
509   NS_ENSURE_SUCCESS(rv, rv);
510 
511   nsCOMPtr<nsIStringBundle> shellBundle;
512   rv = bundleService->CreateBundle(SHELLSERVICE_PROPERTIES,
513                                    getter_AddRefs(shellBundle));
514   NS_ENSURE_SUCCESS(rv, rv);
515 
516   // e.g. "Desktop Background.bmp"
517   nsAutoString fileLeafName;
518   rv = shellBundle->GetStringFromName("desktopBackgroundLeafNameWin",
519                                       fileLeafName);
520   NS_ENSURE_SUCCESS(rv, rv);
521 
522   // get the profile root directory
523   nsCOMPtr<nsIFile> file;
524   rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR,
525                               getter_AddRefs(file));
526   NS_ENSURE_SUCCESS(rv, rv);
527 
528   // eventually, the path is "%APPDATA%\Mozilla\Firefox\Desktop Background.bmp"
529   rv = file->Append(fileLeafName);
530   NS_ENSURE_SUCCESS(rv, rv);
531 
532   nsAutoString path;
533   rv = file->GetPath(path);
534   NS_ENSURE_SUCCESS(rv, rv);
535 
536   // write the bitmap to a file in the profile directory.
537   // We have to write old bitmap format for Windows 7 wallpapar support.
538   rv = WriteBitmap(file, container);
539 
540   // if the file was written successfully, set it as the system wallpaper
541   if (NS_SUCCEEDED(rv)) {
542     nsCOMPtr<nsIWindowsRegKey> regKey =
543         do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
544     NS_ENSURE_SUCCESS(rv, rv);
545 
546     rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
547                         u"Control Panel\\Desktop"_ns,
548                         nsIWindowsRegKey::ACCESS_SET_VALUE);
549     NS_ENSURE_SUCCESS(rv, rv);
550 
551     nsAutoString tile;
552     nsAutoString style;
553     switch (aPosition) {
554       case BACKGROUND_TILE:
555         style.Assign('0');
556         tile.Assign('1');
557         break;
558       case BACKGROUND_CENTER:
559         style.Assign('0');
560         tile.Assign('0');
561         break;
562       case BACKGROUND_STRETCH:
563         style.Assign('2');
564         tile.Assign('0');
565         break;
566       case BACKGROUND_FILL:
567         style.AssignLiteral("10");
568         tile.Assign('0');
569         break;
570       case BACKGROUND_FIT:
571         style.Assign('6');
572         tile.Assign('0');
573         break;
574       case BACKGROUND_SPAN:
575         style.AssignLiteral("22");
576         tile.Assign('0');
577         break;
578     }
579 
580     rv = regKey->WriteStringValue(u"TileWallpaper"_ns, tile);
581     NS_ENSURE_SUCCESS(rv, rv);
582     rv = regKey->WriteStringValue(u"WallpaperStyle"_ns, style);
583     NS_ENSURE_SUCCESS(rv, rv);
584     rv = regKey->Close();
585     NS_ENSURE_SUCCESS(rv, rv);
586 
587     ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(),
588                             SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
589   }
590   return rv;
591 }
592 
593 NS_IMETHODIMP
GetDesktopBackgroundColor(uint32_t * aColor)594 nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor) {
595   uint32_t color = ::GetSysColor(COLOR_DESKTOP);
596   *aColor =
597       (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color);
598   return NS_OK;
599 }
600 
601 NS_IMETHODIMP
SetDesktopBackgroundColor(uint32_t aColor)602 nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor) {
603   int aParameters[2] = {COLOR_BACKGROUND, COLOR_DESKTOP};
604   BYTE r = (aColor >> 16);
605   BYTE g = (aColor << 16) >> 24;
606   BYTE b = (aColor << 24) >> 24;
607   COLORREF colors[2] = {RGB(r, g, b), RGB(r, g, b)};
608 
609   ::SetSysColors(sizeof(aParameters) / sizeof(int), aParameters, colors);
610 
611   nsresult rv;
612   nsCOMPtr<nsIWindowsRegKey> regKey =
613       do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
614   NS_ENSURE_SUCCESS(rv, rv);
615 
616   rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
617                       u"Control Panel\\Colors"_ns,
618                       nsIWindowsRegKey::ACCESS_SET_VALUE);
619   NS_ENSURE_SUCCESS(rv, rv);
620 
621   wchar_t rgb[12];
622   _snwprintf(rgb, 12, L"%u %u %u", r, g, b);
623 
624   rv = regKey->WriteStringValue(u"Background"_ns, nsDependentString(rgb));
625   NS_ENSURE_SUCCESS(rv, rv);
626 
627   return regKey->Close();
628 }
629 
630 NS_IMETHODIMP
CreateShortcut(nsIFile * aBinary,const nsTArray<nsString> & aArguments,const nsAString & aDescription,nsIFile * aIconFile,const nsAString & aAppUserModelId,nsIFile * aTarget)631 nsWindowsShellService::CreateShortcut(nsIFile* aBinary,
632                                       const nsTArray<nsString>& aArguments,
633                                       const nsAString& aDescription,
634                                       nsIFile* aIconFile,
635                                       const nsAString& aAppUserModelId,
636                                       nsIFile* aTarget) {
637   NS_ENSURE_ARG(aBinary);
638   NS_ENSURE_ARG(aTarget);
639 
640   RefPtr<IShellLinkW> link;
641   HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
642                                 IID_IShellLinkW, getter_AddRefs(link));
643   NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
644 
645   nsString path(aBinary->NativePath());
646   link->SetPath(path.get());
647 
648   if (!aDescription.IsEmpty()) {
649     link->SetDescription(PromiseFlatString(aDescription).get());
650   }
651 
652   // TODO: Properly escape quotes in the string, see bug 1604287.
653   nsString arguments;
654   for (auto& arg : aArguments) {
655     arguments.AppendPrintf("\"%S\" ", arg.get());
656   }
657 
658   link->SetArguments(arguments.get());
659 
660   if (aIconFile) {
661     nsString icon(aIconFile->NativePath());
662     link->SetIconLocation(icon.get(), 0);
663   }
664 
665   if (!aAppUserModelId.IsEmpty()) {
666     RefPtr<IPropertyStore> propStore;
667     hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore));
668     NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
669 
670     PROPVARIANT pv;
671     if (FAILED(InitPropVariantFromString(
672             PromiseFlatString(aAppUserModelId).get(), &pv))) {
673       return NS_ERROR_FAILURE;
674     }
675 
676     hr = propStore->SetValue(PKEY_AppUserModel_ID, pv);
677     PropVariantClear(&pv);
678     NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
679 
680     hr = propStore->Commit();
681     NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
682   }
683 
684   RefPtr<IPersistFile> persist;
685   hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist));
686   NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
687 
688   nsString target(aTarget->NativePath());
689   hr = persist->Save(target.get(), TRUE);
690   NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE);
691 
692   return NS_OK;
693 }
694 
695 // Constructs a path to an installer-created shortcut, under a directory
696 // specified by a CSIDL.
GetShortcutPath(int aCSIDL,nsAutoString & aPath)697 static nsresult GetShortcutPath(int aCSIDL, /* out */ nsAutoString& aPath) {
698   wchar_t folderPath[MAX_PATH] = {};
699   HRESULT hr = SHGetFolderPathW(nullptr, aCSIDL, nullptr, SHGFP_TYPE_CURRENT,
700                                 folderPath);
701   if (NS_WARN_IF(FAILED(hr))) {
702     return NS_ERROR_FAILURE;
703   }
704 
705   aPath.Assign(folderPath);
706   if (NS_WARN_IF(aPath.IsEmpty())) {
707     return NS_ERROR_FAILURE;
708   }
709   if (aPath[aPath.Length() - 1] != '\\') {
710     aPath.AppendLiteral("\\");
711   }
712   // NOTE: In the installer, shortcuts are named "${BrandShortName}.lnk".
713   // This is set from MOZ_APP_DISPLAYNAME in defines.nsi.in. (Except in dev
714   // edition where it's explicitly set to "Firefox Developer Edition" in
715   // branding.nsi, which matches MOZ_APP_DISPLAYNAME in aurora/configure.sh.)
716   //
717   // If this changes, we could expand this to check shortcuts_log.ini,
718   // which records the name of the shortcuts as created by the installer.
719   aPath.AppendLiteral(MOZ_APP_DISPLAYNAME ".lnk");
720 
721   return NS_OK;
722 }
723 
724 // Check if the instaler-created shortcut in the given location matches,
725 // if so output its path
726 //
727 // NOTE: DO NOT USE if a false negative (mismatch) is unacceptable.
728 // aExePath is compared directly to the path retrieved from the shortcut.
729 // Due to the presence of symlinks or other filesystem issues, it's possible
730 // for different paths to refer to the same file, which would cause the check
731 // to fail.
732 // This should rarely be an issue as we are most likely to be run from a path
733 // written by the installer (shortcut, association, launch from installer),
734 // which also wrote the shortcuts. But it is possible.
735 //
736 // aCSIDL   the CSIDL of the directory containing the shortcut to check.
737 // aAUMID   the AUMID to check for
738 // aExePath the target exe path to check for, should be a long path where
739 //          possible
740 // aShortcutPath outparam, set to matching shortcut path if NS_OK is returned.
741 //
742 // Returns
743 //   NS_ERROR_FAILURE on errors before the shortcut was loaded
744 //   NS_ERROR_FILE_NOT_FOUND if the shortcut doesn't exist
745 //   NS_ERROR_FILE_ALREADY_EXISTS if the shortcut exists but doesn't match the
746 //                                current app
747 //   NS_OK if the shortcut matches
GetMatchingShortcut(int aCSIDL,const nsAutoString & aAUMID,const wchar_t aExePath[MAXPATHLEN],nsAutoString & aShortcutPath)748 static nsresult GetMatchingShortcut(int aCSIDL, const nsAutoString& aAUMID,
749                                     const wchar_t aExePath[MAXPATHLEN],
750                                     /* out */ nsAutoString& aShortcutPath) {
751   nsresult result = NS_ERROR_FAILURE;
752 
753   nsAutoString path;
754   nsresult rv = GetShortcutPath(aCSIDL, path);
755   if (NS_WARN_IF(NS_FAILED(rv))) {
756     return result;
757   }
758 
759   // Create a shell link object for loading the shortcut
760   RefPtr<IShellLinkW> link;
761   HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
762                                 IID_IShellLinkW, getter_AddRefs(link));
763   if (NS_WARN_IF(FAILED(hr))) {
764     return result;
765   }
766 
767   // Load
768   RefPtr<IPersistFile> persist;
769   hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist));
770   if (NS_WARN_IF(FAILED(hr))) {
771     return result;
772   }
773 
774   hr = persist->Load(path.get(), STGM_READ);
775   if (FAILED(hr)) {
776     if (NS_WARN_IF(hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))) {
777       // empty branch, result unchanged but warning issued
778     } else {
779       result = NS_ERROR_FILE_NOT_FOUND;
780     }
781     return result;
782   }
783   result = NS_ERROR_FILE_ALREADY_EXISTS;
784 
785   // Check the AUMID
786   RefPtr<IPropertyStore> propStore;
787   hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore));
788   if (NS_WARN_IF(FAILED(hr))) {
789     return result;
790   }
791 
792   PROPVARIANT pv;
793   hr = propStore->GetValue(PKEY_AppUserModel_ID, &pv);
794   if (NS_WARN_IF(FAILED(hr))) {
795     return result;
796   }
797 
798   wchar_t storedAUMID[MAX_PATH];
799   hr = PropVariantToString(pv, storedAUMID, MAX_PATH);
800   PropVariantClear(&pv);
801   if (NS_WARN_IF(FAILED(hr))) {
802     return result;
803   }
804 
805   if (!aAUMID.Equals(storedAUMID)) {
806     return result;
807   }
808 
809   // Check the exe path
810   static_assert(MAXPATHLEN == MAX_PATH);
811   wchar_t storedExePath[MAX_PATH] = {};
812   // With no flags GetPath gets a long path
813   hr = link->GetPath(storedExePath, ArrayLength(storedExePath), nullptr, 0);
814   if (FAILED(hr) || hr == S_FALSE) {
815     return result;
816   }
817   // Case insensitive path comparison
818   if (wcsnicmp(storedExePath, aExePath, MAXPATHLEN) != 0) {
819     return result;
820   }
821 
822   // Success, report the shortcut path
823   aShortcutPath.Assign(path);
824   result = NS_OK;
825 
826   return result;
827 }
828 
PinCurrentAppToTaskbarImpl(bool aCheckOnly)829 static nsresult PinCurrentAppToTaskbarImpl(bool aCheckOnly) {
830   // This enum is likely only used for Windows telemetry, INT_MAX is chosen to
831   // avoid confusion with existing uses.
832   enum PINNEDLISTMODIFYCALLER { PLMC_INT_MAX = INT_MAX };
833 
834   // The types below, and the idea of using IPinnedList3::Modify,
835   // are thanks to Gee Law <https://geelaw.blog/entries/msedge-pins/>
836   static constexpr GUID CLSID_TaskbandPin = {
837       0x90aa3a4e,
838       0x1cba,
839       0x4233,
840       {0xb8, 0xbb, 0x53, 0x57, 0x73, 0xd4, 0x84, 0x49}};
841 
842   static constexpr GUID IID_IPinnedList3 = {
843       0x0dd79ae2,
844       0xd156,
845       0x45d4,
846       {0x9e, 0xeb, 0x3b, 0x54, 0x97, 0x69, 0xe9, 0x40}};
847 
848   struct IPinnedList3Vtbl;
849   struct IPinnedList3 {
850     IPinnedList3Vtbl* vtbl;
851   };
852 
853   typedef ULONG STDMETHODCALLTYPE ReleaseFunc(IPinnedList3 * that);
854   typedef HRESULT STDMETHODCALLTYPE ModifyFunc(
855       IPinnedList3 * that, PCIDLIST_ABSOLUTE unpin, PCIDLIST_ABSOLUTE pin,
856       PINNEDLISTMODIFYCALLER caller);
857 
858   struct IPinnedList3Vtbl {
859     void* QueryInterface;  // 0
860     void* AddRef;          // 1
861     ReleaseFunc* Release;  // 2
862     void* Other[13];       // 3-15
863     ModifyFunc* Modify;    // 16
864   };
865 
866   struct ILFreeDeleter {
867     void operator()(LPITEMIDLIST aPtr) {
868       if (aPtr) {
869         ILFree(aPtr);
870       }
871     }
872   };
873 
874   // First available on 1809
875   if (!IsWin10Sep2018UpdateOrLater()) {
876     return NS_ERROR_NOT_AVAILABLE;
877   }
878 
879   nsAutoString aumid;
880   if (NS_WARN_IF(!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid))) {
881     return NS_ERROR_FAILURE;
882   }
883 
884   wchar_t exePath[MAXPATHLEN] = {};
885   if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) {
886     return NS_ERROR_FAILURE;
887   }
888 
889   // Try to find a shortcut matching the running app
890   nsAutoString shortcutPath;
891   int shortcutCSIDLs[] = {CSIDL_COMMON_PROGRAMS, CSIDL_PROGRAMS,
892                           CSIDL_COMMON_DESKTOPDIRECTORY,
893                           CSIDL_DESKTOPDIRECTORY};
894   for (int i = 0; i < ArrayLength(shortcutCSIDLs); ++i) {
895     // GetMatchingShortcut may fail when the exe path doesn't match, even
896     // if it refers to the same file. This should be rare, and the worst
897     // outcome would be failure to pin, so the risk is acceptable.
898     nsresult rv =
899         GetMatchingShortcut(shortcutCSIDLs[i], aumid, exePath, shortcutPath);
900     if (NS_SUCCEEDED(rv)) {
901       break;
902     } else {
903       shortcutPath.Truncate();
904     }
905   }
906   if (shortcutPath.IsEmpty()) {
907     return NS_ERROR_FILE_NOT_FOUND;
908   }
909 
910   mozilla::UniquePtr<__unaligned ITEMIDLIST, ILFreeDeleter> path(
911       ILCreateFromPathW(shortcutPath.get()));
912   if (NS_WARN_IF(!path)) {
913     return NS_ERROR_FAILURE;
914   }
915 
916   IPinnedList3* pinnedList = nullptr;
917   HRESULT hr =
918       CoCreateInstance(CLSID_TaskbandPin, nullptr, CLSCTX_INPROC_SERVER,
919                        IID_IPinnedList3, (void**)&pinnedList);
920   if (FAILED(hr) || !pinnedList) {
921     return NS_ERROR_NOT_AVAILABLE;
922   }
923 
924   if (!aCheckOnly) {
925     hr =
926         pinnedList->vtbl->Modify(pinnedList, nullptr, path.get(), PLMC_INT_MAX);
927   }
928 
929   pinnedList->vtbl->Release(pinnedList);
930 
931   if (FAILED(hr)) {
932     return NS_ERROR_FAILURE;
933   } else {
934     return NS_OK;
935   }
936 }
937 
938 NS_IMETHODIMP
PinCurrentAppToTaskbar()939 nsWindowsShellService::PinCurrentAppToTaskbar() {
940   return PinCurrentAppToTaskbarImpl(/* aCheckOnly */ false);
941 }
942 
943 NS_IMETHODIMP
CheckPinCurrentAppToTaskbar()944 nsWindowsShellService::CheckPinCurrentAppToTaskbar() {
945   return PinCurrentAppToTaskbarImpl(/* aCheckOnly */ true);
946 }
947 
IsCurrentAppPinnedToTaskbarSync()948 static bool IsCurrentAppPinnedToTaskbarSync() {
949   wchar_t exePath[MAXPATHLEN] = {};
950   if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) {
951     return false;
952   }
953 
954   wchar_t folderChars[MAX_PATH] = {};
955   HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr,
956                                 SHGFP_TYPE_CURRENT, folderChars);
957   if (NS_WARN_IF(FAILED(hr))) {
958     return false;
959   }
960 
961   nsAutoString folder;
962   folder.Assign(folderChars);
963   if (NS_WARN_IF(folder.IsEmpty())) {
964     return false;
965   }
966   if (folder[folder.Length() - 1] != '\\') {
967     folder.AppendLiteral("\\");
968   }
969   folder.AppendLiteral(
970       "Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar");
971   nsAutoString pattern;
972   pattern.Assign(folder);
973   pattern.AppendLiteral("\\*.lnk");
974 
975   WIN32_FIND_DATAW findData = {};
976   HANDLE hFindFile = FindFirstFileW(pattern.get(), &findData);
977   if (hFindFile == INVALID_HANDLE_VALUE) {
978     Unused << NS_WARN_IF(GetLastError() != ERROR_FILE_NOT_FOUND);
979     return false;
980   }
981   // Past this point we don't return until the end of the function,
982   // when FindClose() is called.
983 
984   // Check all shortcuts until a match is found
985   bool isPinned = false;
986   do {
987     nsAutoString fileName;
988     fileName.Assign(folder);
989     fileName.AppendLiteral("\\");
990     fileName.Append(findData.cFileName);
991 
992     // Create a shell link object for loading the shortcut
993     RefPtr<IShellLinkW> link;
994     HRESULT hr =
995         CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
996                          IID_IShellLinkW, getter_AddRefs(link));
997     if (NS_WARN_IF(FAILED(hr))) {
998       continue;
999     }
1000 
1001     // Load
1002     RefPtr<IPersistFile> persist;
1003     hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist));
1004     if (NS_WARN_IF(FAILED(hr))) {
1005       continue;
1006     }
1007 
1008     hr = persist->Load(fileName.get(), STGM_READ);
1009     if (NS_WARN_IF(FAILED(hr))) {
1010       continue;
1011     }
1012 
1013     // Note: AUMID is not checked, so a pin that does not group properly
1014     // will still count as long as the exe matches.
1015 
1016     // Check the exe path
1017     static_assert(MAXPATHLEN == MAX_PATH);
1018     wchar_t storedExePath[MAX_PATH] = {};
1019     // With no flags GetPath gets a long path
1020     hr = link->GetPath(storedExePath, ArrayLength(storedExePath), nullptr, 0);
1021     if (FAILED(hr) || hr == S_FALSE) {
1022       continue;
1023     }
1024     // Case insensitive path comparison
1025     // NOTE: Because this compares the path directly, it is possible to
1026     // have a false negative mismatch.
1027     if (wcsnicmp(storedExePath, exePath, MAXPATHLEN) == 0) {
1028       isPinned = true;
1029       break;
1030     }
1031   } while (FindNextFileW(hFindFile, &findData));
1032 
1033   FindClose(hFindFile);
1034 
1035   return isPinned;
1036 }
1037 
1038 NS_IMETHODIMP
IsCurrentAppPinnedToTaskbarAsync(JSContext * aCx,dom::Promise ** aPromise)1039 nsWindowsShellService::IsCurrentAppPinnedToTaskbarAsync(
1040     JSContext* aCx, /* out */ dom::Promise** aPromise) {
1041   if (!NS_IsMainThread()) {
1042     return NS_ERROR_NOT_SAME_THREAD;
1043   }
1044 
1045   ErrorResult rv;
1046   RefPtr<dom::Promise> promise =
1047       dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
1048   if (MOZ_UNLIKELY(rv.Failed())) {
1049     return rv.StealNSResult();
1050   }
1051 
1052   // A holder to pass the promise through the background task and back to
1053   // the main thread when finished.
1054   auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>(
1055       "IsCurrentAppPinnedToTaskbarAsync promise", promise);
1056 
1057   NS_DispatchBackgroundTask(
1058       NS_NewRunnableFunction(
1059           "IsCurrentAppPinnedToTaskbarAsync",
1060           [promiseHolder = std::move(promiseHolder)] {
1061             bool isPinned = false;
1062 
1063             HRESULT hr = CoInitialize(nullptr);
1064             if (SUCCEEDED(hr)) {
1065               isPinned = IsCurrentAppPinnedToTaskbarSync();
1066               CoUninitialize();
1067             }
1068 
1069             // Dispatch back to the main thread to resolve the promise.
1070             NS_DispatchToMainThread(NS_NewRunnableFunction(
1071                 "IsCurrentAppPinnedToTaskbarAsync callback",
1072                 [isPinned, promiseHolder = std::move(promiseHolder)] {
1073                   promiseHolder.get()->get()->MaybeResolve(isPinned);
1074                 }));
1075           }),
1076       NS_DISPATCH_EVENT_MAY_BLOCK);
1077 
1078   promise.forget(aPromise);
1079   return NS_OK;
1080 }
1081 
1082 NS_IMETHODIMP
ClassifyShortcut(const nsAString & aPath,nsAString & aResult)1083 nsWindowsShellService::ClassifyShortcut(const nsAString& aPath,
1084                                         nsAString& aResult) {
1085   aResult.Truncate();
1086 
1087   nsAutoString shortcutPath(PromiseFlatString(aPath));
1088 
1089   // NOTE: On Windows 7, Start Menu pin shortcuts are stored under
1090   // "<FOLDERID_User Pinned>\StartMenu", but on Windows 10 they are just normal
1091   // Start Menu shortcuts. These both map to "StartMenu" for consistency,
1092   // rather than having a separate "StartMenuPins" which would only apply on
1093   // Win7.
1094   struct {
1095     KNOWNFOLDERID folderId;
1096     const char16_t* postfix;
1097     const char16_t* classification;
1098   } folders[] = {{FOLDERID_CommonStartMenu, u"\\", u"StartMenu"},
1099                  {FOLDERID_StartMenu, u"\\", u"StartMenu"},
1100                  {FOLDERID_PublicDesktop, u"\\", u"Desktop"},
1101                  {FOLDERID_Desktop, u"\\", u"Desktop"},
1102                  {FOLDERID_UserPinned, u"\\TaskBar\\", u"Taskbar"},
1103                  {FOLDERID_UserPinned, u"\\StartMenu\\", u"StartMenu"}};
1104 
1105   for (int i = 0; i < ArrayLength(folders); ++i) {
1106     nsAutoString knownPath;
1107 
1108     // These flags are chosen to avoid I/O, see bug 1363398.
1109     DWORD flags =
1110         KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS;
1111     PWSTR rawPath = nullptr;
1112 
1113     if (FAILED(SHGetKnownFolderPath(folders[i].folderId, flags, nullptr,
1114                                     &rawPath))) {
1115       continue;
1116     }
1117 
1118     knownPath = nsDependentString(rawPath);
1119     CoTaskMemFree(rawPath);
1120 
1121     knownPath.Append(folders[i].postfix);
1122     // Check if the shortcut path starts with the shell folder path.
1123     if (wcsnicmp(shortcutPath.get(), knownPath.get(), knownPath.Length()) ==
1124         0) {
1125       aResult.Assign(folders[i].classification);
1126       return NS_OK;
1127     }
1128   }
1129 
1130   // Nothing found, aResult is already "".
1131   return NS_OK;
1132 }
1133 
nsWindowsShellService()1134 nsWindowsShellService::nsWindowsShellService() {}
1135 
~nsWindowsShellService()1136 nsWindowsShellService::~nsWindowsShellService() {}
1137