diff options
Diffstat (limited to 'toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp')
-rw-r--r-- | toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp new file mode 100644 index 0000000000..2c2d3ffbc5 --- /dev/null +++ b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp @@ -0,0 +1,462 @@ +/* -*- 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/dom/Promise.h" +#include "mozilla/Logging.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#ifndef __MINGW32__ +# include "nsProxyRelease.h" +# 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 + 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 + 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__ +} + +static HRESULT RejectOnMainThread( + const char* aName, nsMainThreadPtrHandle<dom::Promise> promiseHolder, + nsresult result) { + DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction( + aName, [promiseHolder = std::move(promiseHolder), result]() { + promiseHolder.get()->MaybeReject(result); + })); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); + + return S_OK; +} + +#ifndef __MINGW32__ +// forward declarations +static void GetCampaignIdFromStoreProductOnBackgroundThread( + ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr, + ComPtr<IStoreContext> storeContext, + nsMainThreadPtrHandle<dom::Promise> promiseHolder); +static void GetCampaignIdFromLicenseOnBackgroundThread( + ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal, + nsMainThreadPtrHandle<dom::Promise> promiseHolder, + nsAutoString aCampaignId); +#endif // __MINGW32__ + +static std::tuple<nsMainThreadPtrHolder<dom::Promise>*, nsresult> +InitializePromise(JSContext* aCx, dom::Promise** aPromise) { + *aPromise = nullptr; + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return std::make_tuple(nullptr, NS_ERROR_FAILURE); + } + + ErrorResult erv; + RefPtr<dom::Promise> outer = dom::Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return std::make_tuple(nullptr, erv.StealNSResult()); + } + + auto promiseHolder = new nsMainThreadPtrHolder<dom::Promise>( + "nsWindowsPackageManager::CampaignId Promise", outer); + + outer.forget(aPromise); + + return std::make_tuple(promiseHolder, NS_OK); +} + +NS_IMETHODIMP +nsWindowsPackageManager::CampaignId(JSContext* aCx, dom::Promise** aPromise) { + NS_ENSURE_ARG_POINTER(aPromise); + +#ifdef __MINGW32__ + return NS_ERROR_NOT_IMPLEMENTED; +#else + + // This is only relevant for MSIX packaged builds. + if (!mozilla::HasPackageIdentity()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + auto [unwrappedPromiseHolder, result] = InitializePromise(aCx, aPromise); + + NS_ENSURE_SUCCESS(result, result); + + nsMainThreadPtrHandle<dom::Promise> promiseHolder(unwrappedPromiseHolder); + + 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) { + promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + ComPtr<IStoreContext> storeContext = nullptr; + hr = scStatics->GetDefault(&storeContext); + if (!SUCCEEDED(hr) || storeContext == nullptr) { + promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + /* Despite the documentation not saying otherwise, these don't work + * consistently when called from the main thread. I tried the two scenarios + * described above multiple times, and couldn't consistently get the campaign + * id when running this code async on the main thread. So instead, this + * dispatches to a background task to do the work, and then dispatches to the + * main thread to call back into the Javascript asynchronously + * + */ + result = NS_DispatchBackgroundTask( + NS_NewRunnableFunction( + __func__, + [storeContext = std::move(storeContext), + promiseHolder = std::move(promiseHolder)]() -> void { + ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr = nullptr; + + auto hr = + storeContext->GetStoreProductForCurrentAppAsync(&asyncSpr); + if (!SUCCEEDED(hr) || asyncSpr == nullptr) { + RejectOnMainThread(__func__, std::move(promiseHolder), + NS_ERROR_FAILURE); + return; + } + + auto callback = + Callback<IAsyncOperationCompletedHandler<StoreProductResult*> >( + [promiseHolder, asyncSpr, + storeContext = std::move(storeContext)]( + IAsyncOperation<StoreProductResult*>* asyncInfo, + AsyncStatus status) -> HRESULT { + bool asyncOpSucceeded = status == AsyncStatus::Completed; + if (!asyncOpSucceeded) { + return RejectOnMainThread(__func__, + std::move(promiseHolder), + NS_ERROR_FAILURE); + } + + GetCampaignIdFromStoreProductOnBackgroundThread( + std::move(asyncSpr), std::move(storeContext), + std::move(promiseHolder)); + + return S_OK; + }); + + hr = asyncSpr->put_Completed(callback.Get()); + if (!SUCCEEDED(hr)) { + RejectOnMainThread(__func__, std::move(promiseHolder), + NS_ERROR_FAILURE); + return; + } + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + + if (NS_FAILED(result)) { + promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + return NS_OK; +#endif // __MINGW32__ +} + +#ifndef __MINGW32__ +static void GetCampaignIdFromStoreProductOnBackgroundThread( + ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr, + ComPtr<IStoreContext> storeContext, + nsMainThreadPtrHandle<dom::Promise> promiseHolder) { + ComPtr<IStoreProductResult> productResult = nullptr; + + auto hr = asyncSpr->GetResults(&productResult); + if (!SUCCEEDED(hr) || productResult == nullptr) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + nsAutoString campaignId; + + ComPtr<IStoreProduct> product = nullptr; + hr = productResult->get_Product(&product); + if (SUCCEEDED(hr) && (product != nullptr)) { + ComPtr<Collections::IVectorView<StoreSku*> > skus = nullptr; + hr = product->get_Skus(&skus); + if (!SUCCEEDED(hr) || skus == nullptr) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + unsigned int size; + hr = skus->get_Size(&size); + if (!SUCCEEDED(hr)) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + for (unsigned int i = 0; i < size; i++) { + ComPtr<IStoreSku> sku = nullptr; + hr = skus->GetAt(i, &sku); + if (!SUCCEEDED(hr) || sku == nullptr) { + RejectOnMainThread(__func__, std::move(promiseHolder), + NS_ERROR_FAILURE); + return; + } + + 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 msCampaignId; + hr = scd->get_CampaignId(msCampaignId.GetAddressOf()); + if (!SUCCEEDED(hr)) continue; + + unsigned int tmp; + campaignId.Assign(msCampaignId.GetRawBuffer(&tmp)); + if (campaignId.Length() > 0) { + campaignId.AppendLiteral("&msstoresignedin=true"); + } + } + } + + if (!campaignId.IsEmpty()) { + // If we got here, it means that campaignId has been processed and can be + // sent back via the promise + DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [promiseHolder = std::move(promiseHolder), + campaignId = std::move(campaignId)]() { + promiseHolder.get()->MaybeResolve(campaignId); + })); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); + + return; + } + + // 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. + ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal = nullptr; + + hr = storeContext->GetAppLicenseAsync(&asyncSal); + if (!SUCCEEDED(hr) || asyncSal == nullptr) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + auto callback = Callback<IAsyncOperationCompletedHandler<StoreAppLicense*> >( + [asyncSal, promiseHolder, campaignId = std::move(campaignId)]( + IAsyncOperation<StoreAppLicense*>* asyncInfo, + AsyncStatus status) -> HRESULT { + bool asyncOpSucceeded = status == AsyncStatus::Completed; + if (!asyncOpSucceeded) { + return RejectOnMainThread(__func__, std::move(promiseHolder), + NS_ERROR_FAILURE); + } + + GetCampaignIdFromLicenseOnBackgroundThread(std::move(asyncSal), + std::move(promiseHolder), + std::move(campaignId)); + + return S_OK; + }); + + hr = asyncSal->put_Completed(callback.Get()); + if (!SUCCEEDED(hr)) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } +} + +static void GetCampaignIdFromLicenseOnBackgroundThread( + ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal, + nsMainThreadPtrHandle<dom::Promise> promiseHolder, + nsAutoString aCampaignId) { + ComPtr<IStoreAppLicense> license = nullptr; + auto hr = asyncSal->GetResults(&license); + if (!SUCCEEDED(hr) || license == nullptr) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + HString extendedData; + hr = license->get_ExtendedJsonData(extendedData.GetAddressOf()); + if (!SUCCEEDED(hr)) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + Json::Value jsonData; + Json::Reader jsonReader; + + unsigned int tmp; + nsAutoString key(extendedData.GetRawBuffer(&tmp)); + if (!jsonReader.parse(NS_ConvertUTF16toUTF8(key).get(), jsonData, false)) { + RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE); + return; + } + + 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"); + } + } + + DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [promiseHolder = std::move(promiseHolder), + aCampaignId = std::move(aCampaignId)]() { + promiseHolder.get()->MaybeResolve(aCampaignId); + })); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} +#endif // __MINGW32__ + +} // namespace system +} // namespace toolkit +} // namespace mozilla |