diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/aboutthirdparty/tests | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/aboutthirdparty/tests')
19 files changed, 1230 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 + ] diff --git a/toolkit/components/aboutthirdparty/tests/browser/browser.toml b/toolkit/components/aboutthirdparty/tests/browser/browser.toml new file mode 100644 index 0000000000..8a2d9db429 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/browser/browser.toml @@ -0,0 +1,6 @@ +[DEFAULT] +head = "head.js" + +["browser_aboutthirdparty.js"] +support-files = ["hello.zzz"] +skip-if = ["os == 'win'"] # Bug 1776048 diff --git a/toolkit/components/aboutthirdparty/tests/browser/browser_aboutthirdparty.js b/toolkit/components/aboutthirdparty/tests/browser/browser_aboutthirdparty.js new file mode 100644 index 0000000000..0a53823a35 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/browser/browser_aboutthirdparty.js @@ -0,0 +1,317 @@ +/* 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/. */ + +// Return card containers matching a given name +function getCardsByName(aContainer, aLeafName) { + const matchedCards = []; + const allCards = aContainer.querySelectorAll(".card"); + for (const card of allCards) { + const nameLabel = card.querySelector(".module-name"); + if (nameLabel.textContent == aLeafName) { + matchedCards.push(card); + } + } + return matchedCards; +} + +function getDetailRow(aContainer, aLabel) { + return aContainer.querySelector(`[data-l10n-id=${aLabel}]`).parentElement; +} + +function verifyClipboardData(aModuleJson) { + Assert.ok( + aModuleJson.hasOwnProperty("blocked"), + "Clipboard data should have blocked property." + ); + const blocked = aModuleJson.blocked.filter( + x => x.name == kUserBlockedModuleName + ); + Assert.equal( + blocked.length, + 1, + "Blocked array should contain the blocked module one time." + ); + Assert.ok( + aModuleJson.hasOwnProperty("modules"), + "Clipboard data should have modules property" + ); + let aModuleArray = aModuleJson.modules; + const filtered = aModuleArray.filter(x => x.name == kExtensionModuleName); + Assert.equal(filtered.length, 1, "No duplicate data for the module."); + + const kDeletedPropertiesOfModule = [ + "application", + "dllFile", + "loadingOnMain", + "trustFlags", + ]; + const kDeletedPropertiesOfLoadingEvent = [ + "baseAddress", + "isDependent", + "mainThread", + "moduleIndex", + "processUptimeMS", + ]; + + for (const module of aModuleArray) { + for (const deletedProperty of kDeletedPropertiesOfModule) { + Assert.ok( + !module.hasOwnProperty(deletedProperty), + `The property \`${deletedProperty}\` is deleted.` + ); + } + + Assert.ok( + !module.hasOwnProperty("typeFlags") || module.typeFlags != 0, + "typeFlags does not exist or is non-zero." + ); + + for (const event of module.events) { + for (const deletedProperty of kDeletedPropertiesOfLoadingEvent) { + Assert.ok( + !event.hasOwnProperty(deletedProperty), + `The property \`${deletedProperty}\` is deleted.` + ); + } + } + } +} + +function verifyModuleSorting(compareFunc) { + const uninteresting = { + typeFlags: 0, + isCrasher: false, + loadingOnMain: 0, + }; + const crasherNotBlocked = { ...uninteresting, isCrasher: true }; + const crasherBlocked = { + ...uninteresting, + isCrasher: true, + typeFlags: Ci.nsIAboutThirdParty.ModuleType_BlockedByUserAtLaunch, + }; + const justBlocked = { + ...uninteresting, + typeFlags: Ci.nsIAboutThirdParty.ModuleType_BlockedByUserAtLaunch, + }; + const uninterestingButSlow = { + ...uninteresting, + loadingOnMain: 10, + }; + let modules = [ + uninteresting, + uninterestingButSlow, + crasherNotBlocked, + justBlocked, + crasherBlocked, + ]; + modules.sort(compareFunc); + Assert.equal( + JSON.stringify([ + crasherBlocked, + justBlocked, + crasherNotBlocked, + uninterestingButSlow, + uninteresting, + ]), + JSON.stringify(modules), + "Modules sort in expected order" + ); +} + +add_task(async () => { + registerCleanupFunction(() => { + unregisterAll(); + }); + await registerObject(); + registerExtensions(); + loadShellExtension(); + + await kATP.collectSystemInfo(); + Assert.equal( + kATP.lookupModuleType(kExtensionModuleName), + Ci.nsIAboutThirdParty.ModuleType_ShellExtension, + "lookupModuleType() returns a correct type " + + "after system info was collected." + ); + + await BrowserTestUtils.withNewTab("about:third-party", async browser => { + if (!content.fetchDataDone) { + const mainDiv = content.document.getElementById("main"); + await BrowserTestUtils.waitForMutationCondition( + mainDiv, + { childList: true }, + () => mainDiv.childElementCount > 0 + ); + Assert.ok(content.fetchDataDone, "onLoad() is completed."); + } + + const reload = content.document.getElementById("button-reload"); + if (!reload.hidden) { + reload.click(); + await BrowserTestUtils.waitForMutationCondition( + reload, + { attributes: true, attributeFilter: ["hidden"] }, + () => reload.hidden + ); + } + + Assert.ok( + content.document.getElementById("no-data").hidden, + "The no-data message is hidden." + ); + const blockedCards = getCardsByName( + content.document, + kUserBlockedModuleName + ); + Assert.equal( + blockedCards.length, + 1, + "Only one card matching the blocked module exists." + ); + const blockedCard = blockedCards[0]; + Assert.equal( + blockedCard.querySelectorAll(".button-block.module-blocked").length, + 1, + "The blocked module has a button indicating it is blocked" + ); + let blockedBlockButton = blockedCard.querySelector( + ".button-block.module-blocked" + ); + Assert.equal( + blockedBlockButton.getAttribute("data-l10n-id"), + "third-party-button-to-unblock-module", + "Button to block the module has correct title" + ); + blockedBlockButton.click(); + await BrowserTestUtils.promiseAlertDialogOpen("cancel"); + Assert.ok( + !blockedBlockButton.classList.contains("module-blocked"), + "After clicking to unblock a module, button should not have module-blocked class." + ); + Assert.equal( + blockedBlockButton.getAttribute("data-l10n-id"), + "third-party-button-to-block-module", + "After clicking to unblock a module, button should have correct title." + ); + // Restore this to blocked for later tests + blockedBlockButton.click(); + await BrowserTestUtils.promiseAlertDialogOpen("cancel"); + Assert.ok( + blockedBlockButton.classList.contains("module-blocked"), + "After clicking to block a module, button should have module-blocked class." + ); + Assert.equal( + blockedBlockButton.getAttribute("data-l10n-id"), + "third-party-button-to-unblock-module", + "After clicking to block a module, button should have correct title." + ); + + const cards = getCardsByName(content.document, kExtensionModuleName); + Assert.equal(cards.length, 1, "Only one card matching the module exists."); + const card = cards[0]; + + const blockButton = card.querySelector(".button-block"); + Assert.ok( + !blockButton.classList.contains("blocklist-disabled"), + "Button to block the module does not indicate the blocklist is disabled." + ); + Assert.ok( + !blockButton.classList.contains("module-blocked"), + "Button to block the module does not indicate the module is blocked." + ); + + Assert.ok( + card.querySelector(".image-warning").hidden, + "No warning sign for the module." + ); + Assert.equal( + card.querySelector(".image-unsigned").hidden, + false, + "The module is labeled as unsigned." + ); + Assert.equal( + card.querySelector(".tag-shellex").hidden, + false, + "The module is labeled as a shell extension." + ); + Assert.equal( + card.querySelector(".tag-ime").hidden, + true, + "The module is not labeled as an IME." + ); + + const versionRow = getDetailRow(card, "third-party-detail-version"); + Assert.equal( + versionRow.childNodes[1].textContent, + "1.2.3.4", + "The version matches a value in TestShellEx.rc." + ); + const vendorRow = getDetailRow(card, "third-party-detail-vendor"); + Assert.equal( + vendorRow.childNodes[1].textContent, + "Mozilla Corporation", + "The vendor name matches a value in TestShellEx.rc." + ); + const occurrencesRow = getDetailRow(card, "third-party-detail-occurrences"); + Assert.equal( + Number(occurrencesRow.childNodes[1].textContent), + 1, + "The module was loaded once." + ); + const durationRow = getDetailRow(card, "third-party-detail-duration"); + Assert.ok( + Number(durationRow.childNodes[1].textContent), + "The duration row shows a valid number." + ); + + const eventTable = card.querySelector(".event-table"); + const tableCells = eventTable.querySelectorAll("td"); + Assert.equal( + tableCells.length, + 3, + "The table has three cells as there is only one event." + ); + Assert.equal( + tableCells[0].querySelector(".process-type").getAttribute("data-l10n-id"), + "process-type-default", + "The module was loaded into the main process." + ); + Assert.ok( + Number(tableCells[0].querySelector(".process-id").textContent), + "A valid process ID is displayed." + ); + Assert.equal( + tableCells[1].querySelector(".event-duration").textContent, + durationRow.childNodes[1].textContent, + "The event's duration is the same as the average " + + "as there is only one event." + ); + Assert.equal( + tableCells[1].querySelector(".tag-background").hidden, + true, + "The icon handler is loaded in the main thread." + ); + Assert.equal( + tableCells[2].getAttribute("data-l10n-id"), + "third-party-status-loaded", + "The module was really loaded without being blocked." + ); + + const button = content.document.getElementById("button-copy-to-clipboard"); + button.click(); + + // Wait until copying is done and the button becomes clickable. + await BrowserTestUtils.waitForMutationCondition( + button, + { attributes: true }, + () => !button.disabled + ); + + const copiedJSON = JSON.parse(await navigator.clipboard.readText()); + Assert.ok(copiedJSON instanceof Object, "Data is an object."); + verifyClipboardData(copiedJSON); + + verifyModuleSorting(content.moduleCompareForDisplay); + }); +}); diff --git a/toolkit/components/aboutthirdparty/tests/browser/head.js b/toolkit/components/aboutthirdparty/tests/browser/head.js new file mode 100644 index 0000000000..eba3948046 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/browser/head.js @@ -0,0 +1,144 @@ +/* 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/. */ + +"use strict"; + +const kClsidTestShellEx = "{10a9521e-0205-4cc7-93a1-62f30a9a54b3}"; +const kFriendlyName = "Minimum Shell Extension for Firefox testing"; +const kExtensionSubkeys = [".zzz\\shellex\\IconHandler"]; +const kExtensionModuleName = "TestShellEx.dll"; +const kUserBlockedModuleName = "TestDllBlocklist_UserBlocked.dll"; +const kFileFilterInDialog = "*.zzz"; +const kATP = Cc["@mozilla.org/about-thirdparty;1"].getService( + Ci.nsIAboutThirdParty +); + +function loadShellExtension() { + // This method call opens the file dialog and shows the support file + // "hello.zzz" in it, which loads TestShellEx.dll to show an icon + // for files with the .zzz extension. + kATP.openAndCloseFileDialogForTesting( + kExtensionModuleName, + getTestFilePath(""), + kFileFilterInDialog + ); +} + +async function registerObject() { + const reg = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + + reg.create( + Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "Software\\Classes\\CLSID\\" + kClsidTestShellEx, + Ci.nsIWindowsRegKey.ACCESS_ALL + ); + + reg.writeStringValue("", kFriendlyName); + + const inprocServer = reg.createChild( + "InprocServer32", + Ci.nsIWindowsRegKey.ACCESS_ALL + ); + + const moduleFullPath = getTestFilePath(kExtensionModuleName); + Assert.ok(await IOUtils.exists(moduleFullPath), "The module file exists."); + + inprocServer.writeStringValue("", moduleFullPath); + inprocServer.writeStringValue("ThreadingModel", "Apartment"); + reg.close(); + + info("registerObject() done - " + moduleFullPath); +} + +function registerExtensions() { + for (const subkey of kExtensionSubkeys) { + const reg = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + reg.create( + Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "Software\\Classes\\" + subkey, + Ci.nsIWindowsRegKey.ACCESS_ALL + ); + + let currentExtension = ""; + try { + // If the key was just created above, the default value does not exist, + // so readStringValue will throw NS_ERROR_FAILURE. + currentExtension = reg.readStringValue(""); + } catch (e) {} + + try { + if (!currentExtension) { + reg.writeStringValue("", kClsidTestShellEx); + } else if (currentExtension != kClsidTestShellEx) { + throw new Error( + `Another extension \`${currentExtension}\` has been registered.` + ); + } + } catch (e) { + throw new Error("Failed to register TestShellEx.dll: " + e); + } finally { + reg.close(); + } + } +} + +function unregisterAll() { + for (const subkey of kExtensionSubkeys) { + const reg = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + + try { + reg.open( + Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "Software\\Classes\\" + subkey, + Ci.nsIWindowsRegKey.ACCESS_ALL + ); + + if (reg.readStringValue("") != kClsidTestShellEx) { + // If another extension is registered, don't overwrite it. + continue; + } + + // Set an empty string instead of deleting the key + // not to touch non-default values. + reg.writeStringValue("", ""); + } catch (e) { + info(`Failed to unregister \`${subkey}\`: ` + e); + } finally { + reg.close(); + } + } + + const reg = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + reg.open( + Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + "Software\\Classes\\CLSID", + Ci.nsIWindowsRegKey.ACCESS_ALL + ); + + try { + const child = reg.openChild( + kClsidTestShellEx, + Ci.nsIWindowsRegKey.ACCESS_ALL + ); + try { + child.removeChild("InprocServer32"); + } catch (e) { + } finally { + child.close(); + } + + reg.removeChild(kClsidTestShellEx); + } catch (e) { + } finally { + reg.close(); + } +} diff --git a/toolkit/components/aboutthirdparty/tests/browser/hello.zzz b/toolkit/components/aboutthirdparty/tests/browser/hello.zzz new file mode 100644 index 0000000000..e69ab604b3 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/browser/hello.zzz @@ -0,0 +1 @@ +Showing this file on Windows Shell loads TestShellEx.dll.
diff --git a/toolkit/components/aboutthirdparty/tests/gtest/TestAboutThirdParty.cpp b/toolkit/components/aboutthirdparty/tests/gtest/TestAboutThirdParty.cpp new file mode 100644 index 0000000000..565e87e0f1 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/gtest/TestAboutThirdParty.cpp @@ -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 (size_t 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); + } +} diff --git a/toolkit/components/aboutthirdparty/tests/gtest/moz.build b/toolkit/components/aboutthirdparty/tests/gtest/moz.build new file mode 100644 index 0000000000..659c0836db --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/gtest/moz.build @@ -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" diff --git a/toolkit/components/aboutthirdparty/tests/xpcshell/head.js b/toolkit/components/aboutthirdparty/tests/xpcshell/head.js new file mode 100644 index 0000000000..ede1d3cc73 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/xpcshell/head.js @@ -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/. */ + +"use strict"; + +const kExtensionModuleName = "TestShellEx.dll"; +const kATP = Cc["@mozilla.org/about-thirdparty;1"].getService( + Ci.nsIAboutThirdParty +); diff --git a/toolkit/components/aboutthirdparty/tests/xpcshell/test_aboutthirdparty.js b/toolkit/components/aboutthirdparty/tests/xpcshell/test_aboutthirdparty.js new file mode 100644 index 0000000000..281fe42188 --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/xpcshell/test_aboutthirdparty.js @@ -0,0 +1,65 @@ +/* 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/. */ + +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; + const promises = []; + for (let i = 0; i < kLoopCount; ++i) { + promises.push(kATP.collectSystemInfo()); + } + + const collectSystemInfoResults = await Promise.allSettled(promises); + Assert.equal(collectSystemInfoResults.length, kLoopCount); + + for (const result of collectSystemInfoResults) { + Assert.ok( + result.status == "fulfilled", + "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." + ); + + 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." + ); +}); diff --git a/toolkit/components/aboutthirdparty/tests/xpcshell/xpcshell.toml b/toolkit/components/aboutthirdparty/tests/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..46a2fbc2ff --- /dev/null +++ b/toolkit/components/aboutthirdparty/tests/xpcshell/xpcshell.toml @@ -0,0 +1,4 @@ +[DEFAULT] +head = "head.js" + +["test_aboutthirdparty.js"] |