Bug 1695817 - Part 6: Show name and publisher of an application owning a module. r=Gijs,fluent-reviewers,mhowell

This patch adds application info (Name and Publisher for now) in the
about:third-party page if a module is a part of an installed application,
which is registered in the registry and shown in Windows Control Panel.
To achieve this, we parse the registry to collect installed applications
in the background task.

Differential Revision: https://phabricator.services.mozilla.com/D109306
This commit is contained in:
Toshihito Kikuchi 2021-05-27 21:14:14 +00:00
Родитель 1a79ad5407
Коммит e8ef8d92ad
14 изменённых файлов: 872 добавлений и 0 удалений

Просмотреть файл

@ -6,10 +6,12 @@
#include "AboutThirdParty.h"
#include "AboutThirdPartyUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/NativeNt.h"
#include "mozilla/StaticPtr.h"
#include "MsiDatabase.h"
#include "nsComponentManagerUtils.h"
#include "nsIWindowsRegKey.h"
#include "nsThreadUtils.h"
@ -56,6 +58,292 @@ void EnumSubkeys(nsIWindowsRegKey* aRegBase, const CallbackT& aCallback) {
} // anonymous namespace
InstallLocationComparator::InstallLocationComparator(const nsAString& aFilePath)
: mFilePath(aFilePath) {}
int InstallLocationComparator::operator()(
const InstallLocationT& aLocation) const {
// Firstly we check whether mFilePath begins with aLocation.
// If yes, mFilePath is a part of the target installation,
// so we return 0 showing match.
const nsAString& location = aLocation.first();
size_t locationLen = location.Length();
if (locationLen <= mFilePath.Length() &&
nsCaseInsensitiveStringComparator(mFilePath.BeginReading(),
location.BeginReading(), locationLen,
locationLen) == 0) {
return 0;
}
return CompareIgnoreCase(mFilePath, location);
}
// The InstalledApplications class behaves like Chrome's InstalledApplications,
// which collects installed applications from two resources below.
//
// 1) Path strings in MSI package components
// An MSI package is consisting of multiple components. This class collects
// MSI components representing a file and stores them as a hash table.
//
// 2) Install location paths in the InstallLocation registry value
// If an application's installer is not MSI but sets the InstallLocation
// registry value, we can use it to search for an application by comparing
// a target module is located under that location path. This class stores
// location path strings as a sorted array so that we can binary-search it.
class InstalledApplications final {
// Limit the number of entries to avoid consuming too much memory
constexpr static uint32_t kMaxComponents = 1000000;
constexpr static uint32_t kMaxInstallLocations = 1000;
nsCOMPtr<nsIWindowsRegKey> mInstallerData;
nsCOMPtr<nsIInstalledApplication> mCurrentApp;
ComponentPathMapT mComponentPaths;
nsTArray<InstallLocationT> mLocations;
void AddInstallLocation(nsIWindowsRegKey* aProductSubKey) {
nsString location;
if (NS_FAILED(
aProductSubKey->ReadStringValue(u"InstallLocation"_ns, location)) ||
location.IsEmpty()) {
return;
}
if (location.Last() != u'\\') {
location.Append(u'\\');
}
mLocations.EmplaceBack(location, this->mCurrentApp);
}
void AddComponentGuid(const nsString& aPackedProductGuid,
const nsString& aPackedComponentGuid) {
nsAutoString componentSubkey(L"Components\\");
componentSubkey += aPackedComponentGuid;
// Pick a first value in the subkeys under |componentSubkey|.
nsString componentPath;
EnumSubkeys(mInstallerData, [&aPackedProductGuid, &componentSubkey,
&componentPath](const nsString& aSid,
nsIWindowsRegKey* aSidSubkey) {
// If we have a value in |componentPath|, the loop should
// have been stopped.
MOZ_ASSERT(componentPath.IsEmpty());
nsCOMPtr<nsIWindowsRegKey> compKey;
nsresult rv =
aSidSubkey->OpenChild(componentSubkey, nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(compKey));
if (NS_FAILED(rv)) {
return CallbackResult::Continue;
}
nsString compData;
if (NS_FAILED(compKey->ReadStringValue(aPackedProductGuid, compData))) {
return CallbackResult::Continue;
}
if (!CorrectMsiComponentPath(compData)) {
return CallbackResult::Continue;
}
componentPath = std::move(compData);
return CallbackResult::Stop;
});
if (componentPath.IsEmpty()) {
return;
}
// Use a full path as a key rather than a leaf name because
// the same name's module can be installed under system32
// and syswow64.
mComponentPaths.WithEntryHandle(componentPath, [this](auto&& addPtr) {
if (addPtr) {
// If the same file appeared in multiple installations, we set null
// for its value because there is no way to know which installation is
// the real owner.
addPtr.Data() = nullptr;
} else {
addPtr.Insert(this->mCurrentApp);
}
});
}
void AddProduct(const nsString& aProductId,
nsIWindowsRegKey* aProductSubKey) {
nsString displayName;
if (NS_FAILED(
aProductSubKey->ReadStringValue(u"DisplayName"_ns, displayName)) ||
displayName.IsEmpty()) {
// Skip if no name is found.
return;
}
nsString publisher;
if (NS_SUCCEEDED(
aProductSubKey->ReadStringValue(u"Publisher"_ns, publisher)) &&
publisher.EqualsIgnoreCase("Microsoft") &&
publisher.EqualsIgnoreCase("Microsoft Corporation")) {
// Skip if the publisher is Microsoft because it's not a third-party.
// We don't skip an application without the publisher name.
return;
}
mCurrentApp =
new InstalledApplication(std::move(displayName), std::move(publisher));
// Try an MSI database first because it's more accurate,
// then fall back to the InstallLocation key.
do {
if (!mInstallerData) {
break;
}
nsAutoString packedProdGuid;
if (!MsiPackGuid(aProductId, packedProdGuid)) {
break;
}
auto db = MsiDatabase::FromProductId(aProductId.get());
if (db.isNothing()) {
break;
}
db->ExecuteSingleColumnQuery(
L"SELECT DISTINCT ComponentId FROM Component",
[this, &packedProdGuid](const wchar_t* aComponentGuid) {
if (this->mComponentPaths.Count() >= kMaxComponents) {
return MsiDatabase::CallbackResult::Stop;
}
nsAutoString packedComponentGuid;
if (MsiPackGuid(nsDependentString(aComponentGuid),
packedComponentGuid)) {
this->AddComponentGuid(packedProdGuid, packedComponentGuid);
}
return MsiDatabase::CallbackResult::Continue;
});
// We've decided to collect data from an MSI database.
// Exiting the function.
return;
} while (false);
if (mLocations.Length() >= kMaxInstallLocations) {
return;
}
// If we cannot use an MSI database for any reason,
// try the InstallLocation key.
AddInstallLocation(aProductSubKey);
}
public:
InstalledApplications() {
nsresult rv;
nsCOMPtr<nsIWindowsRegKey> regKey =
do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
if (NS_SUCCEEDED(rv) &&
NS_SUCCEEDED(regKey->Open(
nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"
u"Installer\\UserData"_ns,
nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64))) {
mInstallerData.swap(regKey);
}
}
~InstalledApplications() = default;
InstalledApplications(InstalledApplications&&) = delete;
InstalledApplications& operator=(InstalledApplications&&) = delete;
InstalledApplications(const InstalledApplications&) = delete;
InstalledApplications& operator=(const InstalledApplications&) = delete;
void Collect(ComponentPathMapT& aOutComponentPaths,
nsTArray<InstallLocationT>& aOutLocations) {
const nsLiteralString kUninstallKey(
u"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
static const uint16_t sProcessor = []() -> uint16_t {
SYSTEM_INFO si;
::GetSystemInfo(&si);
return si.wProcessorArchitecture;
}();
nsresult rv;
nsCOMPtr<nsIWindowsRegKey> regKey =
do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
if (NS_FAILED(rv)) {
return;
}
switch (sProcessor) {
case PROCESSOR_ARCHITECTURE_INTEL:
rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
kUninstallKey, nsIWindowsRegKey::ACCESS_READ);
if (NS_SUCCEEDED(rv)) {
EnumSubkeys(regKey, [this](const nsString& aProductId,
nsIWindowsRegKey* aProductSubKey) {
this->AddProduct(aProductId, aProductSubKey);
return CallbackResult::Continue;
});
}
break;
case PROCESSOR_ARCHITECTURE_AMD64:
// A 64-bit application may be installed by a 32-bit installer,
// or vice versa. So we enumerate both views regardless of
// the process's (not processor's) bitness.
rv = regKey->Open(
nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kUninstallKey,
nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64);
if (NS_SUCCEEDED(rv)) {
EnumSubkeys(regKey, [this](const nsString& aProductId,
nsIWindowsRegKey* aProductSubKey) {
this->AddProduct(aProductId, aProductSubKey);
return CallbackResult::Continue;
});
}
rv = regKey->Open(
nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kUninstallKey,
nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_32);
if (NS_SUCCEEDED(rv)) {
EnumSubkeys(regKey, [this](const nsString& aProductId,
nsIWindowsRegKey* aProductSubKey) {
this->AddProduct(aProductId, aProductSubKey);
return CallbackResult::Continue;
});
}
break;
default:
MOZ_ASSERT(false, "Unsupported CPU architecture");
return;
}
// The "HKCU\SOFTWARE\" subtree is shared between the 32-bits and 64 bits
// views. No need to enumerate wow6432node for HKCU.
// https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys
rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, kUninstallKey,
nsIWindowsRegKey::ACCESS_READ);
if (NS_SUCCEEDED(rv)) {
EnumSubkeys(regKey, [this](const nsString& aProductId,
nsIWindowsRegKey* aProductSubKey) {
this->AddProduct(aProductId, aProductSubKey);
return CallbackResult::Continue;
});
}
aOutComponentPaths.SwapElements(mComponentPaths);
mLocations.Sort([](const InstallLocationT& aA, const InstallLocationT& aB) {
return CompareIgnoreCase(aA.first(), aB.first());
});
aOutLocations.SwapElements(mLocations);
}
};
class KnownModule final {
static KnownModule sKnownExtensions[static_cast<int>(KnownModuleType::Last)];
@ -317,8 +605,25 @@ namespace mozilla {
static StaticRefPtr<AboutThirdParty> sSingleton;
NS_IMPL_ISUPPORTS(InstalledApplication, nsIInstalledApplication);
NS_IMPL_ISUPPORTS(AboutThirdParty, nsIAboutThirdParty);
InstalledApplication::InstalledApplication(nsString&& aAppName,
nsString&& aPublisher)
: mName(std::move(aAppName)), mPublisher(std::move(aPublisher)) {}
NS_IMETHODIMP
InstalledApplication::GetName(nsAString& aResult) {
aResult = mName;
return NS_OK;
}
NS_IMETHODIMP
InstalledApplication::GetPublisher(nsAString& aResult) {
aResult = mPublisher;
return NS_OK;
}
/*static*/
already_AddRefed<AboutThirdParty> AboutThirdParty::GetSingleton() {
if (!sSingleton) {
@ -355,6 +660,9 @@ void AboutThirdParty::BackgroundThread() {
self->AddKnownModule(aDllPath, aType);
});
InstalledApplications apps;
apps.Collect(mComponentPaths, mLocations);
mWorkerState = WorkerState::Done;
}
@ -395,6 +703,41 @@ NS_IMETHODIMP AboutThirdParty::LookupModuleType(const nsAString& aLeafName,
return NS_OK;
}
NS_IMETHODIMP AboutThirdParty::LookupApplication(
const nsAString& aModulePath, nsIInstalledApplication** aResult) {
MOZ_ASSERT(NS_IsMainThread());
*aResult = nullptr;
if (mWorkerState != WorkerState::Done) {
return NS_OK;
}
const nsDependentSubstring leaf = nt::GetLeafName(aModulePath);
if (leaf.IsEmpty()) {
return NS_OK;
}
// Look up the component path's map first because it's more accurate
// than the location's array.
nsCOMPtr<nsIInstalledApplication> app = mComponentPaths.Get(aModulePath);
if (app) {
app.forget(aResult);
return NS_OK;
}
auto bounds = EqualRange(mLocations, 0, mLocations.Length(),
InstallLocationComparator(aModulePath));
// If more than one application includes the module, we return null
// because there is no way to know which is the real owner.
if (bounds.second() - bounds.first() != 1) {
return NS_OK;
}
app = mLocations[bounds.first()].second();
app.forget(aResult);
return NS_OK;
}
RefPtr<BackgroundThreadPromise> AboutThirdParty::CollectSystemInfoAsync() {
MOZ_ASSERT(NS_IsMainThread());

Просмотреть файл

@ -9,10 +9,17 @@
#include "mozilla/MozPromise.h"
#include "nsIAboutThirdParty.h"
#include "nsInterfaceHashtable.h"
#include "nsTArray.h"
#include "nsTHashMap.h"
namespace mozilla {
using InstallLocationT =
CompactPair<nsString, nsCOMPtr<nsIInstalledApplication>>;
using ComponentPathMapT = nsInterfaceHashtable<nsStringCaseInsensitiveHashKey,
nsIInstalledApplication>;
enum class KnownModuleType : uint32_t {
Ime = 0,
IconOverlay,
@ -29,6 +36,32 @@ enum class KnownModuleType : uint32_t {
Last,
};
struct InstallLocationComparator {
const nsAString& mFilePath;
explicit InstallLocationComparator(const nsAString& aFilePath);
int operator()(const InstallLocationT& aLocation) const;
};
class InstalledApplication final : public nsIInstalledApplication {
nsString mName;
nsString mPublisher;
~InstalledApplication() = default;
public:
InstalledApplication() = default;
InstalledApplication(nsString&& aAppName, nsString&& aPublisher);
InstalledApplication(InstalledApplication&&) = delete;
InstalledApplication& operator=(InstalledApplication&&) = delete;
InstalledApplication(const InstalledApplication&) = delete;
InstalledApplication& operator=(const InstalledApplication&) = delete;
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINSTALLEDAPPLICATION
};
using BackgroundThreadPromise =
MozPromise<bool /* aIgnored */, nsresult, /* IsExclusive */ false>;
@ -42,6 +75,8 @@ class AboutThirdParty final : public nsIAboutThirdParty {
Atomic<WorkerState, SequentiallyConsistent> mWorkerState;
RefPtr<BackgroundThreadPromise::Private> mPromise;
nsTHashMap<nsStringCaseInsensitiveHashKey, uint32_t> mKnownModules;
ComponentPathMapT mComponentPaths;
nsTArray<InstallLocationT> mLocations;
~AboutThirdParty() = default;
void BackgroundThread();

69
toolkit/components/aboutthirdparty/AboutThirdPartyUtils.cpp поставляемый Normal file
Просмотреть файл

@ -0,0 +1,69 @@
/* -*- 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 "AboutThirdPartyUtils.h"
#include "nsUnicharUtils.h"
namespace mozilla {
int32_t CompareIgnoreCase(const nsAString& aStr1, const nsAString& aStr2) {
uint32_t len1 = aStr1.Length();
uint32_t len2 = aStr2.Length();
uint32_t lenMin = XPCOM_MIN(len1, len2);
int32_t result = nsCaseInsensitiveStringComparator(
aStr1.BeginReading(), aStr2.BeginReading(), lenMin, lenMin);
return result ? result : len1 - len2;
}
bool MsiPackGuid(const nsAString& aGuid, nsAString& aPacked) {
if (aGuid.Length() != 38 || aGuid.First() != u'{' || aGuid.Last() != u'}') {
return false;
}
constexpr int kPackedLength = 32;
const uint8_t kIndexMapping[kPackedLength] = {
// clang-format off
0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
0x0d, 0x0c, 0x0b, 0x0a, 0x12, 0x11, 0x10, 0x0f,
0x15, 0x14, 0x17, 0x16, 0x1a, 0x19, 0x1c, 0x1b,
0x1e, 0x1d, 0x20, 0x1f, 0x22, 0x21, 0x24, 0x23,
// clang-format on
};
int index = 0;
aPacked.SetLength(kPackedLength);
for (auto iter = aPacked.BeginWriting(), strEnd = aPacked.EndWriting();
iter != strEnd; ++iter, ++index) {
*iter = aGuid[kIndexMapping[index]];
}
return true;
}
bool CorrectMsiComponentPath(nsAString& aPath) {
if (aPath.Length() < 3 || !aPath.BeginReading()[0]) {
return false;
}
char16_t* strBegin = aPath.BeginWriting();
if (strBegin[1] == u'?') {
strBegin[1] = strBegin[0] == u'\\' ? u'\\' : u':';
}
if (strBegin[1] != u':' || strBegin[2] != u'\\') {
return false;
}
if (aPath.Length() > 3 && aPath.BeginReading()[3] == u'?') {
aPath.ReplaceLiteral(3, 1, u"");
}
return true;
}
} // namespace mozilla

36
toolkit/components/aboutthirdparty/AboutThirdPartyUtils.h поставляемый Normal file
Просмотреть файл

@ -0,0 +1,36 @@
/* -*- 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/. */
#ifndef __AboutThirdPartyUtils_h__
#define __AboutThirdPartyUtils_h__
#include "nsString.h"
namespace mozilla {
// Define a custom case-insensitive string comparator wrapping
// nsCaseInsensitiveStringComparator to sort items alphabetically because
// nsCaseInsensitiveStringComparator sorts items by the length first.
int32_t CompareIgnoreCase(const nsAString& aStr1, const nsAString& aStr2);
// Mimicking the logic in msi!PackGUID to convert a GUID string to
// a packed GUID used as registry keys.
bool MsiPackGuid(const nsAString& aGuid, nsAString& aPacked);
// Mimicking the validation logic for a path in msi!_GetComponentPath
//
// Accecpted patterns and conversions:
// C:\path --> C:\path
// C?\path --> C:\path
// C:\?path --> C:\path
//
// msi!_GetComponentPath also checks the existence by calling
// RegOpenKeyExW or GetFileAttributesExW, but we don't need it.
bool CorrectMsiComponentPath(nsAString& aPath);
} // namespace mozilla
#endif // __AboutThirdPartyUtils_h__

88
toolkit/components/aboutthirdparty/MsiDatabase.cpp поставляемый Normal file
Просмотреть файл

@ -0,0 +1,88 @@
/* -*- 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 "MsiDatabase.h"
#ifdef UNICODE
# define MSIDBOPEN_READONLY_W MSIDBOPEN_READONLY
# define INSTALLPROPERTY_LOCALPACKAGE_W INSTALLPROPERTY_LOCALPACKAGE
#else
// MSIDBOPEN_READONLY is defined as `(LPCTSTR)0` in msiquery.h, so we need to
// cast it to LPCWSTR.
# define MSIDBOPEN_READONLY_W reinterpret_cast<LPCWSTR>(MSIDBOPEN_READONLY)
// INSTALLPROPERTY_LOCALPACKAGE is defined as `__TEXT("LocalPackage")` in msi.h,
// so we need to define a wchar_t version.
# define INSTALLPROPERTY_LOCALPACKAGE_W L"LocalPackage"
#endif // UNICODE
namespace mozilla {
/*static*/
UniquePtr<wchar_t[]> MsiDatabase::GetRecordString(MSIHANDLE aRecord,
UINT aFieldIndex) {
// The 3rd parameter of MsiRecordGetStringW must not be nullptr.
wchar_t kEmptyString[] = L"";
DWORD len = 0;
UINT ret = ::MsiRecordGetStringW(aRecord, aFieldIndex, kEmptyString, &len);
if (ret != ERROR_MORE_DATA) {
return nullptr;
}
// |len| returned from MsiRecordGetStringW does not include
// a null-character, but a length to pass to MsiRecordGetStringW
// needs to include a null-character.
++len;
auto buf = MakeUnique<wchar_t[]>(len);
ret = ::MsiRecordGetStringW(aRecord, aFieldIndex, buf.get(), &len);
if (ret != ERROR_SUCCESS) {
return nullptr;
}
return buf;
}
MsiDatabase::MsiDatabase(const wchar_t* aDatabasePath) {
MSIHANDLE handle = 0;
UINT ret = ::MsiOpenDatabaseW(aDatabasePath, MSIDBOPEN_READONLY_W, &handle);
if (ret != ERROR_SUCCESS) {
return;
}
mDatabase.own(handle);
}
Maybe<MsiDatabase> MsiDatabase::FromProductId(const wchar_t* aProductId) {
DWORD len = MAX_PATH;
wchar_t bufStack[MAX_PATH];
UINT ret = ::MsiGetProductInfoW(aProductId, INSTALLPROPERTY_LOCALPACKAGE_W,
bufStack, &len);
if (ret == ERROR_SUCCESS) {
return Some(MsiDatabase(bufStack));
}
if (ret != ERROR_MORE_DATA) {
return Nothing();
}
// |len| returned from MsiGetProductInfoW does not include
// a null-character, but a length to pass to MsiGetProductInfoW
// needs to include a null-character.
++len;
std::unique_ptr<wchar_t[]> bufHeap(new wchar_t[len]);
ret = ::MsiGetProductInfoW(aProductId, INSTALLPROPERTY_LOCALPACKAGE_W,
bufHeap.get(), &len);
if (ret == ERROR_SUCCESS) {
return Some(MsiDatabase(bufHeap.get()));
}
return Nothing();
}
MsiDatabase::operator bool() const { return !!mDatabase; }
} // namespace mozilla

94
toolkit/components/aboutthirdparty/MsiDatabase.h поставляемый Normal file
Просмотреть файл

@ -0,0 +1,94 @@
/* -*- 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/. */
#ifndef __MsiDatabase_h__
#define __MsiDatabase_h__
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "nsWindowsHelpers.h"
#include <windows.h>
#include <msi.h>
#include <msiquery.h>
namespace mozilla {
class MsiDatabase final {
static UniquePtr<wchar_t[]> GetRecordString(MSIHANDLE aRecord,
UINT aFieldIndex);
nsAutoMsiHandle mDatabase;
MsiDatabase() = default;
explicit MsiDatabase(const wchar_t* aDatabasePath);
public:
// A callback function passed to ExecuteSingleColumnQuery uses this type
// to control the enumeration loop.
enum class CallbackResult { Continue, Stop };
static Maybe<MsiDatabase> FromProductId(const wchar_t* aProductId);
MsiDatabase(const MsiDatabase&) = delete;
MsiDatabase& operator=(const MsiDatabase&) = delete;
MsiDatabase(MsiDatabase&& aOther) : mDatabase(aOther.mDatabase.disown()) {}
MsiDatabase& operator=(MsiDatabase&& aOther) {
if (this != &aOther) {
mDatabase.own(aOther.mDatabase.disown());
}
return *this;
}
explicit operator bool() const;
template <typename CallbackT>
bool ExecuteSingleColumnQuery(const wchar_t* aQuery,
const CallbackT& aCallback) const {
MSIHANDLE handle;
UINT ret = ::MsiDatabaseOpenViewW(mDatabase, aQuery, &handle);
if (ret != ERROR_SUCCESS) {
return false;
}
nsAutoMsiHandle view(handle);
ret = ::MsiViewExecute(view, 0);
if (ret != ERROR_SUCCESS) {
return false;
}
for (;;) {
ret = ::MsiViewFetch(view, &handle);
if (ret == ERROR_NO_MORE_ITEMS) {
break;
} else if (ret != ERROR_SUCCESS) {
return false;
}
nsAutoMsiHandle record(handle);
UniquePtr<wchar_t[]> guidStr = GetRecordString(record, 1);
if (!guidStr) {
continue;
}
CallbackResult result = aCallback(guidStr.get());
if (result == CallbackResult::Continue) {
continue;
} else if (result == CallbackResult::Stop) {
break;
} else {
MOZ_ASSERT_UNREACHABLE("Unexpected CallbackResult.");
}
}
return true;
}
};
} // namespace mozilla
#endif // __MsiDatabase_h__

Просмотреть файл

@ -58,6 +58,9 @@ async function fetchData() {
module.typeFlags = AboutThirdParty.lookupModuleType(
module.dllFile?.leafName
);
module.application = AboutThirdParty.lookupApplication(
module.dllFile?.path
);
}
for (const [proc, perProc] of Object.entries(data.processes)) {
@ -166,6 +169,10 @@ function copyDataToClipboard(aData) {
if (module.companyName) {
copied.companyName = module.companyName;
}
if (module.application) {
copied.applicationName = module.application.name;
copied.applicationPublisher = module.application.publisher;
}
if (Array.isArray(module.events)) {
copied.events = module.events.map(event => {
@ -225,6 +232,18 @@ function visualizeData(aData) {
const modDetailContainer = newCard.querySelector(".module-details");
if (module.application) {
modDetailContainer.appendChild(
createDetailRow("third-party-detail-app", module.application.name)
);
modDetailContainer.appendChild(
createDetailRow(
"third-party-detail-publisher",
module.application.publisher
)
);
}
if (module.fileVersion) {
modDetailContainer.appendChild(
createDetailRow("third-party-detail-version", module.fileVersion)

Просмотреть файл

@ -21,4 +21,12 @@ EXPORTS.mozilla += [
SOURCES += [
"AboutThirdParty.cpp",
"AboutThirdPartyUtils.cpp",
"MsiDatabase.cpp",
]
OS_LIBS += ["msi"]
DELAYLOAD_DLLS += ["msi.dll"]
if CONFIG["ENABLE_TESTS"]:
DIRS += ["tests/gtest"]

Просмотреть файл

@ -5,6 +5,13 @@
#include "nsISupports.idl"
[scriptable, uuid(063813a0-85d8-4e77-80ea-b61292c0493d)]
interface nsIInstalledApplication : nsISupports
{
readonly attribute AString name;
readonly attribute AString publisher;
};
[scriptable, uuid(d33ff086-b328-4ae6-aaf5-52d41aa5df38)]
interface nsIAboutThirdParty : nsISupports
{
@ -21,6 +28,12 @@ interface nsIAboutThirdParty : nsISupports
*/
unsigned long lookupModuleType(in AString aLeafName);
/**
* Returns an object representing an application which includes
* the given path of a module in its installation.
*/
nsIInstalledApplication lookupApplication(in AString aModulePath);
/**
* Posts a background task to collect system information and resolves
* the returned promise when the task is finished.

Просмотреть файл

@ -0,0 +1,125 @@
/* -*- Mode: C++; tab-width: 8; 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 https://mozilla.org/MPL/2.0/. */
#include <windows.h>
#include "gtest/gtest.h"
#include "../../AboutThirdPartyUtils.h"
#include "mozilla/AboutThirdParty.h"
#include "mozilla/ArrayUtils.h"
#include "nsTArray.h"
using namespace mozilla;
#define WEATHER_RU u"\x041F\x043E\x0433\x043E\x0434\x0430"_ns
#define WEATHER_JA u"\x5929\x6C17"_ns
TEST(AboutThirdParty, CompareIgnoreCase)
{
EXPECT_EQ(CompareIgnoreCase(u""_ns, u""_ns), 0);
EXPECT_EQ(CompareIgnoreCase(u"abc"_ns, u"aBc"_ns), 0);
EXPECT_LT(CompareIgnoreCase(u"a"_ns, u"ab"_ns), 0);
EXPECT_GT(CompareIgnoreCase(u"ab"_ns, u"A"_ns), 0);
EXPECT_LT(CompareIgnoreCase(u""_ns, u"aB"_ns), 0);
EXPECT_GT(CompareIgnoreCase(u"ab"_ns, u""_ns), 0);
// non-ascii testcases
EXPECT_EQ(CompareIgnoreCase(WEATHER_JA, WEATHER_JA), 0);
EXPECT_EQ(CompareIgnoreCase(WEATHER_RU, WEATHER_RU), 0);
EXPECT_LT(CompareIgnoreCase(WEATHER_RU, WEATHER_JA), 0);
EXPECT_GT(CompareIgnoreCase(WEATHER_JA, WEATHER_RU), 0);
EXPECT_EQ(CompareIgnoreCase(WEATHER_RU u"x"_ns WEATHER_JA,
WEATHER_RU u"X"_ns WEATHER_JA),
0);
EXPECT_GT(
CompareIgnoreCase(WEATHER_RU u"a"_ns WEATHER_JA, WEATHER_RU u"A"_ns), 0);
EXPECT_LT(CompareIgnoreCase(WEATHER_RU u"a"_ns WEATHER_RU,
WEATHER_RU u"A"_ns WEATHER_JA),
0);
}
TEST(AboutThirdParty, MsiPackGuid)
{
nsAutoString packedGuid;
EXPECT_FALSE(
MsiPackGuid(u"EDA620E3-AA98-3846-B81E-3493CB2E0E02"_ns, packedGuid));
EXPECT_FALSE(
MsiPackGuid(u"*EDA620E3-AA98-3846-B81E-3493CB2E0E02*"_ns, packedGuid));
EXPECT_TRUE(
MsiPackGuid(u"{EDA620E3-AA98-3846-B81E-3493CB2E0E02}"_ns, packedGuid));
EXPECT_STREQ(packedGuid.get(), L"3E026ADE89AA64838BE14339BCE2E020");
}
TEST(AboutThirdParty, CorrectMsiComponentPath)
{
nsAutoString testPath;
testPath = u""_ns;
EXPECT_FALSE(CorrectMsiComponentPath(testPath));
testPath = u"\\\\server\\share"_ns;
EXPECT_FALSE(CorrectMsiComponentPath(testPath));
testPath = u"hello"_ns;
EXPECT_FALSE(CorrectMsiComponentPath(testPath));
testPath = u"02:\\Software"_ns;
EXPECT_FALSE(CorrectMsiComponentPath(testPath));
testPath = u"C:\\path\\"_ns;
EXPECT_TRUE(CorrectMsiComponentPath(testPath));
EXPECT_STREQ(testPath.get(), L"C:\\path\\");
testPath = u"C?\\path\\"_ns;
EXPECT_TRUE(CorrectMsiComponentPath(testPath));
EXPECT_STREQ(testPath.get(), L"C:\\path\\");
testPath = u"C:\\?path\\"_ns;
EXPECT_TRUE(CorrectMsiComponentPath(testPath));
EXPECT_STREQ(testPath.get(), L"C:\\path\\");
testPath = u"\\?path\\"_ns;
EXPECT_FALSE(CorrectMsiComponentPath(testPath));
}
TEST(AboutThirdParty, InstallLocations)
{
const nsLiteralString kDirectoriesUnsorted[] = {
u"C:\\duplicate\\"_ns, u"C:\\duplicate\\"_ns, u"C:\\app1\\"_ns,
u"C:\\app2\\"_ns, u"C:\\app11\\"_ns, u"C:\\app12\\"_ns,
};
struct TestCase {
nsLiteralString mFile;
nsLiteralString mInstallPath;
} const kTestCases[] = {
{u"C:\\app\\sub\\file.dll"_ns, u""_ns},
{u"C:\\app1\\sub\\file.dll"_ns, u"C:\\app1\\"_ns},
{u"C:\\app11\\sub\\file.dll"_ns, u"C:\\app11\\"_ns},
{u"C:\\app12\\sub\\file.dll"_ns, u"C:\\app12\\"_ns},
{u"C:\\app13\\sub\\file.dll"_ns, u""_ns},
{u"C:\\duplicate\\sub\\file.dll"_ns, u""_ns},
};
nsTArray<InstallLocationT> locations(ArrayLength(kDirectoriesUnsorted));
for (int i = 0; i < ArrayLength(kDirectoriesUnsorted); ++i) {
locations.EmplaceBack(kDirectoriesUnsorted[i], new InstalledApplication());
}
locations.Sort([](const InstallLocationT& aA, const InstallLocationT& aB) {
return CompareIgnoreCase(aA.first(), aB.first());
});
for (const auto& testCase : kTestCases) {
auto bounds = EqualRange(locations, 0, locations.Length(),
InstallLocationComparator(testCase.mFile));
if (bounds.second() - bounds.first() != 1) {
EXPECT_TRUE(testCase.mInstallPath.IsEmpty());
continue;
}
EXPECT_EQ(locations[bounds.first()].first(), testCase.mInstallPath);
}
}

13
toolkit/components/aboutthirdparty/tests/gtest/moz.build поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
# -*- 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/.
LOCAL_INCLUDES += [
"../..",
]
UNIFIED_SOURCES += ["TestAboutThirdParty.cpp"]
FINAL_LIBRARY = "xul-gtest"

Просмотреть файл

@ -50,4 +50,16 @@ add_task(async () => {
Ci.nsIAboutThirdParty.ModuleType_Unknown,
"Looking up an invalid name succeeds and returns ModuleType_Unknown."
);
Assert.equal(
kATP.lookupApplication(""),
null,
"Looking up an empty string returns null."
);
Assert.equal(
kATP.lookupApplication("invalid path"),
null,
"Looking up an invalid path returns null."
);
});

Просмотреть файл

@ -22,6 +22,8 @@ third-party-detail-occurrences = Occurrences
.title = How many times this module was loaded.
third-party-detail-duration = Avg. Blocking time (ms)
.title = How long this module blocked the application.
third-party-detail-app = Application
third-party-detail-publisher = Publisher
third-party-th-process = Process
third-party-th-duration = Loading Duration (ms)

Просмотреть файл

@ -11,6 +11,7 @@
#define nsWindowsHelpers_h
#include <windows.h>
#include <msi.h>
#include "nsAutoRef.h"
#include "mozilla/Assertions.h"
#include "mozilla/UniquePtr.h"
@ -172,6 +173,19 @@ class nsAutoRefTraits<DEVMODEW*> {
}
};
template <>
class nsAutoRefTraits<MSIHANDLE> {
public:
typedef MSIHANDLE RawRef;
static RawRef Void() { return 0; }
static void Release(RawRef aHandle) {
if (aHandle != Void()) {
::MsiCloseHandle(aHandle);
}
}
};
// HGLOBAL is just a typedef of HANDLE which nsSimpleRef has a specialization
// of, that means having a nsAutoRefTraits specialization for HGLOBAL is
// useless. Therefore we create a wrapper class for HGLOBAL to make
@ -237,6 +251,7 @@ typedef nsAutoRef<HMODULE> nsModuleHandle;
typedef nsAutoRef<DEVMODEW*> nsAutoDevMode;
typedef nsAutoRef<nsHGLOBAL> nsAutoGlobalMem;
typedef nsAutoRef<nsHPRINTER> nsAutoPrinter;
typedef nsAutoRef<MSIHANDLE> nsAutoMsiHandle;
namespace {