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 ®isteredApp);
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