nsWindowsShellService.cpp (103457B)
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 #define UNICODE 7 8 #include "nsWindowsShellService.h" 9 #include "nsWindowsShellServiceInternal.h" 10 11 #include "BinaryPath.h" 12 #include "gfxUtils.h" 13 #include "imgIContainer.h" 14 #include "imgIRequest.h" 15 #include "imgITools.h" 16 #include "mozilla/dom/Element.h" 17 #include "mozilla/dom/Promise.h" 18 #include "mozilla/ErrorResult.h" 19 #include "mozilla/FileUtils.h" 20 #include "mozilla/gfx/2D.h" 21 #include "mozilla/intl/Localization.h" 22 #include "mozilla/RefPtr.h" 23 #include "mozilla/widget/WinTaskbar.h" 24 #include "mozilla/WindowsVersion.h" 25 #include "mozilla/WinHeaderOnlyUtils.h" 26 #include "nsAppDirectoryServiceDefs.h" 27 #include "nsComponentManagerUtils.h" 28 #include "nsDirectoryServiceDefs.h" 29 #include "nsDirectoryServiceUtils.h" 30 #include "nsIContent.h" 31 #include "nsIFile.h" 32 #include "nsIFileStreams.h" 33 #include "nsIImageLoadingContent.h" 34 #include "nsIMIMEService.h" 35 #include "nsINIParser.h" 36 #include "nsIOutputStream.h" 37 #include "nsIPrefService.h" 38 #include "nsIStringBundle.h" 39 #include "nsIWindowsRegKey.h" 40 #include "nsIXULAppInfo.h" 41 #include "nsLocalFile.h" 42 #include "nsNativeAppSupportWin.h" 43 #include "nsNetUtil.h" 44 #include "nsProxyRelease.h" 45 #include "nsServiceManagerUtils.h" 46 #include "nsShellService.h" 47 #include "nsUnicharUtils.h" 48 #include "nsWindowsHelpers.h" 49 #include "nsXULAppAPI.h" 50 #include "Windows11TaskbarPinning.h" 51 #include "WindowsDefaultBrowser.h" 52 #include "WindowsUserChoice.h" 53 #include "WinUtils.h" 54 55 #include <comutil.h> 56 #include <knownfolders.h> 57 #include <mbstring.h> 58 #include <objbase.h> 59 #include <propkey.h> 60 #include <propvarutil.h> 61 #include <shellapi.h> 62 #include <strsafe.h> 63 #include <windows.h> 64 #include <windows.foundation.h> 65 #include <wrl.h> 66 #include <wrl/wrappers/corewrappers.h> 67 68 using namespace ABI::Windows; 69 using namespace ABI::Windows::Foundation; 70 using namespace ABI::Windows::Foundation::Collections; 71 using namespace Microsoft::WRL; 72 using namespace Microsoft::WRL::Wrappers; 73 74 #ifndef __MINGW32__ 75 # include <windows.applicationmodel.h> 76 # include <windows.applicationmodel.activation.h> 77 # include <windows.applicationmodel.core.h> 78 # include <windows.ui.startscreen.h> 79 using namespace ABI::Windows::ApplicationModel; 80 using namespace ABI::Windows::ApplicationModel::Core; 81 using namespace ABI::Windows::UI::StartScreen; 82 #endif 83 84 #define PRIVATE_BROWSING_BINARY L"private_browsing.exe" 85 86 #undef ACCESS_READ 87 88 #ifndef MAX_BUF 89 # define MAX_BUF 4096 90 #endif 91 92 #define REG_SUCCEEDED(val) (val == ERROR_SUCCESS) 93 94 #define REG_FAILED(val) (val != ERROR_SUCCESS) 95 96 #ifdef DEBUG 97 # define NS_ENSURE_HRESULT(hres, ret) \ 98 do { \ 99 HRESULT result = hres; \ 100 if (MOZ_UNLIKELY(FAILED(result))) { \ 101 mozilla::SmprintfPointer msg = mozilla::Smprintf( \ 102 "NS_ENSURE_HRESULT(%s, %s) failed with " \ 103 "result 0x%" PRIX32, \ 104 #hres, #ret, static_cast<uint32_t>(result)); \ 105 NS_WARNING(msg.get()); \ 106 return ret; \ 107 } \ 108 } while (false) 109 #else 110 # define NS_ENSURE_HRESULT(hres, ret) \ 111 if (MOZ_UNLIKELY(FAILED(hres))) return ret 112 #endif 113 114 using namespace mozilla; 115 using mozilla::intl::Localization; 116 117 struct SysFreeStringDeleter { 118 void operator()(BSTR aPtr) { ::SysFreeString(aPtr); } 119 }; 120 using BStrPtr = mozilla::UniquePtr<OLECHAR, SysFreeStringDeleter>; 121 122 NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIToolkitShellService, 123 nsIShellService, nsIWindowsShellService) 124 125 /* Enable logging by setting MOZ_LOG to "nsWindowsShellService:5" for debugging 126 * purposes. */ 127 static LazyLogModule sLog("nsWindowsShellService"); 128 129 static bool PollAppsFolderForShortcut(const nsAString& aAppUserModelId, 130 const TimeDuration aTimeout); 131 static nsresult PinCurrentAppToTaskbarWin10(bool aCheckOnly, 132 const nsAString& aAppUserModelId, 133 const nsAString& aShortcutPath); 134 static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage); 135 static nsresult WriteIcon(nsIFile* aIcoFile, gfx::DataSourceSurface* aSurface); 136 137 static nsresult OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName, 138 HKEY* aKey) { 139 const nsString& flatName = PromiseFlatString(aKeyName); 140 141 DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey); 142 switch (res) { 143 case ERROR_SUCCESS: 144 break; 145 case ERROR_ACCESS_DENIED: 146 return NS_ERROR_FILE_ACCESS_DENIED; 147 case ERROR_FILE_NOT_FOUND: 148 return NS_ERROR_NOT_AVAILABLE; 149 } 150 151 return NS_OK; 152 } 153 154 nsresult GetHelperPath(nsAutoString& aPath) { 155 nsresult rv; 156 nsCOMPtr<nsIProperties> directoryService = 157 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); 158 NS_ENSURE_SUCCESS(rv, rv); 159 160 nsCOMPtr<nsIFile> appHelper; 161 rv = directoryService->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), 162 getter_AddRefs(appHelper)); 163 NS_ENSURE_SUCCESS(rv, rv); 164 165 rv = appHelper->SetNativeLeafName("uninstall"_ns); 166 NS_ENSURE_SUCCESS(rv, rv); 167 168 rv = appHelper->AppendNative("helper.exe"_ns); 169 NS_ENSURE_SUCCESS(rv, rv); 170 171 rv = appHelper->GetPath(aPath); 172 173 aPath.Insert(L'"', 0); 174 aPath.Append(L'"'); 175 return rv; 176 } 177 178 nsresult LaunchHelper(nsAutoString& aPath) { 179 STARTUPINFOW si = {sizeof(si), 0}; 180 PROCESS_INFORMATION pi = {0}; 181 182 if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE, 0, 183 nullptr, nullptr, &si, &pi)) { 184 return NS_ERROR_FAILURE; 185 } 186 187 CloseHandle(pi.hProcess); 188 CloseHandle(pi.hThread); 189 return NS_OK; 190 } 191 192 static bool IsPathDefaultForClass( 193 const RefPtr<IApplicationAssociationRegistration>& pAAR, wchar_t* exePath, 194 LPCWSTR aClassName) { 195 LPWSTR registeredApp; 196 bool isProtocol = *aClassName != L'.'; 197 ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION; 198 HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE, 199 ®isteredApp); 200 if (FAILED(hr)) { 201 return false; 202 } 203 204 nsAutoString regAppName(registeredApp); 205 CoTaskMemFree(registeredApp); 206 207 // Make sure the application path for this progID is this installation. 208 regAppName.AppendLiteral("\\shell\\open\\command"); 209 HKEY theKey; 210 nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, regAppName, &theKey); 211 if (NS_FAILED(rv)) { 212 return false; 213 } 214 215 wchar_t cmdFromReg[MAX_BUF] = L""; 216 DWORD len = sizeof(cmdFromReg); 217 DWORD res = ::RegQueryValueExW(theKey, nullptr, nullptr, nullptr, 218 (LPBYTE)cmdFromReg, &len); 219 ::RegCloseKey(theKey); 220 if (REG_FAILED(res)) { 221 return false; 222 } 223 224 nsAutoString pathFromReg(cmdFromReg); 225 nsLocalFile::CleanupCmdHandlerPath(pathFromReg); 226 227 return _wcsicmp(exePath, pathFromReg.Data()) == 0; 228 } 229 230 NS_IMETHODIMP 231 nsWindowsShellService::IsDefaultBrowser(bool aForAllTypes, 232 bool* aIsDefaultBrowser) { 233 *aIsDefaultBrowser = false; 234 235 RefPtr<IApplicationAssociationRegistration> pAAR; 236 HRESULT hr = CoCreateInstance( 237 CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC, 238 IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR)); 239 if (FAILED(hr)) { 240 return NS_OK; 241 } 242 243 wchar_t exePath[MAXPATHLEN] = L""; 244 nsresult rv = BinaryPath::GetLong(exePath); 245 246 if (NS_FAILED(rv)) { 247 return NS_OK; 248 } 249 250 *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L"http"); 251 if (*aIsDefaultBrowser && aForAllTypes) { 252 *aIsDefaultBrowser = IsPathDefaultForClass(pAAR, exePath, L".html"); 253 } 254 return NS_OK; 255 } 256 257 NS_IMETHODIMP 258 nsWindowsShellService::IsDefaultHandlerFor( 259 const nsAString& aFileExtensionOrProtocol, bool* aIsDefaultHandlerFor) { 260 *aIsDefaultHandlerFor = false; 261 262 RefPtr<IApplicationAssociationRegistration> pAAR; 263 HRESULT hr = CoCreateInstance( 264 CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC, 265 IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR)); 266 if (FAILED(hr)) { 267 return NS_OK; 268 } 269 270 wchar_t exePath[MAXPATHLEN] = L""; 271 nsresult rv = BinaryPath::GetLong(exePath); 272 273 if (NS_FAILED(rv)) { 274 return NS_OK; 275 } 276 277 const nsString& flatClass = PromiseFlatString(aFileExtensionOrProtocol); 278 279 *aIsDefaultHandlerFor = IsPathDefaultForClass(pAAR, exePath, flatClass.get()); 280 return NS_OK; 281 } 282 283 NS_IMETHODIMP 284 nsWindowsShellService::QueryCurrentDefaultHandlerFor( 285 const nsAString& aFileExtensionOrProtocol, nsAString& aResult) { 286 aResult.Truncate(); 287 288 RefPtr<IApplicationAssociationRegistration> pAAR; 289 HRESULT hr = CoCreateInstance( 290 CLSID_ApplicationAssociationRegistration, nullptr, CLSCTX_INPROC, 291 IID_IApplicationAssociationRegistration, getter_AddRefs(pAAR)); 292 if (FAILED(hr)) { 293 return NS_OK; 294 } 295 296 const nsString& flatClass = PromiseFlatString(aFileExtensionOrProtocol); 297 298 LPWSTR registeredApp; 299 bool isProtocol = flatClass.First() != L'.'; 300 ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION; 301 hr = pAAR->QueryCurrentDefault(flatClass.get(), queryType, AL_EFFECTIVE, 302 ®isteredApp); 303 if (hr == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { 304 return NS_OK; 305 } 306 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 307 308 aResult = registeredApp; 309 CoTaskMemFree(registeredApp); 310 311 return NS_OK; 312 } 313 314 nsresult nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI() { 315 IApplicationAssociationRegistrationUI* pAARUI; 316 HRESULT hr = CoCreateInstance( 317 CLSID_ApplicationAssociationRegistrationUI, NULL, CLSCTX_INPROC, 318 IID_IApplicationAssociationRegistrationUI, (void**)&pAARUI); 319 if (SUCCEEDED(hr)) { 320 mozilla::UniquePtr<wchar_t[]> appRegName; 321 GetAppRegName(appRegName); 322 hr = pAARUI->LaunchAdvancedAssociationUI(appRegName.get()); 323 pAARUI->Release(); 324 } 325 return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; 326 } 327 328 NS_IMETHODIMP 329 nsWindowsShellService::CheckAllProgIDsExist(bool* aResult) { 330 *aResult = false; 331 nsAutoString aumid; 332 if (!mozilla::widget::WinTaskbar::GetAppUserModelID(aumid)) { 333 return NS_OK; 334 } 335 336 if (widget::WinUtils::HasPackageIdentity()) { 337 UniquePtr<wchar_t[]> extraProgID; 338 nsresult rv; 339 bool result = true; 340 341 // "FirefoxURL". 342 rv = GetMsixProgId(L"https", extraProgID); 343 if (NS_WARN_IF(NS_FAILED(rv))) { 344 return rv; 345 } 346 result = result && CheckProgIDExists(extraProgID.get()); 347 348 // "FirefoxHTML". 349 rv = GetMsixProgId(L".htm", extraProgID); 350 if (NS_WARN_IF(NS_FAILED(rv))) { 351 return rv; 352 } 353 result = result && CheckProgIDExists(extraProgID.get()); 354 355 // "FirefoxPDF". 356 rv = GetMsixProgId(L".pdf", extraProgID); 357 if (NS_WARN_IF(NS_FAILED(rv))) { 358 return rv; 359 } 360 result = result && CheckProgIDExists(extraProgID.get()); 361 362 *aResult = result; 363 } else { 364 *aResult = 365 CheckProgIDExists(FormatProgID(L"FirefoxURL", aumid.get()).get()) && 366 CheckProgIDExists(FormatProgID(L"FirefoxHTML", aumid.get()).get()) && 367 CheckProgIDExists(FormatProgID(L"FirefoxPDF", aumid.get()).get()); 368 } 369 370 return NS_OK; 371 } 372 373 NS_IMETHODIMP 374 nsWindowsShellService::CheckBrowserUserChoiceHashes(bool* aResult) { 375 *aResult = ::CheckBrowserUserChoiceHashes(); 376 return NS_OK; 377 } 378 379 NS_IMETHODIMP 380 nsWindowsShellService::CheckCurrentProcessAUMIDForTesting( 381 nsAString& aRetAumid) { 382 PWSTR id; 383 HRESULT hr = GetCurrentProcessExplicitAppUserModelID(&id); 384 385 if (FAILED(hr)) { 386 // Process AUMID may not be set on MSIX builds, 387 // if so we should return a dummy value 388 if (widget::WinUtils::HasPackageIdentity()) { 389 aRetAumid.Assign(u"MSIXAumidTestValue"_ns); 390 return NS_OK; 391 } 392 return NS_ERROR_FAILURE; 393 } 394 aRetAumid.Assign(id); 395 CoTaskMemFree(id); 396 397 return NS_OK; 398 } 399 400 NS_IMETHODIMP 401 nsWindowsShellService::CanSetDefaultBrowserUserChoice(bool* aResult) { 402 *aResult = false; 403 // If the WDBA is not available, this could never succeed. 404 #ifdef MOZ_DEFAULT_BROWSER_AGENT 405 bool progIDsExist = false; 406 bool hashOk = false; 407 *aResult = NS_SUCCEEDED(CheckAllProgIDsExist(&progIDsExist)) && 408 progIDsExist && 409 NS_SUCCEEDED(CheckBrowserUserChoiceHashes(&hashOk)) && hashOk; 410 #endif 411 return NS_OK; 412 } 413 414 nsresult nsWindowsShellService::LaunchModernSettingsDialogDefaultApps() { 415 return ::LaunchModernSettingsDialogDefaultApps() ? NS_OK : NS_ERROR_FAILURE; 416 } 417 418 NS_IMETHODIMP 419 nsWindowsShellService::SetDefaultBrowser(bool aForAllUsers) { 420 // If running from within a package, don't attempt to set default with 421 // the helper, as it will not work and will only confuse our package's 422 // virtualized registry. 423 nsresult rv = NS_OK; 424 if (!widget::WinUtils::HasPackageIdentity()) { 425 nsAutoString appHelperPath; 426 if (NS_FAILED(GetHelperPath(appHelperPath))) return NS_ERROR_FAILURE; 427 428 if (aForAllUsers) { 429 appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal"); 430 } else { 431 appHelperPath.AppendLiteral(" /SetAsDefaultAppUser"); 432 } 433 434 rv = LaunchHelper(appHelperPath); 435 } 436 437 if (NS_SUCCEEDED(rv)) { 438 rv = LaunchModernSettingsDialogDefaultApps(); 439 // The above call should never really fail, but just in case 440 // fall back to showing control panel for all defaults 441 if (NS_FAILED(rv)) { 442 rv = LaunchControlPanelDefaultsSelectionUI(); 443 } 444 } 445 446 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); 447 if (prefs) { 448 (void)prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true); 449 // Reset the number of times the dialog should be shown 450 // before it is silenced. 451 (void)prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0); 452 } 453 454 return rv; 455 } 456 457 /* 458 * Asynchronous function to Write an ico file to the disk / in a nsIFile. 459 * Limitation: Only square images are supported as of now. 460 */ 461 NS_IMETHODIMP 462 nsWindowsShellService::CreateWindowsIcon(nsIFile* aIcoFile, 463 imgIContainer* aImage, JSContext* aCx, 464 dom::Promise** aPromise) { 465 NS_ENSURE_ARG_POINTER(aIcoFile); 466 NS_ENSURE_ARG_POINTER(aImage); 467 NS_ENSURE_ARG_POINTER(aCx); 468 NS_ENSURE_ARG_POINTER(aPromise); 469 470 if (!NS_IsMainThread()) { 471 return NS_ERROR_NOT_SAME_THREAD; 472 } 473 474 ErrorResult rv; 475 RefPtr<dom::Promise> promise = 476 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 477 478 if (MOZ_UNLIKELY(rv.Failed())) { 479 return rv.StealNSResult(); 480 } 481 482 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 483 "CreateWindowsIcon promise", promise); 484 485 MOZ_LOG(sLog, LogLevel::Debug, 486 ("%s:%d - Reading input image...\n", __FILE__, __LINE__)); 487 488 // At present SVG frame retrieval defaults to 16x16, which will result in 489 // small bordered icon in some contexts icons are used - e.g. pin to taskbar 490 // notifications. To prevent this we retrieve the frame at 256x256. This only 491 // works for SVGs, raster `imgIContainer` formats instead select the closest 492 // matching size from existing frames. 493 RefPtr<gfx::SourceSurface> surface = 494 aImage->GetFrameAtSize(nsIntSize(256, 256), imgIContainer::FRAME_FIRST, 495 imgIContainer::FLAG_SYNC_DECODE | 496 imgIContainer::FLAG_HIGH_QUALITY_SCALING); 497 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); 498 499 // At time of writing only `DataSourceSurface` was guaranteed thread safe. We 500 // need this guarantee to write the icon file off the main thread. 501 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface(); 502 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); 503 504 MOZ_LOG(sLog, LogLevel::Debug, 505 ("%s:%d - Surface found, writing icon... \n", __FILE__, __LINE__)); 506 507 NS_DispatchBackgroundTask( 508 NS_NewRunnableFunction( 509 "CreateWindowsIcon", 510 [icoFile = nsCOMPtr<nsIFile>(aIcoFile), dataSurface, promiseHolder] { 511 nsresult rv = WriteIcon(icoFile, dataSurface); 512 513 NS_DispatchToMainThread(NS_NewRunnableFunction( 514 "CreateWindowsIcon callback", [rv, promiseHolder] { 515 dom::Promise* promise = promiseHolder.get()->get(); 516 517 if (NS_SUCCEEDED(rv)) { 518 promise->MaybeResolveWithUndefined(); 519 } else { 520 promise->MaybeReject(rv); 521 } 522 })); 523 }), 524 NS_DISPATCH_EVENT_MAY_BLOCK); 525 526 promise.forget(aPromise); 527 return NS_OK; 528 } 529 530 static nsresult WriteIcon(nsIFile* aIcoFile, gfx::DataSourceSurface* aSurface) { 531 NS_ENSURE_ARG(aIcoFile); 532 NS_ENSURE_ARG(aSurface); 533 534 const gfx::IntSize size = aSurface->GetSize(); 535 if (size.IsEmpty()) { 536 MOZ_LOG(sLog, LogLevel::Debug, 537 ("%s:%d - The input image looks empty :(\n", __FILE__, __LINE__)); 538 return NS_ERROR_FAILURE; 539 } 540 541 int32_t width = aSurface->GetSize().width; 542 int32_t height = aSurface->GetSize().height; 543 544 MOZ_LOG(sLog, LogLevel::Debug, 545 ("%s:%d - Input image dimensions are: %dx%d pixels\n", __FILE__, 546 __LINE__, width, height)); 547 548 NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE); 549 NS_ENSURE_TRUE(width > 0, NS_ERROR_FAILURE); 550 NS_ENSURE_TRUE(width == height, NS_ERROR_FAILURE); 551 552 MOZ_LOG(sLog, LogLevel::Debug, 553 ("%s:%d - Opening file for writing...\n", __FILE__, __LINE__)); 554 555 ScopedCloseFile file; 556 nsresult rv = aIcoFile->OpenANSIFileDesc("wb", getter_Transfers(file)); 557 NS_ENSURE_SUCCESS(rv, rv); 558 559 MOZ_LOG(sLog, LogLevel::Debug, 560 ("%s:%d - Writing icon...\n", __FILE__, __LINE__)); 561 562 rv = gfxUtils::EncodeSourceSurface(aSurface, ImageType::ICO, u""_ns, 563 gfxUtils::eBinaryEncode, file.get()); 564 565 if (NS_FAILED(rv)) { 566 MOZ_LOG(sLog, LogLevel::Debug, 567 ("%s:%d - Could not write the icon!\n", __FILE__, __LINE__)); 568 return rv; 569 } 570 571 MOZ_LOG(sLog, LogLevel::Debug, 572 ("%s:%d - Icon written!\n", __FILE__, __LINE__)); 573 return NS_OK; 574 } 575 576 static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage) { 577 nsresult rv; 578 579 RefPtr<gfx::SourceSurface> surface = aImage->GetFrame( 580 imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE); 581 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); 582 583 // For either of the following formats we want to set the biBitCount member 584 // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap 585 // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored 586 // for the BI_RGB value we use for the biCompression member. 587 MOZ_ASSERT(surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8 || 588 surface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8); 589 590 RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface(); 591 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); 592 593 int32_t width = dataSurface->GetSize().width; 594 int32_t height = dataSurface->GetSize().height; 595 int32_t bytesPerPixel = 4 * sizeof(uint8_t); 596 uint32_t bytesPerRow = bytesPerPixel * width; 597 598 // initialize these bitmap structs which we will later 599 // serialize directly to the head of the bitmap file 600 BITMAPINFOHEADER bmi; 601 bmi.biSize = sizeof(BITMAPINFOHEADER); 602 bmi.biWidth = width; 603 bmi.biHeight = height; 604 bmi.biPlanes = 1; 605 bmi.biBitCount = (WORD)bytesPerPixel * 8; 606 bmi.biCompression = BI_RGB; 607 bmi.biSizeImage = bytesPerRow * height; 608 bmi.biXPelsPerMeter = 0; 609 bmi.biYPelsPerMeter = 0; 610 bmi.biClrUsed = 0; 611 bmi.biClrImportant = 0; 612 613 BITMAPFILEHEADER bf; 614 bf.bfType = 0x4D42; // 'BM' 615 bf.bfReserved1 = 0; 616 bf.bfReserved2 = 0; 617 bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); 618 bf.bfSize = bf.bfOffBits + bmi.biSizeImage; 619 620 // get a file output stream 621 nsCOMPtr<nsIOutputStream> stream; 622 rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile); 623 NS_ENSURE_SUCCESS(rv, rv); 624 625 // (redundant) guard clause to assert stream is initialized 626 if (NS_WARN_IF(!stream)) { 627 MOZ_ASSERT(stream, "rv should have failed when stream is not initialized."); 628 return NS_ERROR_FAILURE; 629 } 630 631 gfx::DataSourceSurface::MappedSurface map; 632 if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) { 633 // removal of file created handled later 634 rv = NS_ERROR_FAILURE; 635 } 636 637 // enter only if datasurface mapping succeeded 638 if (NS_SUCCEEDED(rv)) { 639 // write the bitmap headers and rgb pixel data to the file 640 uint32_t written; 641 rv = stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written); 642 if (NS_SUCCEEDED(rv)) { 643 rv = stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written); 644 if (NS_SUCCEEDED(rv)) { 645 // write out the image data backwards because the desktop won't 646 // show bitmaps with negative heights for top-to-bottom 647 uint32_t i = map.mStride * height; 648 do { 649 i -= map.mStride; 650 rv = stream->Write(((const char*)map.mData) + i, bytesPerRow, 651 &written); 652 if (NS_FAILED(rv)) { 653 break; 654 } 655 } while (i != 0); 656 } 657 } 658 659 dataSurface->Unmap(); 660 } 661 662 stream->Close(); 663 664 // Obtaining the file output stream results in a newly created file or 665 // truncates the file if it already exists. As such, it is necessary to 666 // remove the file if the write fails for some reason. 667 if (NS_FAILED(rv)) { 668 if (NS_WARN_IF(NS_FAILED(aFile->Remove(PR_FALSE)))) { 669 MOZ_LOG(sLog, LogLevel::Warning, 670 ("Failed to remove empty bitmap file : %s", 671 aFile->HumanReadablePath().get())); 672 } 673 } 674 675 return rv; 676 } 677 678 NS_IMETHODIMP 679 nsWindowsShellService::SetDesktopBackground(dom::Element* aElement, 680 int32_t aPosition, 681 const nsACString& aImageName) { 682 if (!aElement || !aElement->IsHTMLElement(nsGkAtoms::img)) { 683 // XXX write background loading stuff! 684 return NS_ERROR_NOT_AVAILABLE; 685 } 686 687 nsresult rv; 688 nsCOMPtr<nsIImageLoadingContent> imageContent = 689 do_QueryInterface(aElement, &rv); 690 if (!imageContent) return rv; 691 692 // get the image container 693 nsCOMPtr<imgIRequest> request; 694 rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, 695 getter_AddRefs(request)); 696 if (!request) return rv; 697 698 nsCOMPtr<imgIContainer> container; 699 rv = request->GetImage(getter_AddRefs(container)); 700 if (!container) return NS_ERROR_FAILURE; 701 702 // get the file name from localized strings, e.g. "Desktop Background", then 703 // append the extension (".bmp"). 704 nsTArray<nsCString> resIds = { 705 "browser/setDesktopBackground.ftl"_ns, 706 }; 707 RefPtr<Localization> l10n = Localization::Create(resIds, true); 708 nsAutoCString fileLeafNameUtf8; 709 IgnoredErrorResult locRv; 710 l10n->FormatValueSync("set-desktop-background-filename"_ns, {}, 711 fileLeafNameUtf8, locRv); 712 nsAutoString fileLeafName = NS_ConvertUTF8toUTF16(fileLeafNameUtf8); 713 fileLeafName.AppendLiteral(".bmp"); 714 715 // get the profile root directory 716 nsCOMPtr<nsIFile> file; 717 rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR, 718 getter_AddRefs(file)); 719 NS_ENSURE_SUCCESS(rv, rv); 720 721 // eventually, the path is "%APPDATA%\Mozilla\Firefox\Desktop Background.bmp" 722 rv = file->Append(fileLeafName); 723 NS_ENSURE_SUCCESS(rv, rv); 724 725 nsAutoString path; 726 rv = file->GetPath(path); 727 NS_ENSURE_SUCCESS(rv, rv); 728 729 // write the bitmap to a file in the profile directory. 730 // We have to write old bitmap format for Windows 7 wallpaper support. 731 rv = WriteBitmap(file, container); 732 733 // if the file was written successfully, set it as the system wallpaper 734 if (NS_SUCCEEDED(rv)) { 735 nsCOMPtr<nsIWindowsRegKey> regKey = 736 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); 737 NS_ENSURE_SUCCESS(rv, rv); 738 739 rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, 740 u"Control Panel\\Desktop"_ns, 741 nsIWindowsRegKey::ACCESS_SET_VALUE); 742 NS_ENSURE_SUCCESS(rv, rv); 743 744 nsAutoString tile; 745 nsAutoString style; 746 switch (aPosition) { 747 case BACKGROUND_TILE: 748 style.Assign('0'); 749 tile.Assign('1'); 750 break; 751 case BACKGROUND_CENTER: 752 style.Assign('0'); 753 tile.Assign('0'); 754 break; 755 case BACKGROUND_STRETCH: 756 style.Assign('2'); 757 tile.Assign('0'); 758 break; 759 case BACKGROUND_FILL: 760 style.AssignLiteral("10"); 761 tile.Assign('0'); 762 break; 763 case BACKGROUND_FIT: 764 style.Assign('6'); 765 tile.Assign('0'); 766 break; 767 case BACKGROUND_SPAN: 768 style.AssignLiteral("22"); 769 tile.Assign('0'); 770 break; 771 } 772 773 rv = regKey->WriteStringValue(u"TileWallpaper"_ns, tile); 774 NS_ENSURE_SUCCESS(rv, rv); 775 rv = regKey->WriteStringValue(u"WallpaperStyle"_ns, style); 776 NS_ENSURE_SUCCESS(rv, rv); 777 rv = regKey->Close(); 778 NS_ENSURE_SUCCESS(rv, rv); 779 780 ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(), 781 SPIF_UPDATEINIFILE | SPIF_SENDCHANGE); 782 } 783 return rv; 784 } 785 786 NS_IMETHODIMP 787 nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor) { 788 uint32_t color = ::GetSysColor(COLOR_DESKTOP); 789 *aColor = 790 (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color); 791 return NS_OK; 792 } 793 794 NS_IMETHODIMP 795 nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor) { 796 int aParameters[2] = {COLOR_BACKGROUND, COLOR_DESKTOP}; 797 BYTE r = (aColor >> 16); 798 BYTE g = (aColor << 16) >> 24; 799 BYTE b = (aColor << 24) >> 24; 800 COLORREF colors[2] = {RGB(r, g, b), RGB(r, g, b)}; 801 802 ::SetSysColors(sizeof(aParameters) / sizeof(int), aParameters, colors); 803 804 nsresult rv; 805 nsCOMPtr<nsIWindowsRegKey> regKey = 806 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); 807 NS_ENSURE_SUCCESS(rv, rv); 808 809 rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, 810 u"Control Panel\\Colors"_ns, 811 nsIWindowsRegKey::ACCESS_SET_VALUE); 812 NS_ENSURE_SUCCESS(rv, rv); 813 814 wchar_t rgb[12]; 815 _snwprintf(rgb, 12, L"%u %u %u", r, g, b); 816 817 rv = regKey->WriteStringValue(u"Background"_ns, nsDependentString(rgb)); 818 NS_ENSURE_SUCCESS(rv, rv); 819 820 return regKey->Close(); 821 } 822 823 enum class ShortcutsLogChange { 824 Add, 825 Remove, 826 }; 827 828 /* 829 * Updates information about a shortcut to a shortcuts log in 830 * %PROGRAMDATA%\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38. 831 * (This is the same directory used for update staging.) 832 * For more on the shortcuts log format and purpose, consult 833 * /toolkit/mozapps/installer/windows/nsis/common.nsh. 834 * 835 * The shortcuts log modified here is named after the currently 836 * running application and current user SID. For example: 837 * Firefox_$SID_shortcuts.ini. 838 * 839 * A new file will be created when the first shortcut is added. 840 * If a matching shortcut already exists, a new one will not 841 * be appended. The file will not be deleted if the last one is 842 * removed. 843 * 844 * In an ideal world this function would not need aShortcutsLogDir 845 * passed to it, but it is called by at least one function that runs 846 * asynchronously, and is therefore unable to use nsDirectoryService 847 * to look it up itself. 848 */ 849 static nsresult UpdateShortcutInLog(nsIFile* aShortcutsLogDir, 850 KNOWNFOLDERID aFolderId, 851 ShortcutsLogChange aChange, 852 const nsAString& aShortcutRelativePath) { 853 // the section inside the shortcuts log 854 nsAutoCString section; 855 // the shortcuts log wants "Programs" shortcuts in its "STARTMENU" section 856 if (aFolderId == FOLDERID_CommonPrograms || aFolderId == FOLDERID_Programs) { 857 section.Assign("STARTMENU"); 858 } else if (aFolderId == FOLDERID_PublicDesktop || 859 aFolderId == FOLDERID_Desktop) { 860 section.Assign("DESKTOP"); 861 } else { 862 return NS_ERROR_INVALID_ARG; 863 } 864 865 nsCOMPtr<nsIFile> shortcutsLog; 866 nsresult rv = aShortcutsLogDir->GetParent(getter_AddRefs(shortcutsLog)); 867 NS_ENSURE_SUCCESS(rv, rv); 868 869 nsAutoCString appName; 870 nsCOMPtr<nsIXULAppInfo> appInfo = 871 do_GetService("@mozilla.org/xre/app-info;1"); 872 rv = appInfo->GetName(appName); 873 NS_ENSURE_SUCCESS(rv, rv); 874 875 auto userSid = GetCurrentUserStringSid(); 876 if (!userSid) { 877 return NS_ERROR_FILE_NOT_FOUND; 878 } 879 880 nsAutoString filename; 881 filename.AppendPrintf("%s_%ls_shortcuts.ini", appName.get(), userSid.get()); 882 rv = shortcutsLog->Append(filename); 883 NS_ENSURE_SUCCESS(rv, rv); 884 885 nsINIParser parser; 886 bool shortcutsLogEntryExists = false; 887 nsAutoCString keyName, shortcutRelativePath, iniShortcut; 888 889 shortcutRelativePath = NS_ConvertUTF16toUTF8(aShortcutRelativePath); 890 891 // Last key that was valid. 892 nsAutoCString lastValidKey; 893 // Last key where the filename was found. 894 nsAutoCString fileFoundAtKeyName; 895 896 // If the shortcuts log exists, find either an existing matching 897 // entry, or the next available shortcut index. 898 rv = parser.Init(shortcutsLog); 899 if (NS_SUCCEEDED(rv)) { 900 for (int i = 0;; i++) { 901 keyName.AssignLiteral("Shortcut"); 902 keyName.AppendInt(i); 903 rv = parser.GetString(section.get(), keyName.get(), iniShortcut); 904 if (NS_FAILED(rv) && rv != NS_ERROR_FAILURE) { 905 return rv; 906 } 907 908 if (rv == NS_ERROR_FAILURE) { 909 // This is the end of the file (as far as we're concerned.) 910 break; 911 } else if (iniShortcut.Equals(shortcutRelativePath)) { 912 shortcutsLogEntryExists = true; 913 fileFoundAtKeyName = keyName; 914 } 915 916 lastValidKey = keyName; 917 } 918 } else if (rv == NS_ERROR_FILE_NOT_FOUND) { 919 // If the file doesn't exist, then start at Shortcut0. 920 // When removing, this does nothing; when adding, this is always 921 // a safe place to start. 922 keyName.AssignLiteral("Shortcut0"); 923 } else { 924 return rv; 925 } 926 927 bool changed = false; 928 if (aChange == ShortcutsLogChange::Add && !shortcutsLogEntryExists) { 929 parser.SetString(section.get(), keyName.get(), shortcutRelativePath.get()); 930 changed = true; 931 } else if (aChange == ShortcutsLogChange::Remove && shortcutsLogEntryExists) { 932 // Don't just remove it! The first missing index is considered 933 // the end of the log. Instead, move the last one in, then delete 934 // the last one, reducing the total length by one. 935 parser.SetString(section.get(), fileFoundAtKeyName.get(), 936 iniShortcut.get()); 937 parser.DeleteString(section.get(), lastValidKey.get()); 938 changed = true; 939 } 940 941 if (changed) { 942 // We write this ourselves instead of using parser->WriteToFile because 943 // the INI parser in our uninstaller needs to read this, and only supports 944 // UTF-16LE encoding. nsINIParser does not support UTF-16. 945 nsAutoCString formatted; 946 parser.WriteToString(formatted); 947 FILE* writeFile; 948 rv = shortcutsLog->OpenANSIFileDesc("w,ccs=UTF-16LE", &writeFile); 949 NS_ENSURE_SUCCESS(rv, rv); 950 NS_ConvertUTF8toUTF16 formattedUTF16(formatted); 951 if (fwrite(formattedUTF16.get(), sizeof(wchar_t), formattedUTF16.Length(), 952 writeFile) != formattedUTF16.Length()) { 953 fclose(writeFile); 954 return NS_ERROR_FAILURE; 955 } 956 fclose(writeFile); 957 } 958 959 return NS_OK; 960 } 961 962 nsresult CreateShellLinkObject(nsIFile* aBinary, 963 const CopyableTArray<nsString>& aArguments, 964 const nsAString& aDescription, 965 nsIFile* aIconFile, uint16_t aIconIndex, 966 const nsAString& aAppUserModelId, 967 IShellLinkW** aLink) { 968 RefPtr<IShellLinkW> link; 969 HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, 970 IID_IShellLinkW, getter_AddRefs(link)); 971 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 972 973 nsString path(aBinary->NativePath()); 974 link->SetPath(path.get()); 975 976 wchar_t workingDir[MAX_PATH + 1]; 977 wcscpy_s(workingDir, MAX_PATH + 1, aBinary->NativePath().get()); 978 PathRemoveFileSpecW(workingDir); 979 link->SetWorkingDirectory(workingDir); 980 981 if (!aDescription.IsEmpty()) { 982 link->SetDescription(PromiseFlatString(aDescription).get()); 983 } 984 985 // TODO: Properly escape quotes in the string, see bug 1604287. 986 nsString arguments; 987 for (const auto& arg : aArguments) { 988 arguments += u"\""_ns + arg + u"\" "_ns; 989 } 990 991 link->SetArguments(arguments.get()); 992 993 if (aIconFile) { 994 nsString icon(aIconFile->NativePath()); 995 link->SetIconLocation(icon.get(), aIconIndex); 996 } 997 998 if (!aAppUserModelId.IsEmpty()) { 999 RefPtr<IPropertyStore> propStore; 1000 hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore)); 1001 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 1002 1003 PROPVARIANT pv; 1004 if (FAILED(InitPropVariantFromString( 1005 PromiseFlatString(aAppUserModelId).get(), &pv))) { 1006 return NS_ERROR_FAILURE; 1007 } 1008 1009 hr = propStore->SetValue(PKEY_AppUserModel_ID, pv); 1010 PropVariantClear(&pv); 1011 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 1012 1013 hr = propStore->Commit(); 1014 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 1015 } 1016 1017 link.forget(aLink); 1018 return NS_OK; 1019 } 1020 1021 struct ShortcutLocations { 1022 KNOWNFOLDERID folderId; 1023 nsCOMPtr<nsIFile> shortcutsLogDir; 1024 nsCOMPtr<nsIFile> shortcutFile; 1025 }; 1026 1027 static nsresult CreateShortcutImpl(nsIFile* aBinary, 1028 const CopyableTArray<nsString>& aArguments, 1029 const nsAString& aDescription, 1030 nsIFile* aIconFile, uint16_t aIconIndex, 1031 const nsAString& aAppUserModelId, 1032 const ShortcutLocations& location, 1033 const nsAString& aShortcutRelativePath) { 1034 NS_ENSURE_ARG(aBinary); 1035 NS_ENSURE_ARG(aIconFile); 1036 1037 nsresult rv = 1038 UpdateShortcutInLog(location.shortcutsLogDir, location.folderId, 1039 ShortcutsLogChange::Add, aShortcutRelativePath); 1040 NS_ENSURE_SUCCESS(rv, rv); 1041 1042 RefPtr<IShellLinkW> link; 1043 rv = CreateShellLinkObject(aBinary, aArguments, aDescription, aIconFile, 1044 aIconIndex, aAppUserModelId, getter_AddRefs(link)); 1045 NS_ENSURE_SUCCESS(rv, rv); 1046 1047 RefPtr<IPersistFile> persist; 1048 HRESULT hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist)); 1049 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 1050 1051 hr = persist->Save(location.shortcutFile->NativePath().get(), TRUE); 1052 NS_ENSURE_HRESULT(hr, NS_ERROR_FAILURE); 1053 1054 return NS_OK; 1055 } 1056 1057 static Result<ShortcutLocations, nsresult> GetShortcutPaths( 1058 const nsAString& aShortcutFolder, const nsAString& aShortcutRelativePath) { 1059 KNOWNFOLDERID folderId; 1060 if (aShortcutFolder.Equals(L"Programs")) { 1061 folderId = FOLDERID_Programs; 1062 } else if (aShortcutFolder.Equals(L"Desktop")) { 1063 folderId = FOLDERID_Desktop; 1064 } else { 1065 return Err(NS_ERROR_INVALID_ARG); 1066 } 1067 1068 nsCOMPtr<nsIFile> updRoot, shortcutsLogDir; 1069 nsresult nsrv = 1070 NS_GetSpecialDirectory(XRE_UPDATE_ROOT_DIR, getter_AddRefs(updRoot)); 1071 NS_ENSURE_SUCCESS(nsrv, Err(nsrv)); 1072 nsrv = updRoot->GetParent(getter_AddRefs(shortcutsLogDir)); 1073 NS_ENSURE_SUCCESS(nsrv, Err(nsrv)); 1074 1075 nsCOMPtr<nsIFile> shortcutFile; 1076 if (folderId == FOLDERID_Programs) { 1077 nsrv = NS_GetSpecialDirectory(NS_WIN_PROGRAMS_DIR, 1078 getter_AddRefs(shortcutFile)); 1079 } else if (folderId == FOLDERID_Desktop) { 1080 nsrv = 1081 NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(shortcutFile)); 1082 } else { 1083 return Err(NS_ERROR_FILE_NOT_FOUND); 1084 } 1085 1086 if (NS_FAILED(nsrv)) { 1087 return Err(NS_ERROR_FILE_NOT_FOUND); 1088 } 1089 1090 nsrv = shortcutFile->AppendRelativePath(aShortcutRelativePath); 1091 NS_ENSURE_SUCCESS(nsrv, Err(nsrv)); 1092 1093 ShortcutLocations result{}; 1094 result.folderId = folderId; 1095 result.shortcutsLogDir = std::move(shortcutsLogDir); 1096 result.shortcutFile = std::move(shortcutFile); 1097 return result; 1098 } 1099 1100 NS_IMETHODIMP 1101 nsWindowsShellService::CreateShortcut(nsIFile* aBinary, 1102 const nsTArray<nsString>& aArguments, 1103 const nsAString& aDescription, 1104 nsIFile* aIconFile, uint16_t aIconIndex, 1105 const nsAString& aAppUserModelId, 1106 const nsAString& aShortcutFolder, 1107 const nsAString& aShortcutRelativePath, 1108 JSContext* aCx, dom::Promise** aPromise) { 1109 if (!NS_IsMainThread()) { 1110 return NS_ERROR_NOT_SAME_THREAD; 1111 } 1112 1113 ErrorResult rv; 1114 RefPtr<dom::Promise> promise = 1115 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 1116 1117 if (MOZ_UNLIKELY(rv.Failed())) { 1118 return rv.StealNSResult(); 1119 } 1120 1121 ShortcutLocations location = 1122 MOZ_TRY(GetShortcutPaths(aShortcutFolder, aShortcutRelativePath)); 1123 1124 nsCOMPtr<nsIFile> parentDirectory; 1125 nsresult nsrv; 1126 nsrv = location.shortcutFile->GetParent(getter_AddRefs(parentDirectory)); 1127 NS_ENSURE_SUCCESS(nsrv, nsrv); 1128 nsrv = parentDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); 1129 if (NS_FAILED(nsrv) && nsrv != NS_ERROR_FILE_ALREADY_EXISTS) { 1130 return nsrv; 1131 } 1132 1133 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 1134 "CreateShortcut promise", promise); 1135 1136 nsCOMPtr<nsIFile> binary(aBinary); 1137 nsCOMPtr<nsIFile> iconFile(aIconFile); 1138 NS_DispatchBackgroundTask( 1139 NS_NewRunnableFunction( 1140 "CreateShortcut", 1141 [binary, aArguments = CopyableTArray<nsString>(aArguments), 1142 aDescription = nsString{aDescription}, iconFile, aIconIndex, 1143 aAppUserModelId = nsString{aAppUserModelId}, 1144 location = std::move(location), 1145 aShortcutFolder = nsString{aShortcutFolder}, 1146 aShortcutRelativePath = nsString{aShortcutRelativePath}, 1147 promiseHolder = std::move(promiseHolder)] { 1148 nsresult rv = CreateShortcutImpl( 1149 binary.get(), aArguments, aDescription, iconFile.get(), 1150 aIconIndex, aAppUserModelId, location, aShortcutRelativePath); 1151 1152 NS_DispatchToMainThread(NS_NewRunnableFunction( 1153 "CreateShortcut callback", 1154 [rv, shortcutFile = location.shortcutFile, 1155 promiseHolder = std::move(promiseHolder)] { 1156 dom::Promise* promise = promiseHolder.get()->get(); 1157 1158 if (NS_SUCCEEDED(rv)) { 1159 promise->MaybeResolve(shortcutFile->NativePath()); 1160 } else { 1161 promise->MaybeReject(rv); 1162 } 1163 })); 1164 }), 1165 NS_DISPATCH_EVENT_MAY_BLOCK); 1166 1167 promise.forget(aPromise); 1168 return NS_OK; 1169 } 1170 1171 static nsresult DeleteShortcutImpl(const ShortcutLocations& aLocation, 1172 const nsAString& aShortcutRelativePath) { 1173 // Do the removal first so an error keeps it in the log. 1174 nsresult rv = aLocation.shortcutFile->Remove(false); 1175 NS_ENSURE_SUCCESS(rv, rv); 1176 1177 rv = UpdateShortcutInLog(aLocation.shortcutsLogDir, aLocation.folderId, 1178 ShortcutsLogChange::Remove, aShortcutRelativePath); 1179 NS_ENSURE_SUCCESS(rv, rv); 1180 1181 return NS_OK; 1182 } 1183 1184 NS_IMETHODIMP 1185 nsWindowsShellService::DeleteShortcut(const nsAString& aShortcutFolder, 1186 const nsAString& aShortcutRelativePath, 1187 JSContext* aCx, dom::Promise** aPromise) { 1188 if (!NS_IsMainThread()) { 1189 return NS_ERROR_NOT_SAME_THREAD; 1190 } 1191 1192 ErrorResult rv; 1193 RefPtr<dom::Promise> promise = 1194 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 1195 1196 if (MOZ_UNLIKELY(rv.Failed())) { 1197 return rv.StealNSResult(); 1198 } 1199 1200 ShortcutLocations location = 1201 MOZ_TRY(GetShortcutPaths(aShortcutFolder, aShortcutRelativePath)); 1202 1203 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 1204 "DeleteShortcut promise", promise); 1205 1206 NS_DispatchBackgroundTask( 1207 NS_NewRunnableFunction( 1208 "DeleteShortcut", 1209 [aShortcutFolder = nsString{aShortcutFolder}, 1210 aShortcutRelativePath = nsString{aShortcutRelativePath}, 1211 location = std::move(location), 1212 promiseHolder = std::move(promiseHolder)] { 1213 nsresult rv = DeleteShortcutImpl(location, aShortcutRelativePath); 1214 1215 NS_DispatchToMainThread(NS_NewRunnableFunction( 1216 "DeleteShortcut callback", 1217 [rv, shortcutFile = location.shortcutFile, 1218 promiseHolder = std::move(promiseHolder)] { 1219 dom::Promise* promise = promiseHolder.get()->get(); 1220 1221 if (NS_SUCCEEDED(rv)) { 1222 promise->MaybeResolve(shortcutFile->NativePath()); 1223 } else { 1224 promise->MaybeReject(rv); 1225 } 1226 })); 1227 }), 1228 NS_DISPATCH_EVENT_MAY_BLOCK); 1229 1230 promise.forget(aPromise); 1231 return NS_OK; 1232 } 1233 1234 NS_IMETHODIMP 1235 nsWindowsShellService::GetLaunchOnLoginShortcuts( 1236 nsTArray<nsString>& aShortcutPaths) { 1237 aShortcutPaths.Clear(); 1238 1239 // Get AppData\\Roaming folder using a known folder ID 1240 RefPtr<IKnownFolderManager> fManager; 1241 RefPtr<IKnownFolder> roamingAppData; 1242 LPWSTR roamingAppDataW; 1243 nsString roamingAppDataNS; 1244 HRESULT hr = 1245 CoCreateInstance(CLSID_KnownFolderManager, nullptr, CLSCTX_INPROC_SERVER, 1246 IID_IKnownFolderManager, getter_AddRefs(fManager)); 1247 if (FAILED(hr)) { 1248 return NS_ERROR_ABORT; 1249 } 1250 fManager->GetFolder(FOLDERID_RoamingAppData, 1251 roamingAppData.StartAssignment()); 1252 hr = roamingAppData->GetPath(0, &roamingAppDataW); 1253 if (FAILED(hr)) { 1254 return NS_ERROR_FILE_NOT_FOUND; 1255 } 1256 1257 // Append startup folder to AppData\\Roaming 1258 roamingAppDataNS.Assign(roamingAppDataW); 1259 CoTaskMemFree(roamingAppDataW); 1260 nsString startupFolder = 1261 roamingAppDataNS + 1262 u"\\Microsoft\\Windows\\Start Menu\\Programs\\Startup"_ns; 1263 nsString startupFolderWildcard = startupFolder + u"\\*.lnk"_ns; 1264 1265 // Get known path for binary file for later comparison with shortcuts. 1266 // Returns lowercase file path which should be fine for Windows as all 1267 // directories and files are case-insensitive by default. 1268 RefPtr<nsIFile> binFile; 1269 nsString binPath; 1270 nsresult rv = XRE_GetBinaryPath(binFile.StartAssignment()); 1271 if (FAILED(rv)) { 1272 return NS_ERROR_FAILURE; 1273 } 1274 rv = binFile->GetPath(binPath); 1275 if (FAILED(rv)) { 1276 return NS_ERROR_FILE_UNRECOGNIZED_PATH; 1277 } 1278 1279 // Check for if first file exists with a shortcut extension (.lnk) 1280 WIN32_FIND_DATAW ffd; 1281 HANDLE fileHandle = INVALID_HANDLE_VALUE; 1282 fileHandle = FindFirstFileW(startupFolderWildcard.get(), &ffd); 1283 if (fileHandle == INVALID_HANDLE_VALUE) { 1284 // This means that no files were found in the folder which 1285 // doesn't imply an error. Most of the time the user won't 1286 // have any shortcuts here. 1287 return NS_OK; 1288 } 1289 1290 do { 1291 // Extract shortcut target path from every 1292 // shortcut in the startup folder. 1293 nsString fileName(ffd.cFileName); 1294 RefPtr<IShellLinkW> link; 1295 RefPtr<IPersistFile> ppf; 1296 nsString target; 1297 target.SetLength(MAX_PATH); 1298 CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, 1299 IID_IShellLinkW, getter_AddRefs(link)); 1300 hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(ppf)); 1301 if (NS_WARN_IF(FAILED(hr))) { 1302 continue; 1303 } 1304 nsString filePath = startupFolder + u"\\"_ns + fileName; 1305 hr = ppf->Load(filePath.get(), STGM_READ); 1306 if (NS_WARN_IF(FAILED(hr))) { 1307 continue; 1308 } 1309 hr = link->GetPath(target.get(), MAX_PATH, nullptr, 0); 1310 if (NS_WARN_IF(FAILED(hr))) { 1311 continue; 1312 } 1313 1314 // If shortcut target matches known binary file value 1315 // then add the path to the shortcut as a valid 1316 // startup shortcut. This has to be a substring search as 1317 // the user could have added unknown command line arguments 1318 // to the shortcut. 1319 if (_wcsnicmp(target.get(), binPath.get(), binPath.Length()) == 0) { 1320 aShortcutPaths.AppendElement(filePath); 1321 } 1322 } while (FindNextFile(fileHandle, &ffd) != 0); 1323 FindClose(fileHandle); 1324 return NS_OK; 1325 } 1326 1327 // Look for any installer-created shortcuts in the given location that match 1328 // the given AUMID and EXE Path. If one is found, output its path. 1329 // 1330 // NOTE: DO NOT USE if a false negative (mismatch) is unacceptable. 1331 // aExePath is compared directly to the path retrieved from the shortcut. 1332 // Due to the presence of symlinks or other filesystem issues, it's possible 1333 // for different paths to refer to the same file, which would cause the check 1334 // to fail. 1335 // This should rarely be an issue as we are most likely to be run from a path 1336 // written by the installer (shortcut, association, launch from installer), 1337 // which also wrote the shortcuts. But it is possible. 1338 // 1339 // aCSIDL the CSIDL of the directory to look for matching shortcuts in 1340 // aAUMID the AUMID to check for 1341 // aExePath the target exe path to check for, should be a long path where 1342 // possible 1343 // aShortcutSubstring a substring to limit which shortcuts in aCSIDL are 1344 // inspected for a match. Only shortcuts whose filename 1345 // contains this substring will be considered 1346 // aShortcutPath outparam, set to matching shortcut path if NS_OK is returned. 1347 // 1348 // Returns 1349 // NS_ERROR_FAILURE on errors before any shortcuts were loaded 1350 // NS_ERROR_FILE_NOT_FOUND if no shortcuts matching aShortcutSubstring exist 1351 // NS_ERROR_FILE_ALREADY_EXISTS if shortcuts were found but did not match 1352 // aAUMID or aExePath 1353 // NS_OK if a matching shortcut is found 1354 static nsresult GetMatchingShortcut(int aCSIDL, const nsAString& aAUMID, 1355 const wchar_t aExePath[MAXPATHLEN], 1356 const nsAString& aShortcutSubstring, 1357 /* out */ nsAutoString& aShortcutPath) { 1358 nsresult result = NS_ERROR_FAILURE; 1359 1360 wchar_t folderPath[MAX_PATH] = {}; 1361 HRESULT hr = SHGetFolderPathW(nullptr, aCSIDL, nullptr, SHGFP_TYPE_CURRENT, 1362 folderPath); 1363 if (NS_WARN_IF(FAILED(hr))) { 1364 return NS_ERROR_FAILURE; 1365 } 1366 if (wcscat_s(folderPath, MAX_PATH, L"\\") != 0) { 1367 return NS_ERROR_FAILURE; 1368 } 1369 1370 // Get list of shortcuts in aCSIDL 1371 nsAutoString pattern(folderPath); 1372 pattern.AppendLiteral("*.lnk"); 1373 1374 WIN32_FIND_DATAW findData = {}; 1375 HANDLE hFindFile = FindFirstFileW(pattern.get(), &findData); 1376 if (hFindFile == INVALID_HANDLE_VALUE) { 1377 (void)NS_WARN_IF(GetLastError() != ERROR_FILE_NOT_FOUND); 1378 return NS_ERROR_FILE_NOT_FOUND; 1379 } 1380 // Past this point we don't return until the end of the function, 1381 // when FindClose() is called. 1382 1383 // todo: improve return values here 1384 do { 1385 // Skip any that don't contain aShortcutSubstring 1386 // This is a case sensitive comparison, but that's probably fine for 1387 // the vast majority of cases -- and certainly for all the ones where 1388 // a shortcut was created by the installer. 1389 if (StrStrIW(findData.cFileName, aShortcutSubstring.Data()) == NULL) { 1390 continue; 1391 } 1392 1393 nsAutoString path(folderPath); 1394 path.Append(findData.cFileName); 1395 1396 // Create a shell link object for loading the shortcut 1397 RefPtr<IShellLinkW> link; 1398 HRESULT hr = 1399 CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, 1400 IID_IShellLinkW, getter_AddRefs(link)); 1401 if (NS_WARN_IF(FAILED(hr))) { 1402 continue; 1403 } 1404 1405 // Load 1406 RefPtr<IPersistFile> persist; 1407 hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist)); 1408 if (NS_WARN_IF(FAILED(hr))) { 1409 continue; 1410 } 1411 1412 hr = persist->Load(path.get(), STGM_READ); 1413 if (FAILED(hr)) { 1414 if (NS_WARN_IF(hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))) { 1415 // empty branch, result unchanged but warning issued 1416 } else { 1417 // If we've ever gotten past this block, result will already be 1418 // NS_ERROR_FILE_ALREADY_EXISTS, which is a more accurate error 1419 // than NS_ERROR_FILE_NOT_FOUND. 1420 if (result != NS_ERROR_FILE_ALREADY_EXISTS) { 1421 result = NS_ERROR_FILE_NOT_FOUND; 1422 } 1423 } 1424 continue; 1425 } 1426 result = NS_ERROR_FILE_ALREADY_EXISTS; 1427 1428 // Check the AUMID 1429 RefPtr<IPropertyStore> propStore; 1430 hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore)); 1431 if (NS_WARN_IF(FAILED(hr))) { 1432 continue; 1433 } 1434 1435 PROPVARIANT pv; 1436 hr = propStore->GetValue(PKEY_AppUserModel_ID, &pv); 1437 if (NS_WARN_IF(FAILED(hr))) { 1438 continue; 1439 } 1440 1441 wchar_t storedAUMID[MAX_PATH]; 1442 hr = PropVariantToString(pv, storedAUMID, MAX_PATH); 1443 PropVariantClear(&pv); 1444 if (NS_WARN_IF(FAILED(hr))) { 1445 continue; 1446 } 1447 1448 if (!aAUMID.Equals(storedAUMID)) { 1449 continue; 1450 } 1451 1452 // Check the exe path 1453 static_assert(MAXPATHLEN == MAX_PATH); 1454 wchar_t storedExePath[MAX_PATH] = {}; 1455 // With no flags GetPath gets a long path 1456 hr = link->GetPath(storedExePath, std::size(storedExePath), nullptr, 0); 1457 if (FAILED(hr) || hr == S_FALSE) { 1458 continue; 1459 } 1460 // Case insensitive path comparison 1461 if (wcsnicmp(storedExePath, aExePath, MAXPATHLEN) == 0) { 1462 aShortcutPath.Assign(path); 1463 result = NS_OK; 1464 break; 1465 } 1466 } while (FindNextFileW(hFindFile, &findData)); 1467 1468 FindClose(hFindFile); 1469 1470 return result; 1471 } 1472 1473 static nsresult FindPinnableShortcut(const nsAString& aAppUserModelId, 1474 const nsAString& aShortcutSubstring, 1475 const bool aPrivateBrowsing, 1476 nsAutoString& aShortcutPath) { 1477 wchar_t exePath[MAXPATHLEN] = {}; 1478 if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) { 1479 return NS_ERROR_FAILURE; 1480 } 1481 1482 if (aPrivateBrowsing) { 1483 if (!PathRemoveFileSpecW(exePath)) { 1484 return NS_ERROR_FAILURE; 1485 } 1486 if (!PathAppendW(exePath, L"private_browsing.exe")) { 1487 return NS_ERROR_FAILURE; 1488 } 1489 } 1490 1491 int shortcutCSIDLs[] = {CSIDL_COMMON_PROGRAMS, CSIDL_PROGRAMS}; 1492 for (int shortcutCSIDL : shortcutCSIDLs) { 1493 // GetMatchingShortcut may fail when the exe path doesn't match, even 1494 // if it refers to the same file. This should be rare, and the worst 1495 // outcome would be failure to pin, so the risk is acceptable. 1496 nsresult rv = GetMatchingShortcut(shortcutCSIDL, aAppUserModelId, exePath, 1497 aShortcutSubstring, aShortcutPath); 1498 if (NS_SUCCEEDED(rv)) { 1499 return NS_OK; 1500 } 1501 } 1502 1503 return NS_ERROR_FILE_NOT_FOUND; 1504 } 1505 1506 static bool HasPinnableShortcutImpl(const nsAString& aAppUserModelId, 1507 const bool aPrivateBrowsing, 1508 const nsAutoString& aShortcutSubstring) { 1509 // unused by us, but required 1510 nsAutoString shortcutPath; 1511 nsresult rv = FindPinnableShortcut(aAppUserModelId, aShortcutSubstring, 1512 aPrivateBrowsing, shortcutPath); 1513 if (SUCCEEDED(rv)) { 1514 return true; 1515 } 1516 1517 return false; 1518 } 1519 1520 NS_IMETHODIMP nsWindowsShellService::HasPinnableShortcut( 1521 const nsAString& aAppUserModelId, const bool aPrivateBrowsing, 1522 JSContext* aCx, dom::Promise** aPromise) { 1523 if (!NS_IsMainThread()) { 1524 return NS_ERROR_NOT_SAME_THREAD; 1525 } 1526 1527 ErrorResult rv; 1528 RefPtr<dom::Promise> promise = 1529 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 1530 1531 if (MOZ_UNLIKELY(rv.Failed())) { 1532 return rv.StealNSResult(); 1533 } 1534 1535 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 1536 "HasPinnableShortcut promise", promise); 1537 1538 NS_DispatchBackgroundTask( 1539 NS_NewRunnableFunction( 1540 "HasPinnableShortcut", 1541 [aAppUserModelId = nsString{aAppUserModelId}, aPrivateBrowsing, 1542 promiseHolder = std::move(promiseHolder)] { 1543 bool rv = false; 1544 HRESULT hr = CoInitialize(nullptr); 1545 1546 if (SUCCEEDED(hr)) { 1547 nsAutoString shortcutSubstring; 1548 shortcutSubstring.AssignLiteral(MOZ_APP_DISPLAYNAME); 1549 rv = HasPinnableShortcutImpl(aAppUserModelId, aPrivateBrowsing, 1550 shortcutSubstring); 1551 CoUninitialize(); 1552 } 1553 1554 NS_DispatchToMainThread(NS_NewRunnableFunction( 1555 "HasPinnableShortcut callback", 1556 [rv, promiseHolder = std::move(promiseHolder)] { 1557 dom::Promise* promise = promiseHolder.get()->get(); 1558 1559 promise->MaybeResolve(rv); 1560 })); 1561 }), 1562 NS_DISPATCH_EVENT_MAY_BLOCK); 1563 1564 promise.forget(aPromise); 1565 return NS_OK; 1566 } 1567 1568 static bool IsCurrentAppPinnedToTaskbarSync(const nsAString& aumid) { 1569 // Use new Windows pinning APIs to determine whether or not we're pinned. 1570 // If these fail we can safely fall back to the old method for regular 1571 // installs however MSIX will always return false. 1572 1573 // Bug 1911343: Add a check for whether we're looking for a regular pin 1574 // or PB pin based on the AUMID value once private browser pinning 1575 // is supported on MSIX. 1576 // Right now only run this check on MSIX to avoid 1577 // false positives when only private browsing is pinned. 1578 if (widget::WinUtils::HasPackageIdentity()) { 1579 auto pinWithWin11TaskbarAPIResults = 1580 IsCurrentAppPinnedToTaskbarWin11(false); 1581 switch (pinWithWin11TaskbarAPIResults.result) { 1582 case Win11PinToTaskBarResultStatus::NotPinned: 1583 return false; 1584 break; 1585 case Win11PinToTaskBarResultStatus::AlreadyPinned: 1586 return true; 1587 break; 1588 default: 1589 // Fall through to the old mechanism. 1590 // The old mechanism should continue working for non-MSIX 1591 // builds. 1592 break; 1593 } 1594 } 1595 1596 // There are two shortcut targets that we created. One always matches the 1597 // binary we're running as (eg: firefox.exe). The other is the wrapper 1598 // for launching in Private Browsing mode. We need to inspect shortcuts 1599 // that point at either of these to accurately judge whether or not 1600 // the app is pinned with the given AUMID. 1601 wchar_t exePath[MAXPATHLEN] = {}; 1602 wchar_t pbExePath[MAXPATHLEN] = {}; 1603 1604 if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) { 1605 return false; 1606 } 1607 1608 wcscpy_s(pbExePath, MAXPATHLEN, exePath); 1609 if (!PathRemoveFileSpecW(pbExePath)) { 1610 return false; 1611 } 1612 if (!PathAppendW(pbExePath, L"private_browsing.exe")) { 1613 return false; 1614 } 1615 1616 wchar_t folderChars[MAX_PATH] = {}; 1617 HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 1618 SHGFP_TYPE_CURRENT, folderChars); 1619 if (NS_WARN_IF(FAILED(hr))) { 1620 return false; 1621 } 1622 1623 nsAutoString folder; 1624 folder.Assign(folderChars); 1625 if (NS_WARN_IF(folder.IsEmpty())) { 1626 return false; 1627 } 1628 if (folder[folder.Length() - 1] != '\\') { 1629 folder.AppendLiteral("\\"); 1630 } 1631 folder.AppendLiteral( 1632 "Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar"); 1633 nsAutoString pattern; 1634 pattern.Assign(folder); 1635 pattern.AppendLiteral("\\*.lnk"); 1636 1637 WIN32_FIND_DATAW findData = {}; 1638 HANDLE hFindFile = FindFirstFileW(pattern.get(), &findData); 1639 if (hFindFile == INVALID_HANDLE_VALUE) { 1640 (void)NS_WARN_IF(GetLastError() != ERROR_FILE_NOT_FOUND); 1641 return false; 1642 } 1643 // Past this point we don't return until the end of the function, 1644 // when FindClose() is called. 1645 1646 // Check all shortcuts until a match is found 1647 bool isPinned = false; 1648 do { 1649 nsAutoString fileName; 1650 fileName.Assign(folder); 1651 fileName.AppendLiteral("\\"); 1652 fileName.Append(findData.cFileName); 1653 1654 // Create a shell link object for loading the shortcut 1655 RefPtr<IShellLinkW> link; 1656 HRESULT hr = 1657 CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, 1658 IID_IShellLinkW, getter_AddRefs(link)); 1659 if (NS_WARN_IF(FAILED(hr))) { 1660 continue; 1661 } 1662 1663 // Load 1664 RefPtr<IPersistFile> persist; 1665 hr = link->QueryInterface(IID_IPersistFile, getter_AddRefs(persist)); 1666 if (NS_WARN_IF(FAILED(hr))) { 1667 continue; 1668 } 1669 1670 hr = persist->Load(fileName.get(), STGM_READ); 1671 if (NS_WARN_IF(FAILED(hr))) { 1672 continue; 1673 } 1674 1675 // Check the exe path 1676 static_assert(MAXPATHLEN == MAX_PATH); 1677 wchar_t storedExePath[MAX_PATH] = {}; 1678 // With no flags GetPath gets a long path 1679 hr = link->GetPath(storedExePath, std::size(storedExePath), nullptr, 0); 1680 if (FAILED(hr) || hr == S_FALSE) { 1681 continue; 1682 } 1683 // Case insensitive path comparison 1684 // NOTE: Because this compares the path directly, it is possible to 1685 // have a false negative mismatch. 1686 if (wcsnicmp(storedExePath, exePath, MAXPATHLEN) == 0 || 1687 wcsnicmp(storedExePath, pbExePath, MAXPATHLEN) == 0) { 1688 RefPtr<IPropertyStore> propStore; 1689 hr = link->QueryInterface(IID_IPropertyStore, getter_AddRefs(propStore)); 1690 if (NS_WARN_IF(FAILED(hr))) { 1691 continue; 1692 } 1693 1694 PROPVARIANT pv; 1695 hr = propStore->GetValue(PKEY_AppUserModel_ID, &pv); 1696 if (NS_WARN_IF(FAILED(hr))) { 1697 continue; 1698 } 1699 1700 wchar_t storedAUMID[MAX_PATH]; 1701 hr = PropVariantToString(pv, storedAUMID, MAX_PATH); 1702 PropVariantClear(&pv); 1703 if (NS_WARN_IF(FAILED(hr))) { 1704 continue; 1705 } 1706 1707 if (aumid.Equals(storedAUMID)) { 1708 isPinned = true; 1709 break; 1710 } 1711 } 1712 } while (FindNextFileW(hFindFile, &findData)); 1713 1714 FindClose(hFindFile); 1715 1716 return isPinned; 1717 } 1718 1719 static nsresult ManageShortcutTaskbarPins(bool aCheckOnly, bool aPinType, 1720 const nsAString& aShortcutPath) { 1721 // This enum is likely only used for Windows telemetry, INT_MAX is chosen to 1722 // avoid confusion with existing uses. 1723 enum PINNEDLISTMODIFYCALLER { PLMC_INT_MAX = INT_MAX }; 1724 1725 // The types below, and the idea of using IPinnedList3::Modify, 1726 // are thanks to Gee Law <https://geelaw.blog/entries/msedge-pins/> 1727 static constexpr GUID CLSID_TaskbandPin = { 1728 0x90aa3a4e, 1729 0x1cba, 1730 0x4233, 1731 {0xb8, 0xbb, 0x53, 0x57, 0x73, 0xd4, 0x84, 0x49}}; 1732 1733 static constexpr GUID IID_IPinnedList3 = { 1734 0x0dd79ae2, 1735 0xd156, 1736 0x45d4, 1737 {0x9e, 0xeb, 0x3b, 0x54, 0x97, 0x69, 0xe9, 0x40}}; 1738 1739 struct IPinnedList3Vtbl; 1740 struct IPinnedList3 { 1741 IPinnedList3Vtbl* vtbl; 1742 }; 1743 1744 typedef ULONG STDMETHODCALLTYPE ReleaseFunc(IPinnedList3 * that); 1745 typedef HRESULT STDMETHODCALLTYPE ModifyFunc( 1746 IPinnedList3 * that, PCIDLIST_ABSOLUTE unpin, PCIDLIST_ABSOLUTE pin, 1747 PINNEDLISTMODIFYCALLER caller); 1748 1749 struct IPinnedList3Vtbl { 1750 void* QueryInterface; // 0 1751 void* AddRef; // 1 1752 ReleaseFunc* Release; // 2 1753 void* Other[13]; // 3-15 1754 ModifyFunc* Modify; // 16 1755 }; 1756 1757 struct ILFreeDeleter { 1758 void operator()(LPITEMIDLIST aPtr) { 1759 if (aPtr) { 1760 ILFree(aPtr); 1761 } 1762 } 1763 }; 1764 1765 mozilla::UniquePtr<__unaligned ITEMIDLIST, ILFreeDeleter> path( 1766 ILCreateFromPathW(nsString(aShortcutPath).get())); 1767 if (NS_WARN_IF(!path)) { 1768 return NS_ERROR_FILE_NOT_FOUND; 1769 } 1770 1771 IPinnedList3* pinnedList = nullptr; 1772 HRESULT hr = CoCreateInstance(CLSID_TaskbandPin, NULL, CLSCTX_INPROC_SERVER, 1773 IID_IPinnedList3, (void**)&pinnedList); 1774 if (FAILED(hr) || !pinnedList) { 1775 return NS_ERROR_NOT_AVAILABLE; 1776 } 1777 1778 if (!aCheckOnly) { 1779 hr = pinnedList->vtbl->Modify(pinnedList, aPinType ? NULL : path.get(), 1780 aPinType ? path.get() : NULL, PLMC_INT_MAX); 1781 } 1782 1783 pinnedList->vtbl->Release(pinnedList); 1784 1785 if (FAILED(hr)) { 1786 return NS_ERROR_FILE_ACCESS_DENIED; 1787 } 1788 return NS_OK; 1789 } 1790 1791 static nsresult PinShortcutToTaskbarImpl(bool aCheckOnly, 1792 const nsAString& aAppUserModelId, 1793 const nsAString& aShortcutPath) { 1794 // Verify shortcut is visible to `shell:appsfolder`. Shortcut creation - 1795 // during install or runtime - causes a race between it propagating to the 1796 // virtual `shell:appsfolder` and attempts to pin via `ITaskbarManager`, 1797 // resulting in pin failures when the latter occurs before the former. We can 1798 // skip this when we're in a MSIX build or only checking whether we're pinned. 1799 if (!widget::WinUtils::HasPackageIdentity() && !aCheckOnly && 1800 !PollAppsFolderForShortcut(aAppUserModelId, 1801 TimeDuration::FromSeconds(15))) { 1802 return NS_ERROR_FILE_NOT_FOUND; 1803 } 1804 1805 auto pinWithWin11TaskbarAPIResults = 1806 PinCurrentAppToTaskbarWin11(aCheckOnly, aAppUserModelId); 1807 switch (pinWithWin11TaskbarAPIResults.result) { 1808 case Win11PinToTaskBarResultStatus::NotSupported: 1809 // Fall through to the win 10 mechanism 1810 break; 1811 1812 case Win11PinToTaskBarResultStatus::Success: 1813 case Win11PinToTaskBarResultStatus::AlreadyPinned: 1814 return NS_OK; 1815 1816 case Win11PinToTaskBarResultStatus::NotPinned: 1817 case Win11PinToTaskBarResultStatus::NotCurrentlyAllowed: 1818 case Win11PinToTaskBarResultStatus::Failed: 1819 // return NS_ERROR_FAILURE; 1820 1821 // Fall through to the old mechanism for now 1822 // In future, we should be sending telemetry for when 1823 // an error occurs or for when pinning is not allowed 1824 // with the Win 11 APIs. 1825 break; 1826 } 1827 1828 return PinCurrentAppToTaskbarWin10(aCheckOnly, aAppUserModelId, 1829 aShortcutPath); 1830 } 1831 1832 /* This function pins a shortcut to the taskbar based on its location. While 1833 * Windows 11 only needs the `aAppUserModelId`, `aShortcutPath` is required 1834 * for pinning in Windows 10. 1835 * @param aAppUserModelId 1836 * The same string used to create an lnk file. 1837 * @param aShortcutPaths 1838 * Path for existing shortcuts (e.g., start menu) 1839 */ 1840 NS_IMETHODIMP 1841 nsWindowsShellService::PinShortcutToTaskbar( 1842 const nsAString& aAppUserModelId, const nsAString& aShortcutFolder, 1843 const nsAString& aShortcutRelativePath, JSContext* aCx, 1844 dom::Promise** aPromise) { 1845 NS_ENSURE_ARG_POINTER(aCx); 1846 NS_ENSURE_ARG_POINTER(aPromise); 1847 1848 if (!NS_IsMainThread()) { 1849 return NS_ERROR_NOT_SAME_THREAD; 1850 } 1851 1852 // First available on 1809 1853 if (!IsWin10Sep2018UpdateOrLater()) { 1854 return NS_ERROR_NOT_AVAILABLE; 1855 } 1856 1857 ErrorResult rv; 1858 RefPtr<dom::Promise> promise = 1859 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 1860 1861 if (MOZ_UNLIKELY(rv.Failed())) { 1862 return rv.StealNSResult(); 1863 } 1864 1865 ShortcutLocations location = 1866 MOZ_TRY(GetShortcutPaths(aShortcutFolder, aShortcutRelativePath)); 1867 1868 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 1869 "pinShortcutToTaskbar promise", promise); 1870 1871 NS_DispatchBackgroundTask( 1872 NS_NewRunnableFunction( 1873 "pinShortcutToTaskbar", 1874 [aumid = nsString{aAppUserModelId}, location = std::move(location), 1875 promiseHolder = std::move(promiseHolder)] { 1876 nsresult rv = NS_ERROR_FAILURE; 1877 HRESULT hr = CoInitialize(nullptr); 1878 1879 if (SUCCEEDED(hr)) { 1880 rv = PinShortcutToTaskbarImpl( 1881 false, aumid, location.shortcutFile->NativePath()); 1882 CoUninitialize(); 1883 } 1884 1885 NS_DispatchToMainThread(NS_NewRunnableFunction( 1886 "pinShortcutToTaskbar callback", 1887 [rv, promiseHolder = std::move(promiseHolder)] { 1888 dom::Promise* promise = promiseHolder.get()->get(); 1889 1890 if (NS_SUCCEEDED(rv)) { 1891 promise->MaybeResolveWithUndefined(); 1892 } else { 1893 promise->MaybeReject(rv); 1894 } 1895 })); 1896 }), 1897 NS_DISPATCH_EVENT_MAY_BLOCK); 1898 1899 promise.forget(aPromise); 1900 return NS_OK; 1901 } 1902 1903 NS_IMETHODIMP 1904 nsWindowsShellService::UnpinShortcutFromTaskbar( 1905 const nsAString& aShortcutFolder, const nsAString& aShortcutRelativePath) { 1906 const bool pinType = false; // false means unpin 1907 const bool runInTestMode = false; 1908 1909 ShortcutLocations location = 1910 MOZ_TRY(GetShortcutPaths(aShortcutFolder, aShortcutRelativePath)); 1911 1912 return ManageShortcutTaskbarPins(runInTestMode, pinType, 1913 location.shortcutFile->NativePath()); 1914 } 1915 1916 static nsresult PinCurrentAppToTaskbarWin10(bool aCheckOnly, 1917 const nsAString& aAppUserModelId, 1918 const nsAString& aShortcutPath) { 1919 // The behavior here is identical if we're only checking or if we try to pin 1920 // but the app is already pinned so we update the variable accordingly. 1921 if (!aCheckOnly) { 1922 aCheckOnly = IsCurrentAppPinnedToTaskbarSync(aAppUserModelId); 1923 } 1924 const bool pinType = true; // true means pin 1925 return ManageShortcutTaskbarPins(aCheckOnly, pinType, aShortcutPath); 1926 } 1927 1928 // There's a delay between shortcuts being created in locations visible to 1929 // `shell:appsfolder` and that information being propogated to 1930 // `shell:appsfolder`. APIs like `ITaskbarManager` pinning rely on said 1931 // shortcuts being visible to `shell:appsfolder`, so we have to introduce a wait 1932 // until they're visible when creating these shortcuts at runtime. 1933 static bool PollAppsFolderForShortcut(const nsAString& aAppUserModelId, 1934 const TimeDuration aTimeout) { 1935 MOZ_DIAGNOSTIC_ASSERT(!NS_IsMainThread(), 1936 "PollAppsFolderForShortcut blocks and should be called " 1937 "off main thread only"); 1938 1939 // Implementation note: it was taken into consideration at the time of writing 1940 // to implement this with `SHChangeNotifyRegister` and a `HWND_MESSAGE` 1941 // window. This added significant complexity in terms of resource management 1942 // and control flow that was deemed excessive for a function that is rarely 1943 // run. Absent evidence that we're consuming excessive system resources, this 1944 // simple, poll-based approach seemed more appropriate. 1945 // 1946 // If in the future it seems appropriate to modify this to be event based, 1947 // here are some of the lessons learned during the investigation: 1948 // - `shell:appsfolder` is a virtual directory, composed of shortcut files 1949 // with unique AUMIDs from 1950 // `[%PROGRAMDATA%|%APPDATA%]\Microsoft\Windows\Start Menu\Programs`. 1951 // - `shell:appsfolder` does not have a full path in the filesystem, 1952 // therefore does not work with most file watching APIs. 1953 // - `SHChangeNotifyRegister` should listen for `SHCNE_UPDATEDIR` on 1954 // `FOLDERID_AppsFolder`. `SHCNE_CREATE` events are not issued for 1955 // shortcuts added to `FOLDERID_AppsFolder` likely due to it's virtual 1956 // nature. 1957 // - The mechanism for inspecting the `shell:appsfolder` for a shortcut with 1958 // matching AUMID is the same in an event-based implementation due to 1959 // `SHCNE_UPDATEDIR` events include the modified folder, but not the 1960 // modified file. 1961 1962 TimeStamp start = TimeStamp::Now(); 1963 1964 ComPtr<IShellItem> appsFolder; 1965 HRESULT hr = SHGetKnownFolderItem(FOLDERID_AppsFolder, KF_FLAG_DEFAULT, 1966 nullptr, IID_PPV_ARGS(&appsFolder)); 1967 if (FAILED(hr)) { 1968 return false; 1969 } 1970 1971 do { 1972 // It's possible to have identically named files in `shell:appsfolder` as 1973 // it's disambiguated by AUMID instead of file name, so we have to iterate 1974 // over all items instead of querying the specific shortcut. 1975 ComPtr<IEnumShellItems> shortcutIter; 1976 hr = appsFolder->BindToHandler(nullptr, BHID_EnumItems, 1977 IID_PPV_ARGS(&shortcutIter)); 1978 if (FAILED(hr)) { 1979 return false; 1980 } 1981 1982 ComPtr<IShellItem> shortcut; 1983 while (shortcutIter->Next(1, &shortcut, nullptr) == S_OK) { 1984 ComPtr<IShellItem2> shortcut2; 1985 hr = shortcut.As(&shortcut2); 1986 if (FAILED(hr)) { 1987 return false; 1988 } 1989 1990 mozilla::UniquePtr<WCHAR, mozilla::CoTaskMemFreeDeleter> shortcutAumid; 1991 hr = shortcut2->GetString(PKEY_AppUserModel_ID, 1992 getter_Transfers(shortcutAumid)); 1993 if (FAILED(hr)) { 1994 // `shell:appsfolder` is populated by unique shortcut AUMID; if this is 1995 // absent something has gone wrong and we should exit. 1996 return false; 1997 } 1998 1999 if (aAppUserModelId == nsDependentString(shortcutAumid.get())) { 2000 return true; 2001 } 2002 } 2003 2004 // Sleep for a quarter of a second to avoid pinning the CPU while waiting. 2005 ::Sleep(250); 2006 } while ((TimeStamp::Now() - start) < aTimeout); 2007 2008 return false; 2009 } 2010 2011 static nsresult PinCurrentAppToTaskbarImpl( 2012 bool aCheckOnly, bool aPrivateBrowsing, const nsAString& aAppUserModelId, 2013 const nsAString& aShortcutName, const nsAString& aShortcutSubstring, 2014 nsIFile* aGreDir, const ShortcutLocations& location) { 2015 MOZ_DIAGNOSTIC_ASSERT( 2016 !NS_IsMainThread(), 2017 "PinCurrentAppToTaskbarImpl should be called off main thread only"); 2018 2019 nsAutoString shortcutPath; 2020 nsresult rv = FindPinnableShortcut(aAppUserModelId, aShortcutSubstring, 2021 aPrivateBrowsing, shortcutPath); 2022 if (NS_FAILED(rv)) { 2023 shortcutPath.Truncate(); 2024 } 2025 if (shortcutPath.IsEmpty()) { 2026 if (aCheckOnly) { 2027 // Later checks rely on a shortcut already existing. 2028 // We don't want to create a shortcut in check only mode 2029 // so the best we can do is assume those parts will work. 2030 return NS_OK; 2031 } 2032 2033 nsAutoString linkName(aShortcutName); 2034 2035 nsCOMPtr<nsIFile> exeFile(aGreDir); 2036 if (aPrivateBrowsing) { 2037 nsAutoString pbExeStr(PRIVATE_BROWSING_BINARY); 2038 nsresult rv = exeFile->Append(pbExeStr); 2039 if (!NS_SUCCEEDED(rv)) { 2040 return NS_ERROR_FAILURE; 2041 } 2042 } else { 2043 wchar_t exePath[MAXPATHLEN] = {}; 2044 if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) { 2045 return NS_ERROR_FAILURE; 2046 } 2047 nsAutoString exeStr(exePath); 2048 nsresult rv = NS_NewLocalFile(exeStr, getter_AddRefs(exeFile)); 2049 if (!NS_SUCCEEDED(rv)) { 2050 return NS_ERROR_FILE_NOT_FOUND; 2051 } 2052 } 2053 2054 nsTArray<nsString> arguments; 2055 rv = CreateShortcutImpl(exeFile, arguments, aShortcutName, exeFile, 2056 // Icon indexes are defined as Resource IDs, but 2057 // CreateShortcutImpl needs an index. 2058 IDI_APPICON - 1, aAppUserModelId, location, 2059 linkName); 2060 if (!NS_SUCCEEDED(rv)) { 2061 return NS_ERROR_FILE_NOT_FOUND; 2062 } 2063 } 2064 return PinShortcutToTaskbarImpl(aCheckOnly, aAppUserModelId, shortcutPath); 2065 } 2066 2067 static nsresult PinCurrentAppToTaskbarAsyncImpl(bool aCheckOnly, 2068 bool aPrivateBrowsing, 2069 JSContext* aCx, 2070 dom::Promise** aPromise) { 2071 if (!NS_IsMainThread()) { 2072 return NS_ERROR_NOT_SAME_THREAD; 2073 } 2074 2075 // First available on 1809 2076 if (!IsWin10Sep2018UpdateOrLater()) { 2077 return NS_ERROR_NOT_AVAILABLE; 2078 } 2079 2080 ErrorResult rv; 2081 RefPtr<dom::Promise> promise = 2082 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2083 2084 if (MOZ_UNLIKELY(rv.Failed())) { 2085 return rv.StealNSResult(); 2086 } 2087 2088 nsAutoString aumid; 2089 if (NS_WARN_IF(!mozilla::widget::WinTaskbar::GetAppUserModelID( 2090 aumid, aPrivateBrowsing))) { 2091 return NS_ERROR_FAILURE; 2092 } 2093 2094 // NOTE: In the installer, non-private shortcuts are named 2095 // "${BrandShortName}.lnk". This is set from MOZ_APP_DISPLAYNAME in 2096 // defines.nsi.in. (Except in dev edition where it's explicitly set to 2097 // "Firefox Developer Edition" in branding.nsi, which matches 2098 // MOZ_APP_DISPLAYNAME in aurora/configure.sh.) 2099 // 2100 // If this changes, we could expand this to check shortcuts_log.ini, 2101 // which records the name of the shortcuts as created by the installer. 2102 // 2103 // Private shortcuts are not created by the installer (they're created 2104 // upon user request, ultimately by CreateShortcutImpl, and recorded in 2105 // a separate shortcuts log. As with non-private shortcuts they have a known 2106 // name - so there's no need to look through logs to find them. 2107 nsAutoString shortcutName; 2108 if (aPrivateBrowsing) { 2109 nsTArray<nsCString> resIds = { 2110 "branding/brand.ftl"_ns, 2111 "browser/browser.ftl"_ns, 2112 }; 2113 RefPtr<Localization> l10n = Localization::Create(resIds, true); 2114 nsAutoCString pbStr; 2115 IgnoredErrorResult rv; 2116 l10n->FormatValueSync("private-browsing-shortcut-text-2"_ns, {}, pbStr, rv); 2117 shortcutName.Append(NS_ConvertUTF8toUTF16(pbStr)); 2118 shortcutName.AppendLiteral(".lnk"); 2119 } else { 2120 shortcutName.AppendLiteral(MOZ_APP_DISPLAYNAME ".lnk"); 2121 } 2122 2123 nsCOMPtr<nsIFile> greDir; 2124 nsresult nsrv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greDir)); 2125 NS_ENSURE_SUCCESS(nsrv, nsrv); 2126 2127 ShortcutLocations location = 2128 MOZ_TRY(GetShortcutPaths(nsString(L"Programs"), shortcutName)); 2129 2130 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2131 "CheckPinCurrentAppToTaskbarAsync promise", promise); 2132 2133 NS_DispatchBackgroundTask( 2134 NS_NewRunnableFunction( 2135 "CheckPinCurrentAppToTaskbarAsync", 2136 [aCheckOnly, aPrivateBrowsing, shortcutName, aumid = nsString{aumid}, 2137 greDir, location = std::move(location), 2138 promiseHolder = std::move(promiseHolder)] { 2139 nsresult rv = NS_ERROR_FAILURE; 2140 HRESULT hr = CoInitialize(nullptr); 2141 2142 if (SUCCEEDED(hr)) { 2143 nsAutoString shortcutSubstring; 2144 shortcutSubstring.AssignLiteral(MOZ_APP_DISPLAYNAME); 2145 rv = PinCurrentAppToTaskbarImpl( 2146 aCheckOnly, aPrivateBrowsing, aumid, shortcutName, 2147 shortcutSubstring, greDir.get(), location); 2148 CoUninitialize(); 2149 } 2150 2151 NS_DispatchToMainThread(NS_NewRunnableFunction( 2152 "CheckPinCurrentAppToTaskbarAsync callback", 2153 [rv, promiseHolder = std::move(promiseHolder)] { 2154 dom::Promise* promise = promiseHolder.get()->get(); 2155 2156 if (NS_SUCCEEDED(rv)) { 2157 promise->MaybeResolveWithUndefined(); 2158 } else { 2159 promise->MaybeReject(rv); 2160 } 2161 })); 2162 }), 2163 NS_DISPATCH_EVENT_MAY_BLOCK); 2164 2165 promise.forget(aPromise); 2166 return NS_OK; 2167 } 2168 2169 NS_IMETHODIMP 2170 nsWindowsShellService::PinCurrentAppToTaskbarAsync(bool aPrivateBrowsing, 2171 JSContext* aCx, 2172 dom::Promise** aPromise) { 2173 return PinCurrentAppToTaskbarAsyncImpl( 2174 /* aCheckOnly */ false, aPrivateBrowsing, aCx, aPromise); 2175 } 2176 2177 NS_IMETHODIMP 2178 nsWindowsShellService::CheckPinCurrentAppToTaskbarAsync( 2179 bool aPrivateBrowsing, JSContext* aCx, dom::Promise** aPromise) { 2180 return PinCurrentAppToTaskbarAsyncImpl( 2181 /* aCheckOnly = */ true, aPrivateBrowsing, aCx, aPromise); 2182 } 2183 2184 NS_IMETHODIMP 2185 nsWindowsShellService::IsCurrentAppPinnedToTaskbarAsync( 2186 const nsAString& aumid, JSContext* aCx, /* out */ dom::Promise** aPromise) { 2187 if (!NS_IsMainThread()) { 2188 return NS_ERROR_NOT_SAME_THREAD; 2189 } 2190 2191 ErrorResult rv; 2192 RefPtr<dom::Promise> promise = 2193 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2194 if (MOZ_UNLIKELY(rv.Failed())) { 2195 return rv.StealNSResult(); 2196 } 2197 2198 // A holder to pass the promise through the background task and back to 2199 // the main thread when finished. 2200 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2201 "IsCurrentAppPinnedToTaskbarAsync promise", promise); 2202 2203 // nsAString can't be captured by a lambda because it does not have a 2204 // public copy constructor 2205 nsAutoString capturedAumid(aumid); 2206 NS_DispatchBackgroundTask( 2207 NS_NewRunnableFunction( 2208 "IsCurrentAppPinnedToTaskbarAsync", 2209 [capturedAumid, promiseHolder = std::move(promiseHolder)] { 2210 bool isPinned = false; 2211 2212 HRESULT hr = CoInitialize(nullptr); 2213 if (SUCCEEDED(hr)) { 2214 isPinned = IsCurrentAppPinnedToTaskbarSync(capturedAumid); 2215 CoUninitialize(); 2216 } 2217 2218 // Dispatch back to the main thread to resolve the promise. 2219 NS_DispatchToMainThread(NS_NewRunnableFunction( 2220 "IsCurrentAppPinnedToTaskbarAsync callback", 2221 [isPinned, promiseHolder = std::move(promiseHolder)] { 2222 promiseHolder.get()->get()->MaybeResolve(isPinned); 2223 })); 2224 }), 2225 NS_DISPATCH_EVENT_MAY_BLOCK); 2226 2227 promise.forget(aPromise); 2228 return NS_OK; 2229 } 2230 2231 #ifndef __MINGW32__ 2232 # define RESOLVE_AND_RETURN(HOLDER, RESOLVE, RETURN) \ 2233 NS_DispatchToMainThread(NS_NewRunnableFunction( \ 2234 __func__, [resolveVal = (RESOLVE), promiseHolder = HOLDER] { \ 2235 promiseHolder.get()->get()->MaybeResolve(resolveVal); \ 2236 })); \ 2237 return RETURN 2238 2239 # define REJECT_AND_RETURN(HOLDER, REJECT, RETURN) \ 2240 NS_DispatchToMainThread( \ 2241 NS_NewRunnableFunction(__func__, [promiseHolder = HOLDER] { \ 2242 promiseHolder.get()->get()->MaybeReject(REJECT); \ 2243 })); \ 2244 return RETURN 2245 2246 static void EnableLaunchOnLoginMSIXAsyncImpl( 2247 const nsString& capturedTaskId, 2248 const RefPtr<nsMainThreadPtrHolder<dom::Promise>> promiseHolder) { 2249 ComPtr<IStartupTaskStatics> startupTaskStatics; 2250 HRESULT hr = GetActivationFactory( 2251 HStringReference(RuntimeClass_Windows_ApplicationModel_StartupTask).Get(), 2252 &startupTaskStatics); 2253 if (FAILED(hr)) { 2254 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2255 } 2256 ComPtr<IAsyncOperation<StartupTask*>> getTaskOperation = nullptr; 2257 hr = startupTaskStatics->GetAsync( 2258 HStringReference(capturedTaskId.get()).Get(), &getTaskOperation); 2259 if (FAILED(hr)) { 2260 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2261 } 2262 auto getTaskCallback = 2263 Callback<IAsyncOperationCompletedHandler<StartupTask*>>( 2264 [promiseHolder](IAsyncOperation<StartupTask*>* operation, 2265 AsyncStatus status) -> HRESULT { 2266 if (status != AsyncStatus::Completed) { 2267 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2268 } 2269 ComPtr<IStartupTask> startupTask; 2270 HRESULT hr = operation->GetResults(&startupTask); 2271 if (FAILED(hr)) { 2272 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2273 } 2274 ComPtr<IAsyncOperation<StartupTaskState>> enableOperation; 2275 hr = startupTask->RequestEnableAsync(&enableOperation); 2276 if (FAILED(hr)) { 2277 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2278 } 2279 // Set another callback for enabling the startup task 2280 auto enableHandler = 2281 Callback<IAsyncOperationCompletedHandler<StartupTaskState>>( 2282 [promiseHolder]( 2283 IAsyncOperation<StartupTaskState>* operation, 2284 AsyncStatus status) -> HRESULT { 2285 StartupTaskState resultState; 2286 HRESULT hr = operation->GetResults(&resultState); 2287 if (SUCCEEDED(hr) && status == AsyncStatus::Completed) { 2288 RESOLVE_AND_RETURN(promiseHolder, true, S_OK); 2289 } 2290 RESOLVE_AND_RETURN(promiseHolder, false, S_OK); 2291 }); 2292 hr = enableOperation->put_Completed(enableHandler.Get()); 2293 if (FAILED(hr)) { 2294 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, hr); 2295 } 2296 return hr; 2297 }); 2298 hr = getTaskOperation->put_Completed(getTaskCallback.Get()); 2299 if (FAILED(hr)) { 2300 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2301 } 2302 } 2303 2304 static void DisableLaunchOnLoginMSIXAsyncImpl( 2305 const nsString& capturedTaskId, 2306 const RefPtr<nsMainThreadPtrHolder<dom::Promise>> promiseHolder) { 2307 ComPtr<IStartupTaskStatics> startupTaskStatics; 2308 HRESULT hr = GetActivationFactory( 2309 HStringReference(RuntimeClass_Windows_ApplicationModel_StartupTask).Get(), 2310 &startupTaskStatics); 2311 if (FAILED(hr)) { 2312 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2313 } 2314 ComPtr<IAsyncOperation<StartupTask*>> getTaskOperation = nullptr; 2315 hr = startupTaskStatics->GetAsync( 2316 HStringReference(capturedTaskId.get()).Get(), &getTaskOperation); 2317 if (FAILED(hr)) { 2318 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2319 } 2320 auto getTaskCallback = 2321 Callback<IAsyncOperationCompletedHandler<StartupTask*>>( 2322 [promiseHolder](IAsyncOperation<StartupTask*>* operation, 2323 AsyncStatus status) -> HRESULT { 2324 if (status != AsyncStatus::Completed) { 2325 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2326 } 2327 ComPtr<IStartupTask> startupTask; 2328 HRESULT hr = operation->GetResults(&startupTask); 2329 if (FAILED(hr)) { 2330 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2331 } 2332 hr = startupTask->Disable(); 2333 if (FAILED(hr)) { 2334 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2335 } 2336 RESOLVE_AND_RETURN(promiseHolder, true, S_OK); 2337 }); 2338 hr = getTaskOperation->put_Completed(getTaskCallback.Get()); 2339 if (FAILED(hr)) { 2340 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2341 } 2342 } 2343 2344 static void GetLaunchOnLoginEnabledMSIXAsyncImpl( 2345 const nsString& capturedTaskId, 2346 const RefPtr<nsMainThreadPtrHolder<dom::Promise>> promiseHolder) { 2347 ComPtr<IStartupTaskStatics> startupTaskStatics; 2348 HRESULT hr = GetActivationFactory( 2349 HStringReference(RuntimeClass_Windows_ApplicationModel_StartupTask).Get(), 2350 &startupTaskStatics); 2351 if (FAILED(hr)) { 2352 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2353 } 2354 ComPtr<IAsyncOperation<StartupTask*>> getTaskOperation = nullptr; 2355 hr = startupTaskStatics->GetAsync( 2356 HStringReference(capturedTaskId.get()).Get(), &getTaskOperation); 2357 if (FAILED(hr)) { 2358 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2359 } 2360 auto getTaskCallback = 2361 Callback<IAsyncOperationCompletedHandler<StartupTask*>>( 2362 [promiseHolder](IAsyncOperation<StartupTask*>* operation, 2363 AsyncStatus status) -> HRESULT { 2364 if (status != AsyncStatus::Completed) { 2365 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2366 } 2367 ComPtr<IStartupTask> startupTask; 2368 HRESULT hr = operation->GetResults(&startupTask); 2369 if (FAILED(hr)) { 2370 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2371 } 2372 StartupTaskState state; 2373 hr = startupTask->get_State(&state); 2374 if (FAILED(hr)) { 2375 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2376 } 2377 switch (state) { 2378 case StartupTaskState_EnabledByPolicy: 2379 RESOLVE_AND_RETURN( 2380 promiseHolder, 2381 nsIWindowsShellService::LaunchOnLoginEnabledEnumerator:: 2382 LAUNCH_ON_LOGIN_ENABLED_BY_POLICY, 2383 S_OK); 2384 break; 2385 case StartupTaskState_Enabled: 2386 RESOLVE_AND_RETURN( 2387 promiseHolder, 2388 nsIWindowsShellService::LaunchOnLoginEnabledEnumerator:: 2389 LAUNCH_ON_LOGIN_ENABLED, 2390 S_OK); 2391 break; 2392 case StartupTaskState_DisabledByUser: 2393 case StartupTaskState_DisabledByPolicy: 2394 RESOLVE_AND_RETURN( 2395 promiseHolder, 2396 nsIWindowsShellService::LaunchOnLoginEnabledEnumerator:: 2397 LAUNCH_ON_LOGIN_DISABLED_BY_SETTINGS, 2398 S_OK); 2399 break; 2400 default: 2401 RESOLVE_AND_RETURN( 2402 promiseHolder, 2403 nsIWindowsShellService::LaunchOnLoginEnabledEnumerator:: 2404 LAUNCH_ON_LOGIN_DISABLED, 2405 S_OK); 2406 } 2407 }); 2408 hr = getTaskOperation->put_Completed(getTaskCallback.Get()); 2409 if (FAILED(hr)) { 2410 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2411 } 2412 } 2413 2414 NS_IMETHODIMP 2415 nsWindowsShellService::EnableLaunchOnLoginMSIXAsync( 2416 const nsAString& aTaskId, JSContext* aCx, 2417 /* out */ dom::Promise** aPromise) { 2418 if (!widget::WinUtils::HasPackageIdentity()) { 2419 return NS_ERROR_NOT_AVAILABLE; 2420 } 2421 2422 if (!NS_IsMainThread()) { 2423 return NS_ERROR_NOT_SAME_THREAD; 2424 } 2425 ErrorResult rv; 2426 RefPtr<dom::Promise> promise = 2427 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2428 if (MOZ_UNLIKELY(rv.Failed())) { 2429 return rv.StealNSResult(); 2430 } 2431 2432 // A holder to pass the promise through the background task and back to 2433 // the main thread when finished. 2434 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2435 "EnableLaunchOnLoginMSIXAsync promise", promise); 2436 2437 NS_DispatchBackgroundTask(NS_NewRunnableFunction( 2438 "EnableLaunchOnLoginMSIXAsync", 2439 [taskId = nsString(aTaskId), promiseHolder] { 2440 EnableLaunchOnLoginMSIXAsyncImpl(taskId, promiseHolder); 2441 })); 2442 2443 promise.forget(aPromise); 2444 return NS_OK; 2445 } 2446 2447 NS_IMETHODIMP 2448 nsWindowsShellService::DisableLaunchOnLoginMSIXAsync( 2449 const nsAString& aTaskId, JSContext* aCx, 2450 /* out */ dom::Promise** aPromise) { 2451 if (!widget::WinUtils::HasPackageIdentity()) { 2452 return NS_ERROR_NOT_AVAILABLE; 2453 } 2454 2455 if (!NS_IsMainThread()) { 2456 return NS_ERROR_NOT_SAME_THREAD; 2457 } 2458 ErrorResult rv; 2459 RefPtr<dom::Promise> promise = 2460 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2461 if (MOZ_UNLIKELY(rv.Failed())) { 2462 return rv.StealNSResult(); 2463 } 2464 2465 // A holder to pass the promise through the background task and back to 2466 // the main thread when finished. 2467 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2468 "DisableLaunchOnLoginMSIXAsync promise", promise); 2469 2470 NS_DispatchBackgroundTask(NS_NewRunnableFunction( 2471 "DisableLaunchOnLoginMSIXAsync", 2472 [taskId = nsString(aTaskId), promiseHolder] { 2473 DisableLaunchOnLoginMSIXAsyncImpl(taskId, promiseHolder); 2474 })); 2475 2476 promise.forget(aPromise); 2477 return NS_OK; 2478 } 2479 2480 NS_IMETHODIMP 2481 nsWindowsShellService::GetLaunchOnLoginEnabledMSIXAsync( 2482 const nsAString& aTaskId, JSContext* aCx, 2483 /* out */ dom::Promise** aPromise) { 2484 if (!widget::WinUtils::HasPackageIdentity()) { 2485 return NS_ERROR_NOT_AVAILABLE; 2486 } 2487 2488 if (!NS_IsMainThread()) { 2489 return NS_ERROR_NOT_SAME_THREAD; 2490 } 2491 ErrorResult rv; 2492 RefPtr<dom::Promise> promise = 2493 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2494 if (MOZ_UNLIKELY(rv.Failed())) { 2495 return rv.StealNSResult(); 2496 } 2497 2498 // A holder to pass the promise through the background task and back to 2499 // the main thread when finished. 2500 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2501 "GetLaunchOnLoginEnabledMSIXAsync promise", promise); 2502 2503 NS_DispatchBackgroundTask(NS_NewRunnableFunction( 2504 "GetLaunchOnLoginEnabledMSIXAsync", 2505 [taskId = nsString(aTaskId), promiseHolder] { 2506 GetLaunchOnLoginEnabledMSIXAsyncImpl(taskId, promiseHolder); 2507 })); 2508 2509 promise.forget(aPromise); 2510 return NS_OK; 2511 } 2512 2513 static HRESULT GetPackage3(ComPtr<IPackage3>& package3) { 2514 // Get the current package and cast it to IPackage3 so we can 2515 // check for AppListEntries 2516 ComPtr<IPackageStatics> packageStatics; 2517 HRESULT hr = GetActivationFactory( 2518 HStringReference(RuntimeClass_Windows_ApplicationModel_Package).Get(), 2519 &packageStatics); 2520 2521 if (FAILED(hr)) { 2522 return hr; 2523 } 2524 ComPtr<IPackage> package; 2525 hr = packageStatics->get_Current(&package); 2526 if (FAILED(hr)) { 2527 return hr; 2528 } 2529 hr = package.As(&package3); 2530 return hr; 2531 } 2532 2533 static HRESULT GetStartScreenManager( 2534 ComPtr<IVectorView<AppListEntry*>>& appListEntries, 2535 ComPtr<IAppListEntry>& entry, 2536 ComPtr<IStartScreenManager>& startScreenManager) { 2537 unsigned int numEntries = 0; 2538 HRESULT hr = appListEntries->get_Size(&numEntries); 2539 if (FAILED(hr) || numEntries == 0) { 2540 return E_FAIL; 2541 } 2542 // There's only one AppListEntry in the Firefox package and by 2543 // convention our main executable should be the first in the 2544 // list. 2545 hr = appListEntries->GetAt(0, &entry); 2546 2547 // Create and init a StartScreenManager and check if we're already 2548 // pinned. 2549 ComPtr<IStartScreenManagerStatics> startScreenManagerStatics; 2550 hr = GetActivationFactory( 2551 HStringReference(RuntimeClass_Windows_UI_StartScreen_StartScreenManager) 2552 .Get(), 2553 &startScreenManagerStatics); 2554 if (FAILED(hr)) { 2555 return hr; 2556 } 2557 2558 hr = startScreenManagerStatics->GetDefault(&startScreenManager); 2559 return hr; 2560 } 2561 2562 static void PinCurrentAppToStartMenuAsyncImpl( 2563 bool aCheckOnly, 2564 const RefPtr<nsMainThreadPtrHolder<dom::Promise>> promiseHolder) { 2565 ComPtr<IPackage3> package3; 2566 HRESULT hr = GetPackage3(package3); 2567 if (FAILED(hr)) { 2568 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2569 } 2570 2571 // Get the AppList entries 2572 ComPtr<IVectorView<AppListEntry*>> appListEntries; 2573 ComPtr<IAsyncOperation<IVectorView<AppListEntry*>*>> 2574 getAppListEntriesOperation; 2575 hr = package3->GetAppListEntriesAsync(&getAppListEntriesOperation); 2576 if (FAILED(hr)) { 2577 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2578 } 2579 auto getAppListEntriesCallback = 2580 Callback<IAsyncOperationCompletedHandler<IVectorView<AppListEntry*>*>>( 2581 [promiseHolder, aCheckOnly]( 2582 IAsyncOperation<IVectorView<AppListEntry*>*>* operation, 2583 AsyncStatus status) -> HRESULT { 2584 if (status != AsyncStatus::Completed) { 2585 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2586 } 2587 ComPtr<IVectorView<AppListEntry*>> appListEntries; 2588 HRESULT hr = operation->GetResults(&appListEntries); 2589 if (FAILED(hr)) { 2590 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2591 } 2592 ComPtr<IStartScreenManager> startScreenManager; 2593 ComPtr<IAppListEntry> entry; 2594 hr = GetStartScreenManager(appListEntries, entry, 2595 startScreenManager); 2596 if (FAILED(hr)) { 2597 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2598 } 2599 ComPtr<IAsyncOperation<bool>> getPinnedOperation; 2600 hr = startScreenManager->ContainsAppListEntryAsync( 2601 entry.Get(), &getPinnedOperation); 2602 if (FAILED(hr)) { 2603 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2604 } 2605 auto getPinnedCallback = 2606 Callback<IAsyncOperationCompletedHandler<bool>>( 2607 [promiseHolder, entry, startScreenManager, aCheckOnly]( 2608 IAsyncOperation<bool>* operation, 2609 AsyncStatus status) -> HRESULT { 2610 if (status != AsyncStatus::Completed) { 2611 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, 2612 E_FAIL); 2613 } 2614 boolean isAlreadyPinned; 2615 HRESULT hr = operation->GetResults(&isAlreadyPinned); 2616 if (FAILED(hr)) { 2617 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, 2618 E_FAIL); 2619 } 2620 // If we're already pinned we can return early 2621 // Ditto if we're just checking whether we *can* pin 2622 if (isAlreadyPinned || aCheckOnly) { 2623 RESOLVE_AND_RETURN(promiseHolder, true, S_OK); 2624 } 2625 ComPtr<IAsyncOperation<bool>> pinOperation; 2626 startScreenManager->RequestAddAppListEntryAsync( 2627 entry.Get(), &pinOperation); 2628 // Set another callback for pinning to the start menu 2629 auto pinOperationCallback = 2630 Callback<IAsyncOperationCompletedHandler<bool>>( 2631 [promiseHolder](IAsyncOperation<bool>* operation, 2632 AsyncStatus status) -> HRESULT { 2633 if (status != AsyncStatus::Completed) { 2634 REJECT_AND_RETURN(promiseHolder, 2635 NS_ERROR_FAILURE, E_FAIL); 2636 }; 2637 boolean pinSuccess; 2638 HRESULT hr = operation->GetResults(&pinSuccess); 2639 if (FAILED(hr)) { 2640 REJECT_AND_RETURN(promiseHolder, 2641 NS_ERROR_FAILURE, E_FAIL); 2642 } 2643 RESOLVE_AND_RETURN(promiseHolder, 2644 pinSuccess ? true : false, 2645 S_OK); 2646 }); 2647 hr = pinOperation->put_Completed( 2648 pinOperationCallback.Get()); 2649 if (FAILED(hr)) { 2650 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, hr); 2651 } 2652 return hr; 2653 }); 2654 hr = getPinnedOperation->put_Completed(getPinnedCallback.Get()); 2655 if (FAILED(hr)) { 2656 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, hr); 2657 } 2658 return hr; 2659 }); 2660 hr = getAppListEntriesOperation->put_Completed( 2661 getAppListEntriesCallback.Get()); 2662 if (FAILED(hr)) { 2663 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2664 } 2665 } 2666 2667 NS_IMETHODIMP 2668 nsWindowsShellService::PinCurrentAppToStartMenuAsync(bool aCheckOnly, 2669 JSContext* aCx, 2670 dom::Promise** aPromise) { 2671 if (!NS_IsMainThread()) { 2672 return NS_ERROR_NOT_SAME_THREAD; 2673 } 2674 // Unfortunately pinning to the Start Menu requires IAppListEntry 2675 // which is only implemented for packaged applications. 2676 if (!widget::WinUtils::HasPackageIdentity()) { 2677 return NS_ERROR_NOT_AVAILABLE; 2678 } 2679 ErrorResult rv; 2680 RefPtr<dom::Promise> promise = 2681 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2682 if (MOZ_UNLIKELY(rv.Failed())) { 2683 return rv.StealNSResult(); 2684 } 2685 2686 // A holder to pass the promise through the background task and back to 2687 // the main thread when finished. 2688 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2689 "PinCurrentAppToStartMenuAsync promise", promise); 2690 NS_DispatchBackgroundTask(NS_NewRunnableFunction( 2691 "PinCurrentAppToStartMenuAsync", [aCheckOnly, promiseHolder] { 2692 PinCurrentAppToStartMenuAsyncImpl(aCheckOnly, promiseHolder); 2693 })); 2694 promise.forget(aPromise); 2695 return NS_OK; 2696 } 2697 2698 static void IsCurrentAppPinnedToStartMenuAsyncImpl( 2699 const RefPtr<nsMainThreadPtrHolder<dom::Promise>> promiseHolder) { 2700 ComPtr<IPackage3> package3; 2701 HRESULT hr = GetPackage3(package3); 2702 if (FAILED(hr)) { 2703 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2704 } 2705 2706 // Get the AppList entries 2707 ComPtr<IVectorView<AppListEntry*>> appListEntries; 2708 ComPtr<IAsyncOperation<IVectorView<AppListEntry*>*>> 2709 getAppListEntriesOperation; 2710 hr = package3->GetAppListEntriesAsync(&getAppListEntriesOperation); 2711 if (FAILED(hr)) { 2712 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2713 } 2714 auto getAppListEntriesCallback = 2715 Callback<IAsyncOperationCompletedHandler<IVectorView<AppListEntry*>*>>( 2716 [promiseHolder]( 2717 IAsyncOperation<IVectorView<AppListEntry*>*>* operation, 2718 AsyncStatus status) -> HRESULT { 2719 if (status != AsyncStatus::Completed) { 2720 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2721 } 2722 ComPtr<IVectorView<AppListEntry*>> appListEntries; 2723 HRESULT hr = operation->GetResults(&appListEntries); 2724 if (FAILED(hr)) { 2725 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2726 } 2727 ComPtr<IStartScreenManager> startScreenManager; 2728 ComPtr<IAppListEntry> entry; 2729 hr = GetStartScreenManager(appListEntries, entry, 2730 startScreenManager); 2731 if (FAILED(hr)) { 2732 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2733 } 2734 ComPtr<IAsyncOperation<bool>> getPinnedOperation; 2735 hr = startScreenManager->ContainsAppListEntryAsync( 2736 entry.Get(), &getPinnedOperation); 2737 if (FAILED(hr)) { 2738 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, E_FAIL); 2739 } 2740 auto getPinnedCallback = 2741 Callback<IAsyncOperationCompletedHandler<bool>>( 2742 [promiseHolder, entry, startScreenManager]( 2743 IAsyncOperation<bool>* operation, 2744 AsyncStatus status) -> HRESULT { 2745 if (status != AsyncStatus::Completed) { 2746 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, 2747 E_FAIL); 2748 } 2749 boolean isAlreadyPinned; 2750 HRESULT hr = operation->GetResults(&isAlreadyPinned); 2751 if (FAILED(hr)) { 2752 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, 2753 E_FAIL); 2754 } 2755 RESOLVE_AND_RETURN(promiseHolder, 2756 isAlreadyPinned ? true : false, S_OK); 2757 }); 2758 hr = getPinnedOperation->put_Completed(getPinnedCallback.Get()); 2759 if (FAILED(hr)) { 2760 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, hr); 2761 } 2762 return hr; 2763 }); 2764 hr = getAppListEntriesOperation->put_Completed( 2765 getAppListEntriesCallback.Get()); 2766 if (FAILED(hr)) { 2767 REJECT_AND_RETURN(promiseHolder, NS_ERROR_FAILURE, /* void */); 2768 } 2769 } 2770 2771 NS_IMETHODIMP 2772 nsWindowsShellService::IsCurrentAppPinnedToStartMenuAsync( 2773 JSContext* aCx, dom::Promise** aPromise) { 2774 if (!NS_IsMainThread()) { 2775 return NS_ERROR_NOT_SAME_THREAD; 2776 } 2777 // Unfortunately pinning to the Start Menu requires IAppListEntry 2778 // which is only implemented for packaged applications. 2779 if (!widget::WinUtils::HasPackageIdentity()) { 2780 return NS_ERROR_NOT_AVAILABLE; 2781 } 2782 ErrorResult rv; 2783 RefPtr<dom::Promise> promise = 2784 dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); 2785 if (MOZ_UNLIKELY(rv.Failed())) { 2786 return rv.StealNSResult(); 2787 } 2788 2789 // A holder to pass the promise through the background task and back to 2790 // the main thread when finished. 2791 auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>( 2792 "IsCurrentAppPinnedToStartMenuAsync promise", promise); 2793 NS_DispatchBackgroundTask(NS_NewRunnableFunction( 2794 "IsCurrentAppPinnedToStartMenuAsync", [promiseHolder] { 2795 IsCurrentAppPinnedToStartMenuAsyncImpl(promiseHolder); 2796 })); 2797 promise.forget(aPromise); 2798 return NS_OK; 2799 } 2800 2801 #else 2802 NS_IMETHODIMP 2803 nsWindowsShellService::EnableLaunchOnLoginMSIXAsync( 2804 const nsAString& aTaskId, JSContext* aCx, 2805 /* out */ dom::Promise** aPromise) { 2806 return NS_ERROR_NOT_IMPLEMENTED; 2807 } 2808 2809 NS_IMETHODIMP 2810 nsWindowsShellService::DisableLaunchOnLoginMSIXAsync( 2811 const nsAString& aTaskId, JSContext* aCx, 2812 /* out */ dom::Promise** aPromise) { 2813 return NS_ERROR_NOT_IMPLEMENTED; 2814 } 2815 2816 NS_IMETHODIMP 2817 nsWindowsShellService::GetLaunchOnLoginEnabledMSIXAsync( 2818 const nsAString& aTaskId, JSContext* aCx, 2819 /* out */ dom::Promise** aPromise) { 2820 return NS_ERROR_NOT_IMPLEMENTED; 2821 } 2822 2823 NS_IMETHODIMP 2824 nsWindowsShellService::PinCurrentAppToStartMenuAsync(bool aCheckOnly, 2825 JSContext* aCx, 2826 dom::Promise** aPromise) { 2827 return NS_ERROR_NOT_IMPLEMENTED; 2828 } 2829 2830 NS_IMETHODIMP 2831 nsWindowsShellService::IsCurrentAppPinnedToStartMenuAsync( 2832 JSContext* aCx, dom::Promise** aPromise) { 2833 return NS_ERROR_NOT_IMPLEMENTED; 2834 } 2835 #endif 2836 2837 NS_IMETHODIMP 2838 nsWindowsShellService::ClassifyShortcut(const nsAString& aPath, 2839 nsAString& aResult) { 2840 aResult.Truncate(); 2841 2842 nsAutoString shortcutPath(PromiseFlatString(aPath)); 2843 2844 // NOTE: On Windows 7, Start Menu pin shortcuts are stored under 2845 // "<FOLDERID_User Pinned>\StartMenu", but on Windows 10 they are just normal 2846 // Start Menu shortcuts. These both map to "StartMenu" for consistency, 2847 // rather than having a separate "StartMenuPins" which would only apply on 2848 // Win7. 2849 struct { 2850 KNOWNFOLDERID folderId; 2851 const char16_t* postfix; 2852 const char16_t* classification; 2853 } folders[] = {{FOLDERID_CommonStartMenu, u"\\", u"StartMenu"}, 2854 {FOLDERID_StartMenu, u"\\", u"StartMenu"}, 2855 {FOLDERID_PublicDesktop, u"\\", u"Desktop"}, 2856 {FOLDERID_Desktop, u"\\", u"Desktop"}, 2857 {FOLDERID_UserPinned, u"\\TaskBar\\", u"Taskbar"}, 2858 {FOLDERID_UserPinned, u"\\StartMenu\\", u"StartMenu"}}; 2859 2860 for (size_t i = 0; i < std::size(folders); ++i) { 2861 nsAutoString knownPath; 2862 2863 // These flags are chosen to avoid I/O, see bug 1363398. 2864 DWORD flags = 2865 KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS; 2866 PWSTR rawPath = nullptr; 2867 2868 if (FAILED(SHGetKnownFolderPath(folders[i].folderId, flags, nullptr, 2869 &rawPath))) { 2870 continue; 2871 } 2872 2873 knownPath = nsDependentString(rawPath); 2874 CoTaskMemFree(rawPath); 2875 2876 knownPath.Append(folders[i].postfix); 2877 // Check if the shortcut path starts with the shell folder path. 2878 if (wcsnicmp(shortcutPath.get(), knownPath.get(), knownPath.Length()) == 2879 0) { 2880 aResult.Assign(folders[i].classification); 2881 nsTArray<nsCString> resIds = { 2882 "branding/brand.ftl"_ns, 2883 "browser/browser.ftl"_ns, 2884 }; 2885 RefPtr<Localization> l10n = Localization::Create(resIds, true); 2886 nsAutoCString pbStr; 2887 IgnoredErrorResult rv; 2888 l10n->FormatValueSync("private-browsing-shortcut-text-2"_ns, {}, pbStr, 2889 rv); 2890 NS_ConvertUTF8toUTF16 widePbStr(pbStr); 2891 if (wcsstr(shortcutPath.get(), widePbStr.get())) { 2892 aResult.AppendLiteral("Private"); 2893 } 2894 return NS_OK; 2895 } 2896 } 2897 2898 // Nothing found, aResult is already "". 2899 return NS_OK; 2900 } 2901 2902 nsWindowsShellService::nsWindowsShellService() {} 2903 2904 nsWindowsShellService::~nsWindowsShellService() {}