summaryrefslogtreecommitdiffstats
path: root/toolkit/system/windowsPackageManager
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/system/windowsPackageManager')
-rw-r--r--toolkit/system/windowsPackageManager/components.conf14
-rw-r--r--toolkit/system/windowsPackageManager/moz.build26
-rw-r--r--toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl42
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp462
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.h28
-rw-r--r--toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp35
-rw-r--r--toolkit/system/windowsPackageManager/tests/gtest/moz.build18
7 files changed, 625 insertions, 0 deletions
diff --git a/toolkit/system/windowsPackageManager/components.conf b/toolkit/system/windowsPackageManager/components.conf
new file mode 100644
index 0000000000..a1bab60e03
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/components.conf
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{c75da378-521f-11ec-84cc-336cd3921c24}',
+ 'contract_ids': ['@mozilla.org/windows-package-manager;1'],
+ 'type': 'mozilla::toolkit::system::nsWindowsPackageManager',
+ 'headers': ['/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h']
+ },
+]
diff --git a/toolkit/system/windowsPackageManager/moz.build b/toolkit/system/windowsPackageManager/moz.build
new file mode 100644
index 0000000000..13f940edbe
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Installer")
+
+TEST_DIRS += ["tests/gtest"]
+
+SOURCES += ["nsWindowsPackageManager.cpp"]
+
+LOCAL_INCLUDES += ["/toolkit/components/jsoncpp/include"]
+
+USE_LIBS += ["jsoncpp"]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+XPIDL_MODULE = "windows_package_manager"
+
+XPIDL_SOURCES += ["nsIWindowsPackageManager.idl"]
+
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl b/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl
new file mode 100644
index 0000000000..f22970abe2
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(ad57ac40-52f0-11ec-ada8-4f671255c4aa)]
+interface nsIWindowsPackageManager : nsISupports
+{
+/* Searches for any user installed MSIX packages whose
+ * packageFamilyName matches any of the provided `aNamePrefixes`
+ * and returns them. The Windows APIs only allow querying user
+ * installed packages without elevation, so this will not see any
+ * packages installed by another user.
+ */
+ Array<AString> findUserInstalledPackages(in Array<AString> prefix);
+
+/* When running within a Packaged App environment, returns the
+ * InstalledDate of the Package. If called when not running within
+ * a Packaged App environment, throws NS_ERROR_NOT_IMPLEMENTED.
+ * Any other others will cause NS_ERROR_FAILURE to be thrown.
+ */
+ unsigned long long getInstalledDate();
+
+/* Asynchronously retrieves the campaignId, if any, a user's Microsoft Store install is
+ * associated with. These are present if the user clicked a "ms-window-store://"
+ * or "https://" link that included a "cid" query argument the very first time
+ * they installed the app. (This value appears to be cached forever, so
+ * subsequent installs will not refresh it.) If a non-empty campaign ID is
+ * found it will be assumed to be a properly formatted attribution code and
+ * have an additional "msstoresignedin" key appended to it indicate whether or
+ * not the user was signed in when they installed the application. This key
+ * will either be set to "true" or "false".
+ *
+ * @throw NS_ERROR_NOT_IMPLEMENTED if called on Windows 8 or earlier, or from
+ * a non-packaged build.
+ * @throw NS_ERROR_FAILURE for any other errors
+ */
+ [implicit_jscontext]
+ Promise campaignId();
+};
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
diff --git a/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h
new file mode 100644
index 0000000000..30cc8a288e
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_toolkit_system_nsWindowsPackageManager_h
+#define mozilla_toolkit_system_nsWindowsPackageManager_h
+
+#include "nsIWindowsPackageManager.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+class nsWindowsPackageManager final : public nsIWindowsPackageManager {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWINDOWSPACKAGEMANAGER
+
+ private:
+ ~nsWindowsPackageManager(){};
+};
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+
+#endif // mozilla_toolkit_system_nsWindowsPackageManager_h
diff --git a/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp b/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp
new file mode 100644
index 0000000000..235f44a2f7
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+#include "nsWindowsPackageManager.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::toolkit::system;
+
+TEST(WindowsPackageManager, TestWithMatches)
+{
+ nsCOMPtr<nsIWindowsPackageManager> wpm(
+ do_GetService("@mozilla.org/windows-package-manager;1"));
+ nsTArray<nsString> prefixes, packages;
+ // We're assuming that there will always be at least _one_ Microsoft package
+ // installed when we run tests. This will _probably_ hold true.
+ prefixes.AppendElement(u"Microsoft"_ns);
+ nsresult retVal = wpm->FindUserInstalledPackages(prefixes, packages);
+ ASSERT_GT(packages.Length(), 0U);
+ ASSERT_EQ(NS_OK, retVal);
+}
+
+TEST(WindowsPackageManager, TestWithoutMatches)
+{
+ nsCOMPtr<nsIWindowsPackageManager> wpm(
+ do_GetService("@mozilla.org/windows-package-manager;1"));
+ nsTArray<nsString> prefixes, packages;
+ prefixes.AppendElement(u"DoesntExist"_ns);
+ nsresult retVal = wpm->FindUserInstalledPackages(prefixes, packages);
+ ASSERT_EQ(packages.Length(), 0U);
+ ASSERT_EQ(NS_OK, retVal);
+}
diff --git a/toolkit/system/windowsPackageManager/tests/gtest/moz.build b/toolkit/system/windowsPackageManager/tests/gtest/moz.build
new file mode 100644
index 0000000000..244ae2dfaa
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/tests/gtest/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestWindowsPackageManager.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/system/windowsPackageManager",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wshadow"]