diff options
Diffstat (limited to 'toolkit/components/aboutthirdparty/tests/TestShellEx')
10 files changed, 545 insertions, 0 deletions
diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/Factory.cpp b/toolkit/components/aboutthirdparty/tests/TestShellEx/Factory.cpp new file mode 100644 index 0000000000..a341c27b73 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/Factory.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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 "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" + +#include <windows.h> +#include <shlobj.h> + +already_AddRefed<IExtractIconW> CreateIconExtension(); + +class ClassFactory final : public IClassFactory { + mozilla::Atomic<uint32_t> mRefCnt; + + ~ClassFactory() = default; + + public: + ClassFactory() : mRefCnt(0) {} + + // IUnknown + + STDMETHODIMP QueryInterface(REFIID aRefIID, void** aResult) { + if (!aResult) { + return E_INVALIDARG; + } + + if (aRefIID == IID_IClassFactory) { + RefPtr ref(static_cast<IClassFactory*>(this)); + ref.forget(aResult); + return S_OK; + } + + return E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef() { return ++mRefCnt; } + + STDMETHODIMP_(ULONG) Release() { + ULONG result = --mRefCnt; + if (!result) { + delete this; + } + return result; + } + + // IClassFactory + + STDMETHODIMP CreateInstance(IUnknown* aOuter, REFIID aRefIID, + void** aResult) { + if (aOuter) { + return CLASS_E_NOAGGREGATION; + } + + RefPtr<IUnknown> instance; + if (IsEqualCLSID(aRefIID, IID_IExtractIconA) || + IsEqualCLSID(aRefIID, IID_IExtractIconW)) { + instance = CreateIconExtension(); + } else { + return E_NOINTERFACE; + } + + return instance ? instance->QueryInterface(aRefIID, aResult) + : E_OUTOFMEMORY; + } + + STDMETHODIMP LockServer(BOOL) { return S_OK; } +}; + +already_AddRefed<IClassFactory> CreateFactory() { + return mozilla::MakeAndAddRef<ClassFactory>(); +} diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/Icon.cpp b/toolkit/components/aboutthirdparty/tests/TestShellEx/Icon.cpp new file mode 100644 index 0000000000..116c0e03a1 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/Icon.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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 "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" +#include "Resource.h" + +#include <windows.h> +#include <shlobj.h> + +#include <string> + +extern std::wstring gDllPath; +extern GUID CLSID_TestShellEx; + +class IconExtension final : public IPersistFile, + public IExtractIconA, + public IExtractIconW { + mozilla::Atomic<uint32_t> mRefCnt; + + ~IconExtension() = default; + + public: + IconExtension() : mRefCnt(0) {} + + // IUnknown + + STDMETHODIMP QueryInterface(REFIID aRefIID, void** aResult) { + if (!aResult) { + return E_INVALIDARG; + } + + if (aRefIID == IID_IPersist) { + RefPtr ref(static_cast<IPersist*>(this)); + ref.forget(aResult); + return S_OK; + } else if (aRefIID == IID_IPersistFile) { + RefPtr ref(static_cast<IPersistFile*>(this)); + ref.forget(aResult); + return S_OK; + } else if (aRefIID == IID_IExtractIconA) { + RefPtr ref(static_cast<IExtractIconA*>(this)); + ref.forget(aResult); + return S_OK; + } else if (aRefIID == IID_IExtractIconW) { + RefPtr ref(static_cast<IExtractIconW*>(this)); + ref.forget(aResult); + return S_OK; + } + + return E_NOINTERFACE; + } + + STDMETHODIMP_(ULONG) AddRef() { return ++mRefCnt; } + + STDMETHODIMP_(ULONG) Release() { + ULONG result = --mRefCnt; + if (!result) { + delete this; + } + return result; + } + + // IPersist + + STDMETHODIMP GetClassID(CLSID* aClassID) { + *aClassID = CLSID_TestShellEx; + return S_OK; + } + + // IPersistFile + + STDMETHODIMP GetCurFile(LPOLESTR*) { return E_NOTIMPL; } + STDMETHODIMP IsDirty() { return S_FALSE; } + STDMETHODIMP Load(LPCOLESTR, DWORD) { return S_OK; } + STDMETHODIMP Save(LPCOLESTR, BOOL) { return E_NOTIMPL; } + STDMETHODIMP SaveCompleted(LPCOLESTR) { return E_NOTIMPL; } + + // IExtractIconA + + STDMETHODIMP Extract(PCSTR, UINT, HICON*, HICON*, UINT) { return E_NOTIMPL; } + STDMETHODIMP GetIconLocation(UINT, PSTR, UINT, int*, UINT*) { + return E_NOTIMPL; + } + + // IExtractIconW + + STDMETHODIMP Extract(PCWSTR, UINT, HICON*, HICON*, UINT) { return S_FALSE; } + + STDMETHODIMP GetIconLocation(UINT, PWSTR aIconFile, UINT aCchMax, int* aIndex, + UINT* aOutFlags) { + if (aCchMax <= gDllPath.size()) { + return E_NOT_SUFFICIENT_BUFFER; + } + + gDllPath.copy(aIconFile, gDllPath.size()); + aIconFile[gDllPath.size()] = 0; + *aOutFlags = GIL_DONTCACHE; + *aIndex = -IDI_ICON1; + return S_OK; + } +}; + +already_AddRefed<IExtractIconW> CreateIconExtension() { + return mozilla::MakeAndAddRef<IconExtension>(); +} diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/RegUtils.cpp b/toolkit/components/aboutthirdparty/tests/TestShellEx/RegUtils.cpp new file mode 100644 index 0000000000..33b2feb12a --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/RegUtils.cpp @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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 "mozilla/UniquePtr.h" +#include "RegUtils.h" + +#include <windows.h> +#include <strsafe.h> + +extern std::wstring gDllPath; + +const wchar_t kClsIdPrefix[] = L"CLSID\\"; +const wchar_t* kExtensionSubkeys[] = { + L".zzz\\shellex\\IconHandler", +}; + +bool RegKey::SetStringInternal(const wchar_t* aValueName, + const wchar_t* aValueData, + DWORD aValueDataLength) { + if (!mKey) { + return false; + } + + return ::RegSetValueExW(mKey, aValueName, 0, REG_SZ, + reinterpret_cast<const BYTE*>(aValueData), + aValueDataLength) == ERROR_SUCCESS; +} + +RegKey::RegKey(HKEY root, const wchar_t* aSubkey) : mKey(nullptr) { + ::RegCreateKeyExW(root, aSubkey, 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, + &mKey, nullptr); +} + +RegKey::~RegKey() { + if (mKey) { + ::RegCloseKey(mKey); + } +} + +bool RegKey::SetString(const wchar_t* aValueName, const wchar_t* aValueData) { + return SetStringInternal( + aValueName, aValueData, + aValueData + ? static_cast<DWORD>((wcslen(aValueData) + 1) * sizeof(wchar_t)) + : 0); +} + +bool RegKey::SetString(const wchar_t* aValueName, + const std::wstring& aValueData) { + return SetStringInternal( + aValueName, aValueData.c_str(), + static_cast<DWORD>((aValueData.size() + 1) * sizeof(wchar_t))); +} + +std::wstring RegKey::GetString(const wchar_t* aValueName) { + DWORD len = 0; + LSTATUS status = ::RegGetValueW(mKey, aValueName, nullptr, RRF_RT_REG_SZ, + nullptr, nullptr, &len); + + mozilla::UniquePtr<uint8_t[]> buf = mozilla::MakeUnique<uint8_t[]>(len); + status = ::RegGetValueW(mKey, aValueName, nullptr, RRF_RT_REG_SZ, nullptr, + buf.get(), &len); + if (status != ERROR_SUCCESS) { + return L""; + } + + return reinterpret_cast<wchar_t*>(buf.get()); +} + +ComRegisterer::ComRegisterer(const GUID& aClsId, const wchar_t* aFriendlyName) + : mClassRoot(HKEY_CURRENT_USER, L"Software\\Classes"), + mFriendlyName(aFriendlyName) { + wchar_t guidStr[64]; + HRESULT hr = ::StringCbPrintfW( + guidStr, sizeof(guidStr), + L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", aClsId.Data1, + aClsId.Data2, aClsId.Data3, aClsId.Data4[0], aClsId.Data4[1], + aClsId.Data4[2], aClsId.Data4[3], aClsId.Data4[4], aClsId.Data4[5], + aClsId.Data4[6], aClsId.Data4[7]); + if (FAILED(hr)) { + return; + } + + mClsId = guidStr; +} + +bool ComRegisterer::UnregisterAll() { + bool isOk = true; + LSTATUS ls; + + for (const wchar_t* subkey : kExtensionSubkeys) { + RegKey root(mClassRoot, subkey); + + std::wstring currentHandler = root.GetString(nullptr); + if (currentHandler != mClsId) { + // If another extension is registered, don't overwrite it. + continue; + } + + // Set an empty string instead of deleting the key. + if (!root.SetString(nullptr)) { + isOk = false; + } + } + + std::wstring subkey(kClsIdPrefix); + subkey += mClsId; + ls = ::RegDeleteTreeW(mClassRoot, subkey.c_str()); + if (ls != ERROR_SUCCESS && ls != ERROR_FILE_NOT_FOUND) { + isOk = false; + } + + return isOk; +} + +bool ComRegisterer::RegisterObject(const wchar_t* aThreadModel) { + std::wstring subkey(kClsIdPrefix); + subkey += mClsId; + + RegKey root(mClassRoot, subkey.c_str()); + if (!root || !root.SetString(nullptr, mFriendlyName)) { + return false; + } + + RegKey inproc(root, L"InprocServer32"); + return inproc && inproc.SetString(nullptr, gDllPath) && + inproc.SetString(L"ThreadingModel", aThreadModel); +} + +bool ComRegisterer::RegisterExtensions() { + for (const wchar_t* subkey : kExtensionSubkeys) { + RegKey root(mClassRoot, subkey); + std::wstring currentHandler = root.GetString(nullptr); + if (!currentHandler.empty()) { + // If another extension is registered, don't overwrite it. + continue; + } + + if (!root.SetString(nullptr, mClsId)) { + return false; + } + } + + return true; +} diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/RegUtils.h b/toolkit/components/aboutthirdparty/tests/TestShellEx/RegUtils.h new file mode 100644 index 0000000000..21a23fdf16 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/RegUtils.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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_TestShellEx_RegUtils_h +#define mozilla_TestShellEx_RegUtils_h + +#include <windows.h> +#include <string> + +class RegKey final { + HKEY mKey; + + bool SetStringInternal(const wchar_t* aValueName, const wchar_t* aValueData, + DWORD aValueDataLength); + + public: + RegKey() : mKey(nullptr) {} + RegKey(HKEY root, const wchar_t* aSubkey); + ~RegKey(); + + RegKey(RegKey&& aOther) = delete; + RegKey& operator=(RegKey&& aOther) = delete; + RegKey(const RegKey&) = delete; + RegKey& operator=(const RegKey&) = delete; + + explicit operator bool() const { return !!mKey; } + operator HKEY() const { return mKey; } + + bool SetString(const wchar_t* aValueName, + const wchar_t* aValueData = nullptr); + bool SetString(const wchar_t* aValueName, const std::wstring& aValueData); + std::wstring GetString(const wchar_t* aValueName); +}; + +class ComRegisterer final { + RegKey mClassRoot; + std::wstring mClsId; + std::wstring mFriendlyName; + + public: + ComRegisterer(const GUID& aClsId, const wchar_t* aFriendlyName); + ~ComRegisterer() = default; + + bool UnregisterAll(); + bool RegisterObject(const wchar_t* aThreadModel); + bool RegisterExtensions(); +}; + +#endif // mozilla_TestShellEx_RegUtils_h diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/Resource.h b/toolkit/components/aboutthirdparty/tests/TestShellEx/Resource.h new file mode 100644 index 0000000000..fa037194d7 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/Resource.h @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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_TestShellEx_Resource_h +#define mozilla_TestShellEx_Resource_h + +#define IDI_ICON1 100 + +#endif // mozilla_TestShellEx_Resource_h diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.cpp b/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.cpp new file mode 100644 index 0000000000..dc0588483f --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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 "mozilla/RefPtr.h" +#include "RegUtils.h" + +#include <windows.h> +#include <objbase.h> + +already_AddRefed<IClassFactory> CreateFactory(); + +// {10A9521E-0205-4CC7-93A1-62F30A9A54B3} +GUID CLSID_TestShellEx = { + 0x10a9521e, 0x205, 0x4cc7, {0x93, 0xa1, 0x62, 0xf3, 0xa, 0x9a, 0x54, 0xb3}}; +wchar_t kFriendlyName[] = L"Minimum Shell Extension for Firefox testing"; + +std::wstring gDllPath; + +BOOL APIENTRY DllMain(HMODULE aModule, DWORD aReason, LPVOID) { + wchar_t buf[MAX_PATH]; + switch (aReason) { + case DLL_PROCESS_ATTACH: + if (!::GetModuleFileNameW(aModule, buf, ARRAYSIZE(buf))) { + return FALSE; + } + gDllPath = buf; + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +STDAPI DllGetClassObject(REFCLSID aClsid, REFIID aIid, void** aResult) { + if (!IsEqualCLSID(aClsid, CLSID_TestShellEx) || + !IsEqualCLSID(aIid, IID_IClassFactory)) { + return CLASS_E_CLASSNOTAVAILABLE; + } + + RefPtr<IClassFactory> factory = CreateFactory(); + return factory ? factory->QueryInterface(aIid, aResult) : E_OUTOFMEMORY; +} + +STDAPI DllCanUnloadNow() { return S_OK; } + +// These functions enable us to run "regsvr32 [/u] TestShellEx.dll" manually. +// (No admin privilege is needed because all access is under HKCU.) +// We don't use these functions in the mochitest, but having these makes easier +// to test the module manually. +STDAPI DllRegisterServer() { + ComRegisterer reg(CLSID_TestShellEx, kFriendlyName); + if (!reg.RegisterObject(L"Apartment") || !reg.RegisterExtensions()) { + return E_ACCESSDENIED; + } + return S_OK; +} +STDAPI DllUnregisterServer() { + ComRegisterer reg(CLSID_TestShellEx, kFriendlyName); + if (!reg.UnregisterAll()) { + return E_ACCESSDENIED; + } + return S_OK; +} diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.def b/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.def new file mode 100644 index 0000000000..e83041771d --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.def @@ -0,0 +1,10 @@ +;+# 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/.
+
+LIBRARY TestShellEx
+EXPORTS
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.rc b/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.rc new file mode 100644 index 0000000000..7500551f02 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/TestShellEx.rc @@ -0,0 +1,39 @@ +/* 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 <winres.h>
+
+#include "Resource.h"
+
+IDI_ICON1 ICON DISCARDABLE "dinosaur.ico"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,2,3,4 // This field will be collected
+ PRODUCTVERSION 5,6,7,8
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "Mozilla Corporation"
+ VALUE "OriginalFilename", "TestShellEx.dll"
+ VALUE "ProductName", "Sample Shell Extension"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/dinosaur.ico b/toolkit/components/aboutthirdparty/tests/TestShellEx/dinosaur.ico Binary files differnew file mode 100644 index 0000000000..d44438903b --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/dinosaur.ico diff --git a/toolkit/components/aboutthirdparty/tests/TestShellEx/moz.build b/toolkit/components/aboutthirdparty/tests/TestShellEx/moz.build new file mode 100644 index 0000000000..44344a0e2e --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/TestShellEx/moz.build @@ -0,0 +1,33 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +DIST_INSTALL = False + +SharedLibrary("TestShellEx") + +UNIFIED_SOURCES = [ + "Factory.cpp", + "Icon.cpp", + "RegUtils.cpp", + "TestShellEx.cpp", +] + +RCFILE = "TestShellEx.rc" +DEFFILE = "TestShellEx.def" +USE_LIBS += [ + "mozglue", +] + +if CONFIG["OS_ARCH"] == "WINNT": + OS_LIBS += [ + "advapi32", + "uuid", + ] + +if CONFIG["COMPILE_ENVIRONMENT"]: + shared_library = "!%sTestShellEx%s" % (CONFIG["DLL_PREFIX"], CONFIG["DLL_SUFFIX"]) + TEST_HARNESS_FILES.testing.mochitest.browser.toolkit.components.aboutthirdparty.tests.browser += [ + shared_library + ] |