summaryrefslogtreecommitdiffstats
path: root/toolkit/system/windowsPackageManager
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/system/windowsPackageManager
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.idl41
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp341
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.h28
-rw-r--r--toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp44
-rw-r--r--toolkit/system/windowsPackageManager/tests/gtest/moz.build18
7 files changed, 512 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..c78fc841e7
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl
@@ -0,0 +1,41 @@
+/* -*- 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();
+
+/* 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
+ */
+ AString getCampaignId();
+};
diff --git a/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp
new file mode 100644
index 0000000000..98cff7db40
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp
@@ -0,0 +1,341 @@
+/* -*- 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
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..0a50fdcbec
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "mozilla/WindowsVersion.h"
+#include "nsWindowsPackageManager.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::toolkit::system;
+
+TEST(WindowsPackageManager, TestWithMatches)
+{
+ if (!mozilla::IsWin10OrLater()) {
+ return;
+ }
+
+ 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)
+{
+ if (!mozilla::IsWin10OrLater()) {
+ return;
+ }
+
+ 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"]