fune/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp
Ben Hearsum 197dfd875f Bug 1797163: handle IAsyncOperations with callbacks and events instead of spinlocking r=nrishel
Also fixes a bug when assigning campaign ID from the extended JSON section (thanks to nrishel for noticing it!).

Differential Revision: https://phabricator.services.mozilla.com/D160543
2022-11-21 21:48:21 +00:00

341 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsWindowsPackageManager.h"
#include "mozilla/Logging.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "mozilla/mscom/EnsureMTA.h"
#ifndef __MINGW32__
# include <comutil.h>
# include <wrl.h>
# include <windows.applicationmodel.store.h>
# include <windows.management.deployment.h>
# include <windows.services.store.h>
#endif // __MINGW32__
#include "nsError.h"
#include "nsString.h"
#include "nsISupportsPrimitives.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsXPCOMCID.h"
#include "json/json.h"
#ifndef __MINGW32__ // WinRT headers not yet supported by MinGW
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Management;
using namespace ABI::Windows::Services::Store;
#endif
// Campaign IDs are stored in a JSON data structure under this key
// for installs done without the user being signed in to the Microsoft
// store.
#define CAMPAIGN_ID_JSON_FIELD_NAME "customPolicyField1"
namespace mozilla {
namespace toolkit {
namespace system {
NS_IMPL_ISUPPORTS(nsWindowsPackageManager, nsIWindowsPackageManager)
NS_IMETHODIMP
nsWindowsPackageManager::FindUserInstalledPackages(
const nsTArray<nsString>& aNamePrefixes, nsTArray<nsString>& aPackages) {
#ifdef __MINGW32__
return NS_ERROR_NOT_IMPLEMENTED;
#else
// The classes we're using are only available beginning with Windows 10
if (!mozilla::IsWin10OrLater()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
ComPtr<IInspectable> pmInspectable;
ComPtr<Deployment::IPackageManager> pm;
HRESULT hr = RoActivateInstance(
HStringReference(
RuntimeClass_Windows_Management_Deployment_PackageManager)
.Get(),
&pmInspectable);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
hr = pmInspectable.As(&pm);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
ComPtr<Collections::IIterable<ApplicationModel::Package*> > pkgs;
hr = pm->FindPackagesByUserSecurityId(NULL, &pkgs);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
ComPtr<Collections::IIterator<ApplicationModel::Package*> > iterator;
hr = pkgs->First(&iterator);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
boolean hasCurrent;
hr = iterator->get_HasCurrent(&hasCurrent);
while (SUCCEEDED(hr) && hasCurrent) {
ComPtr<ApplicationModel::IPackage> package;
hr = iterator->get_Current(&package);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
ComPtr<ApplicationModel::IPackageId> packageId;
hr = package->get_Id(&packageId);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
HString rawName;
hr = packageId->get_FamilyName(rawName.GetAddressOf());
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
unsigned int tmp;
nsString name(rawName.GetRawBuffer(&tmp));
for (uint32_t i = 0; i < aNamePrefixes.Length(); i++) {
if (name.Find(aNamePrefixes.ElementAt(i)) != kNotFound) {
aPackages.AppendElement(name);
break;
}
}
hr = iterator->MoveNext(&hasCurrent);
}
return NS_OK;
#endif // __MINGW32__
}
NS_IMETHODIMP
nsWindowsPackageManager::GetInstalledDate(uint64_t* ts) {
#ifdef __MINGW32__
return NS_ERROR_NOT_IMPLEMENTED;
#else
// The classes we're using are only available beginning with Windows 10
if (!mozilla::IsWin10OrLater()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
ComPtr<ApplicationModel::IPackageStatics> pkgStatics;
HRESULT hr = RoGetActivationFactory(
HStringReference(RuntimeClass_Windows_ApplicationModel_Package).Get(),
IID_PPV_ARGS(&pkgStatics));
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
ComPtr<ApplicationModel::IPackage> package;
hr = pkgStatics->get_Current(&package);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
ComPtr<ApplicationModel::IPackage3> package3;
hr = package.As(&package3);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
DateTime installedDate;
hr = package3->get_InstalledDate(&installedDate);
if (!SUCCEEDED(hr)) {
return NS_ERROR_FAILURE;
}
*ts = installedDate.UniversalTime;
return NS_OK;
#endif // __MINGW32__
}
NS_IMETHODIMP
nsWindowsPackageManager::GetCampaignId(nsAString& aCampaignId) {
#ifdef __MINGW32__
return NS_ERROR_NOT_IMPLEMENTED;
#else
// The classes we're using are only available beginning with Windows 10,
// and this is only relevant for MSIX packaged builds.
if (!mozilla::IsWin10OrLater() || !mozilla::HasPackageIdentity()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
ComPtr<IStoreContextStatics> scStatics = nullptr;
HRESULT hr = RoGetActivationFactory(
HStringReference(RuntimeClass_Windows_Services_Store_StoreContext).Get(),
IID_PPV_ARGS(&scStatics));
/* Per
* https://docs.microsoft.com/en-us/windows/uwp/publish/create-a-custom-app-promotion-campaign#programmatically-retrieve-the-custom-campaign-id-for-an-app
* there are three ways to find a campaign ID.
* 1) If the user was logged into the Microsoft Store when they installed
* it will be available in a SKU.
* 2) If they were not logged in, it will be available through the
* StoreAppLicense
*
* There's also a third way, in theory, to retrieve it on very old versions of
* Windows 10 (1511 or earlier). However, these versions don't appear to be
* able to use the Windows Store at all anymore - so there's no point in
* supporting that scenario.
*
*/
if (!SUCCEEDED(hr) || scStatics == nullptr) return NS_ERROR_FAILURE;
ComPtr<IStoreContext> storeContext = nullptr;
hr = scStatics->GetDefault(&storeContext);
if (!SUCCEEDED(hr) || storeContext == nullptr) return NS_ERROR_FAILURE;
ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr = nullptr;
{
nsAutoHandle event(CreateEventW(nullptr, true, false, nullptr));
bool asyncOpSucceeded = false;
// Despite the documentation indicating otherwise, the async operations
// and callbacks used here don't seem to work outside of a COM MTA.
mozilla::mscom::EnsureMTA(
[&event, &asyncOpSucceeded, &hr, &storeContext, &asyncSpr]() -> void {
auto callback =
Callback<IAsyncOperationCompletedHandler<StoreProductResult*> >(
[&asyncOpSucceeded, &event](
IAsyncOperation<StoreProductResult*>* asyncInfo,
AsyncStatus status) -> HRESULT {
asyncOpSucceeded = status == AsyncStatus::Completed;
return SetEvent(event.get());
});
hr = storeContext->GetStoreProductForCurrentAppAsync(&asyncSpr);
if (!SUCCEEDED(hr) || asyncSpr == nullptr) {
asyncOpSucceeded = false;
return;
}
hr = asyncSpr->put_Completed(callback.Get());
if (!SUCCEEDED(hr)) {
asyncOpSucceeded = false;
return;
}
DWORD ret = WaitForSingleObject(event.get(), 30000);
if (ret != WAIT_OBJECT_0) {
asyncOpSucceeded = false;
}
});
if (!asyncOpSucceeded) return NS_ERROR_FAILURE;
}
ComPtr<IStoreProductResult> productResult = nullptr;
hr = asyncSpr->GetResults(&productResult);
if (!SUCCEEDED(hr) || productResult == nullptr) return NS_ERROR_FAILURE;
ComPtr<IStoreProduct> product = nullptr;
hr = productResult->get_Product(&product);
if (!SUCCEEDED(hr) || product == nullptr) return NS_ERROR_FAILURE;
ComPtr<Collections::IVectorView<StoreSku*> > skus = nullptr;
hr = product->get_Skus(&skus);
if (!SUCCEEDED(hr) || skus == nullptr) return NS_ERROR_FAILURE;
unsigned int size;
hr = skus->get_Size(&size);
if (!SUCCEEDED(hr)) return NS_ERROR_FAILURE;
for (unsigned int i = 0; i < size; i++) {
ComPtr<IStoreSku> sku = nullptr;
hr = skus->GetAt(i, &sku);
if (!SUCCEEDED(hr) || sku == nullptr) return NS_ERROR_FAILURE;
boolean isInUserCollection = false;
hr = sku->get_IsInUserCollection(&isInUserCollection);
if (!SUCCEEDED(hr) || !isInUserCollection) continue;
ComPtr<IStoreCollectionData> scd = nullptr;
hr = sku->get_CollectionData(&scd);
if (!SUCCEEDED(hr) || scd == nullptr) continue;
HString campaignId;
hr = scd->get_CampaignId(campaignId.GetAddressOf());
if (!SUCCEEDED(hr)) continue;
unsigned int tmp;
aCampaignId.Assign(campaignId.GetRawBuffer(&tmp));
if (aCampaignId.Length() > 0) {
aCampaignId.AppendLiteral("&msstoresignedin=true");
}
}
// There's various points above that could exit without a failure.
// If we get here without a campaignId we may as well just check
// the AppStoreLicense.
if (aCampaignId.IsEmpty()) {
ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal = nullptr;
bool asyncOpSucceeded = false;
nsAutoHandle event(CreateEventW(nullptr, true, false, nullptr));
mozilla::mscom::EnsureMTA(
[&event, &asyncOpSucceeded, &hr, &storeContext, &asyncSal]() -> void {
auto callback =
Callback<IAsyncOperationCompletedHandler<StoreAppLicense*> >(
[&asyncOpSucceeded, &event](
IAsyncOperation<StoreAppLicense*>* asyncInfo,
AsyncStatus status) -> HRESULT {
asyncOpSucceeded = status == AsyncStatus::Completed;
return SetEvent(event.get());
});
hr = storeContext->GetAppLicenseAsync(&asyncSal);
if (!SUCCEEDED(hr) || asyncSal == nullptr) {
asyncOpSucceeded = false;
return;
}
hr = asyncSal->put_Completed(callback.Get());
if (!SUCCEEDED(hr)) {
asyncOpSucceeded = false;
return;
}
DWORD ret = WaitForSingleObject(event.get(), 30000);
if (ret != WAIT_OBJECT_0) {
asyncOpSucceeded = false;
}
});
if (!asyncOpSucceeded) return NS_ERROR_FAILURE;
ComPtr<IStoreAppLicense> license = nullptr;
hr = asyncSal->GetResults(&license);
if (!SUCCEEDED(hr) || license == nullptr) return NS_ERROR_FAILURE;
HString extendedData;
hr = license->get_ExtendedJsonData(extendedData.GetAddressOf());
if (!SUCCEEDED(hr)) return NS_ERROR_FAILURE;
Json::Value jsonData;
Json::Reader jsonReader;
unsigned int tmp;
nsAutoString key(extendedData.GetRawBuffer(&tmp));
if (!jsonReader.parse(NS_ConvertUTF16toUTF8(key).get(), jsonData, false)) {
return NS_ERROR_FAILURE;
}
if (jsonData.isMember(CAMPAIGN_ID_JSON_FIELD_NAME) &&
jsonData[CAMPAIGN_ID_JSON_FIELD_NAME].isString()) {
aCampaignId.Assign(
NS_ConvertUTF8toUTF16(
jsonData[CAMPAIGN_ID_JSON_FIELD_NAME].asString().c_str())
.get());
if (aCampaignId.Length() > 0) {
aCampaignId.AppendLiteral("&msstoresignedin=false");
}
}
}
// No matter what happens in either block above, if they don't exit with a
// failure we managed to successfully pull the campaignId from somewhere
// (even if its empty).
return NS_OK;
#endif // __MINGW32__
}
} // namespace system
} // namespace toolkit
} // namespace mozilla