summaryrefslogtreecommitdiffstats
path: root/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp')
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp462
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