Bug 1695817 - Part 4: Label a module as ShellExtension or IME. r=Gijs,mhowell

This patch adds a feature to mark each module in the about:third-party page
as a shell extension or an IME if it is so.  To achieve this, when the page
is loaded, it starts a background task to collect registered shell extensions
from the registry and adds a tag next to a module's name.

Differential Revision: https://phabricator.services.mozilla.com/D109305
This commit is contained in:
Toshihito Kikuchi 2021-05-28 22:35:58 +00:00
Родитель 62021812c8
Коммит cb2d12abe0
9 изменённых файлов: 486 добавлений и 1 удалений

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

@ -8,9 +8,311 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/NativeNt.h"
#include "mozilla/StaticPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsIWindowsRegKey.h"
#include "nsThreadUtils.h"
#include <objbase.h>
using namespace mozilla;
namespace {
// A callback function passed to EnumSubkeys uses this type
// to control the enumeration loop.
enum class CallbackResult { Continue, Stop };
template <typename CallbackT>
void EnumSubkeys(nsIWindowsRegKey* aRegBase, const CallbackT& aCallback) {
uint32_t count = 0;
if (NS_FAILED(aRegBase->GetChildCount(&count))) {
return;
}
for (uint32_t i = 0; i < count; ++i) {
nsAutoString subkeyName;
if (NS_FAILED(aRegBase->GetChildName(i, subkeyName))) {
continue;
}
nsCOMPtr<nsIWindowsRegKey> subkey;
if (NS_FAILED(aRegBase->OpenChild(subkeyName, nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(subkey)))) {
continue;
}
CallbackResult result = aCallback(subkeyName, subkey);
if (result == CallbackResult::Continue) {
continue;
} else if (result == CallbackResult::Stop) {
break;
} else {
MOZ_ASSERT_UNREACHABLE("Unexpected CallbackResult.");
}
}
}
} // anonymous namespace
class KnownModule final {
static KnownModule sKnownExtensions[static_cast<int>(KnownModuleType::Last)];
static bool GetInprocServerDllPathFromGuid(const GUID& aGuid,
nsAString& aResult) {
nsAutoStringN<60> subkey;
subkey.AppendPrintf(
"CLSID\\{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\\"
"InProcServer32",
aGuid.Data1, aGuid.Data2, aGuid.Data3, aGuid.Data4[0], aGuid.Data4[1],
aGuid.Data4[2], aGuid.Data4[3], aGuid.Data4[4], aGuid.Data4[5],
aGuid.Data4[6], aGuid.Data4[7]);
nsresult rv;
nsCOMPtr<nsIWindowsRegKey> regKey =
do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
if (NS_FAILED(rv)) {
return false;
}
rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, subkey,
nsIWindowsRegKey::ACCESS_READ);
if (NS_FAILED(rv)) {
return false;
}
rv = regKey->ReadStringValue(u""_ns, aResult);
return NS_SUCCEEDED(rv);
}
enum class HandlerType {
// For this type of handler, multiple extensions can be registered as
// subkeys under the handler subkey.
Multi,
// For this type of handler, a single extension can be registered as
// the default value of the handler subkey.
Single,
};
HandlerType mHandlerType;
nsLiteralString mSubkeyName;
using CallbackT = std::function<void(const nsString&, KnownModuleType)>;
void EnumInternal(nsIWindowsRegKey* aRegBase, KnownModuleType aType,
const CallbackT& aCallback) const {
nsCOMPtr<nsIWindowsRegKey> shexType;
if (NS_FAILED(aRegBase->OpenChild(mSubkeyName,
nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(shexType)))) {
return;
}
switch (mHandlerType) {
case HandlerType::Single: {
nsAutoString valData;
GUID guid;
if (NS_FAILED(shexType->ReadStringValue(u""_ns, valData)) ||
FAILED(::CLSIDFromString(valData.get(), &guid))) {
return;
}
nsAutoString dllPath;
if (!GetInprocServerDllPathFromGuid(guid, dllPath)) {
return;
}
aCallback(dllPath, aType);
break;
}
case HandlerType::Multi:
EnumSubkeys(shexType, [aType, &aCallback](const nsString& aSubKeyName,
nsIWindowsRegKey* aSubKey) {
GUID guid;
HRESULT hr = ::CLSIDFromString(aSubKeyName.get(), &guid);
if (hr == CO_E_CLASSSTRING) {
// If the key's name is not a GUID, the default value of the key
// may be a GUID.
nsAutoString valData;
if (NS_SUCCEEDED(aSubKey->ReadStringValue(u""_ns, valData))) {
hr = ::CLSIDFromString(valData.get(), &guid);
}
}
if (FAILED(hr)) {
return CallbackResult::Continue;
}
nsAutoString dllPath;
if (!GetInprocServerDllPathFromGuid(guid, dllPath)) {
return CallbackResult::Continue;
}
aCallback(dllPath, aType);
return CallbackResult::Continue;
});
break;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected KnownModule::Type.");
break;
}
}
static void Enum(nsIWindowsRegKey* aRegBase, KnownModuleType aType,
const CallbackT& aCallback) {
sKnownExtensions[static_cast<int>(aType)].EnumInternal(aRegBase, aType,
aCallback);
}
KnownModule(HandlerType aHandlerType, nsLiteralString aSubkeyName)
: mHandlerType(aHandlerType), mSubkeyName(aSubkeyName) {}
public:
static void EnumAll(const CallbackT& aCallback) {
nsresult rv;
nsCOMPtr<nsIWindowsRegKey> regKey =
do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
if (NS_FAILED(rv)) {
return;
}
// Icon Overlay Handlers are registered under HKLM only.
// No need to look at HKCU.
rv = regKey->Open(
nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
u"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"_ns,
nsIWindowsRegKey::ACCESS_READ);
if (NS_SUCCEEDED(rv)) {
Enum(regKey, KnownModuleType::IconOverlay, aCallback);
}
// IMEs can be enumerated by
// ITfInputProcessorProfiles::EnumInputProcessorInfo, but enumerating
// the registry key is easier.
// The "HKLM\Software\Microsoft\CTF\TIP" subtree is shared between
// the 32-bits and 64 bits views.
// https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys
// This logic cannot detect legacy (TSF-unaware) IMEs.
rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
u"Software\\Microsoft\\CTF"_ns,
nsIWindowsRegKey::ACCESS_READ);
if (NS_SUCCEEDED(rv)) {
Enum(regKey, KnownModuleType::Ime, aCallback);
}
// Because HKCR is a merged view of HKLM\Software\Classes and
// HKCU\Software\Classes, looking at HKCR covers both per-machine
// and per-user extensions.
rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, u""_ns,
nsIWindowsRegKey::ACCESS_READ);
if (NS_FAILED(rv)) {
return;
}
EnumSubkeys(regKey, [&aCallback](const nsString& aSubKeyName,
nsIWindowsRegKey* aSubKey) {
if (aSubKeyName.EqualsIgnoreCase("DesktopBackground") ||
aSubKeyName.EqualsIgnoreCase("AudioCD")) {
return CallbackResult::Continue;
}
if (aSubKeyName.EqualsIgnoreCase("Directory")) {
nsCOMPtr<nsIWindowsRegKey> regBackground;
if (NS_SUCCEEDED(aSubKey->OpenChild(u"Background\\shellex"_ns,
nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(regBackground)))) {
Enum(regBackground, KnownModuleType::ContextMenuHandler, aCallback);
}
} else if (aSubKeyName.EqualsIgnoreCase("Network")) {
nsCOMPtr<nsIWindowsRegKey> regNetworkTypes;
if (NS_SUCCEEDED(aSubKey->OpenChild(u"Type"_ns,
nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(regNetworkTypes)))) {
EnumSubkeys(
regNetworkTypes,
[&aCallback](const nsString&, nsIWindowsRegKey* aRegNetworkType) {
nsCOMPtr<nsIWindowsRegKey> regNetworkTypeShex;
if (NS_FAILED(aRegNetworkType->OpenChild(
u"shellex"_ns, nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(regNetworkTypeShex)))) {
return CallbackResult::Continue;
}
Enum(regNetworkTypeShex, KnownModuleType::ContextMenuHandler,
aCallback);
Enum(regNetworkTypeShex, KnownModuleType::PropertySheetHandler,
aCallback);
return CallbackResult::Continue;
});
}
}
nsCOMPtr<nsIWindowsRegKey> regShex;
if (NS_FAILED(aSubKey->OpenChild(u"shellex"_ns,
nsIWindowsRegKey::ACCESS_READ,
getter_AddRefs(regShex)))) {
return CallbackResult::Continue;
}
Enum(regShex, KnownModuleType::ContextMenuHandler, aCallback);
Enum(regShex, KnownModuleType::PropertySheetHandler, aCallback);
if (aSubKeyName.EqualsIgnoreCase("AllFileSystemObjects") ||
aSubKeyName.EqualsIgnoreCase("Network") ||
aSubKeyName.EqualsIgnoreCase("NetShare") ||
aSubKeyName.EqualsIgnoreCase("NetServer") ||
aSubKeyName.EqualsIgnoreCase("DVD")) {
return CallbackResult::Continue;
}
if (aSubKeyName.EqualsIgnoreCase("Directory")) {
Enum(regShex, KnownModuleType::CopyHookHandler, aCallback);
Enum(regShex, KnownModuleType::DragDropHandler, aCallback);
return CallbackResult::Continue;
} else if (aSubKeyName.EqualsIgnoreCase("Drive")) {
Enum(regShex, KnownModuleType::DragDropHandler, aCallback);
return CallbackResult::Continue;
} else if (aSubKeyName.EqualsIgnoreCase("Folder")) {
Enum(regShex, KnownModuleType::DragDropHandler, aCallback);
return CallbackResult::Continue;
} else if (aSubKeyName.EqualsIgnoreCase("Printers")) {
Enum(regShex, KnownModuleType::CopyHookHandler, aCallback);
return CallbackResult::Continue;
}
Enum(regShex, KnownModuleType::DataHandler, aCallback);
Enum(regShex, KnownModuleType::DropHandler, aCallback);
Enum(regShex, KnownModuleType::IconHandler, aCallback);
Enum(regShex, KnownModuleType::PropertyHandler, aCallback);
Enum(regShex, KnownModuleType::InfotipHandler, aCallback);
return CallbackResult::Continue;
});
}
KnownModule() = delete;
KnownModule(KnownModule&&) = delete;
KnownModule& operator=(KnownModule&&) = delete;
KnownModule(const KnownModule&) = delete;
KnownModule& operator=(const KnownModule&) = delete;
};
KnownModule KnownModule::sKnownExtensions[] = {
{HandlerType::Multi, u"TIP"_ns},
{HandlerType::Multi, u"ShellIconOverlayIdentifiers"_ns},
{HandlerType::Multi, u"ContextMenuHandlers"_ns},
{HandlerType::Multi, u"CopyHookHandlers"_ns},
{HandlerType::Multi, u"DragDropHandlers"_ns},
{HandlerType::Multi, u"PropertySheetHandlers"_ns},
{HandlerType::Single, u"DataHandler"_ns},
{HandlerType::Single, u"DropHandler"_ns},
{HandlerType::Single, u"IconHandler"_ns},
{HandlerType::Single, u"{00021500-0000-0000-C000-000000000046}"_ns},
{HandlerType::Single, u"PropertyHandler"_ns},
};
namespace mozilla {
static StaticRefPtr<AboutThirdParty> sSingleton;
@ -30,13 +332,70 @@ already_AddRefed<AboutThirdParty> AboutThirdParty::GetSingleton() {
AboutThirdParty::AboutThirdParty()
: mPromise(new BackgroundThreadPromise::Private(__func__)) {}
void AboutThirdParty::AddKnownModule(const nsString& aPath,
KnownModuleType aType) {
MOZ_ASSERT(!NS_IsMainThread());
const uint32_t flag = 1u << static_cast<uint32_t>(aType);
mKnownModules.WithEntryHandle(nt::GetLeafName(aPath), [flag](auto&& addPtr) {
if (addPtr) {
addPtr.Data() |= flag;
} else {
addPtr.Insert(flag);
}
});
}
void AboutThirdParty::BackgroundThread() {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(mWorkerState == WorkerState::Running);
KnownModule::EnumAll(
[self = RefPtr{this}](const nsString& aDllPath, KnownModuleType aType) {
self->AddKnownModule(aDllPath, aType);
});
mWorkerState = WorkerState::Done;
}
NS_IMETHODIMP AboutThirdParty::LookupModuleType(const nsAString& aLeafName,
uint32_t* aResult) {
constexpr uint32_t kShellExtensions =
1u << static_cast<uint32_t>(KnownModuleType::IconOverlay) |
1u << static_cast<uint32_t>(KnownModuleType::ContextMenuHandler) |
1u << static_cast<uint32_t>(KnownModuleType::CopyHookHandler) |
1u << static_cast<uint32_t>(KnownModuleType::DragDropHandler) |
1u << static_cast<uint32_t>(KnownModuleType::PropertySheetHandler) |
1u << static_cast<uint32_t>(KnownModuleType::DataHandler) |
1u << static_cast<uint32_t>(KnownModuleType::DropHandler) |
1u << static_cast<uint32_t>(KnownModuleType::IconHandler) |
1u << static_cast<uint32_t>(KnownModuleType::InfotipHandler) |
1u << static_cast<uint32_t>(KnownModuleType::PropertyHandler);
MOZ_ASSERT(NS_IsMainThread());
*aResult = 0;
if (mWorkerState != WorkerState::Done) {
return NS_OK;
}
uint32_t flags;
if (!mKnownModules.Get(aLeafName, &flags)) {
*aResult = nsIAboutThirdParty::ModuleType_Unknown;
return NS_OK;
}
if (flags & (1u << static_cast<uint32_t>(KnownModuleType::Ime))) {
*aResult |= nsIAboutThirdParty::ModuleType_IME;
}
if (flags & kShellExtensions) {
*aResult |= nsIAboutThirdParty::ModuleType_ShellExtension;
}
return NS_OK;
}
RefPtr<BackgroundThreadPromise> AboutThirdParty::CollectSystemInfoAsync() {
MOZ_ASSERT(NS_IsMainThread());

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

@ -9,9 +9,26 @@
#include "mozilla/MozPromise.h"
#include "nsIAboutThirdParty.h"
#include "nsTHashMap.h"
namespace mozilla {
enum class KnownModuleType : uint32_t {
Ime = 0,
IconOverlay,
ContextMenuHandler,
CopyHookHandler,
DragDropHandler,
PropertySheetHandler,
DataHandler,
DropHandler,
IconHandler,
InfotipHandler,
PropertyHandler,
Last,
};
using BackgroundThreadPromise =
MozPromise<bool /* aIgnored */, nsresult, /* IsExclusive */ false>;
@ -24,9 +41,11 @@ class AboutThirdParty final : public nsIAboutThirdParty {
};
Atomic<WorkerState, SequentiallyConsistent> mWorkerState;
RefPtr<BackgroundThreadPromise::Private> mPromise;
nsTHashMap<nsStringCaseInsensitiveHashKey, uint32_t> mKnownModules;
~AboutThirdParty() = default;
void BackgroundThread();
void AddKnownModule(const nsString& aPath, KnownModuleType aType);
public:
static already_AddRefed<AboutThirdParty> GetSingleton();

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

@ -39,6 +39,10 @@ h1 {
margin: 0;
}
.module-tags {
margin-inline: .5em;
}
.module-tag {
font-size: 12px;
white-space: nowrap;

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

@ -22,6 +22,8 @@
<div>
<button id="button-copy-to-clipboard"
data-l10n-id="third-party-button-copy-to-clipboard"/>
<button id="button-reload" hidden
data-l10n-id="third-party-button-reload"/>
</div>
<h2 data-l10n-id="third-party-section-title"></h2>
@ -52,6 +54,12 @@
<img src="chrome://global/skin/icons/security-broken.svg"
class="svg-common image-unsigned" hidden
data-l10n-id="third-party-unsigned-icon"/>
<div class="module-tags">
<span class="module-tag tag-ime" hidden
data-l10n-id="third-party-tag-ime"></span>
<span class="module-tag tag-shellex" hidden
data-l10n-id="third-party-tag-shellex"></span>
</div>
<button class="svg-button button-open-dir"
data-l10n-id="third-party-button-open">
<img src="chrome://global/skin/icons/folder.svg"

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

@ -55,6 +55,9 @@ async function fetchData() {
for (const module of data.modules) {
module.events = [];
module.loadingOnMain = { count: 0, sum: 0 };
module.typeFlags = AboutThirdParty.lookupModuleType(
module.dllFile?.leafName
);
}
for (const [proc, perProc] of Object.entries(data.processes)) {
@ -81,6 +84,14 @@ async function fetchData() {
module.events.sort((a, b) => a.process.localeCompare(b.process));
}
data.modules.sort((a, b) => {
// First sort by |typeFlags| in ascending order to move up
// unknown-type modules first, then sort by |loadingOnMain|
// in descending order to move up slower modules.
const diff = a.typeFlags - b.typeFlags;
return diff == 0 ? b.loadingOnMain - a.loadingOnMain : diff;
});
return data.modules;
}
@ -144,6 +155,11 @@ function copyDataToClipboard(aData) {
fileVersion: module.fileVersion,
};
// We include the typeFlags field only when it's not 0 because
// typeFlags == 0 means system info is not yet collected.
if (module.typeFlags) {
copied.typeFlags = module.typeFlags;
}
if (module.signedBy) {
copied.signedBy = module.signedBy;
}
@ -191,6 +207,14 @@ function visualizeData(aData) {
.querySelector(".button-expand")
.addEventListener("click", onClickExpand);
const modTagsContainer = newCard.querySelector(".module-tags");
if (module.typeFlags & Ci.nsIAboutThirdParty.ModuleType_IME) {
modTagsContainer.querySelector(".tag-ime").hidden = false;
}
if (module.typeFlags & Ci.nsIAboutThirdParty.ModuleType_ShellExtension) {
modTagsContainer.querySelector(".tag-shellex").hidden = false;
}
const btnOpenDir = newCard.querySelector(".button-open-dir");
btnOpenDir.fileObj = module.dllFile;
btnOpenDir.addEventListener("click", onClickOpenDir);
@ -265,8 +289,27 @@ async function onLoad() {
e.target.disabled = false;
});
let hasData = false;
AboutThirdParty.collectSystemInfo()
.then(() => {
if (!hasData) {
// If collectSystemInfo was completed before fetchData,
// or there was no data available, visualizeData shows
// full info and the reload button is not needed.
return;
}
const button = document.getElementById("button-reload");
button.addEventListener("click", () => {
location.reload();
});
button.hidden = false;
})
.catch(Cu.reportError);
const data = await fetchData();
if (!data?.length) {
hasData = data?.length;
if (!hasData) {
document.getElementById("no-data").hidden = false;
return;
}

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

@ -8,6 +8,19 @@
[scriptable, uuid(d33ff086-b328-4ae6-aaf5-52d41aa5df38)]
interface nsIAboutThirdParty : nsISupports
{
/**
* ModuleType flags used by lookupModuleType.
*/
const unsigned long ModuleType_Unknown = 1 << 0;
const unsigned long ModuleType_IME = 1 << 1;
const unsigned long ModuleType_ShellExtension = 1 << 2;
/**
* Returns a bitwise combination of the ModuleType_* flags
* for the given leaf name of a module.
*/
unsigned long lookupModuleType(in AString aLeafName);
/**
* Posts a background task to collect system information and resolves
* the returned promise when the task is finished.

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

@ -4,6 +4,7 @@
"use strict";
const kExtensionModuleName = "TestShellEx.dll";
const kATP = Cc["@mozilla.org/about-thirdparty;1"].getService(
Ci.nsIAboutThirdParty
);

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

@ -3,6 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
add_task(async () => {
Assert.equal(
kATP.lookupModuleType(kExtensionModuleName),
0,
"lookupModuleType() returns 0 before system info is collected."
);
// Make sure successive calls of collectSystemInfo() do not
// cause anything bad.
const kLoopCount = 100;
@ -20,4 +26,28 @@ add_task(async () => {
"All results from collectSystemInfo() are resolved."
);
}
Assert.equal(
kATP.lookupModuleType("SHELL32.dll"),
Ci.nsIAboutThirdParty.ModuleType_ShellExtension,
"Shell32.dll is always registered as a shell extension."
);
Assert.equal(
kATP.lookupModuleType(""),
Ci.nsIAboutThirdParty.ModuleType_Unknown,
"Looking up an empty string succeeds and returns ModuleType_Unknown."
);
Assert.equal(
kATP.lookupModuleType(null),
Ci.nsIAboutThirdParty.ModuleType_Unknown,
"Looking up null succeeds and returns ModuleType_Unknown."
);
Assert.equal(
kATP.lookupModuleType("invalid name"),
Ci.nsIAboutThirdParty.ModuleType_Unknown,
"Looking up an invalid name succeeds and returns ModuleType_Unknown."
);
});

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

@ -27,6 +27,12 @@ third-party-th-process = Process
third-party-th-duration = Loading Duration (ms)
third-party-th-status = Status
third-party-tag-ime = IME
.title =
This type of module is loaded when you use a third-party IME.
third-party-tag-shellex = Shell Extension
.title =
This type of module is loaded when you open the system file dialog.
third-party-tag-background = Background
.title =
This module did not block the application because it was loaded
@ -39,6 +45,8 @@ third-party-status-blocked = Blocked
third-party-status-redirected = Redirected
third-party-button-copy-to-clipboard = Copy raw data to clipboard
third-party-button-reload = Reload with system info
.title = Reload with system information
third-party-button-open =
.title = Open file location…
third-party-button-expand =