diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpcom/components | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/components')
29 files changed, 7015 insertions, 0 deletions
diff --git a/xpcom/components/GenericFactory.cpp b/xpcom/components/GenericFactory.cpp new file mode 100644 index 0000000000..5eb88e8ba4 --- /dev/null +++ b/xpcom/components/GenericFactory.cpp @@ -0,0 +1,18 @@ +/* -*- 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 "mozilla/GenericFactory.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericFactory, nsIFactory) + +NS_IMETHODIMP +GenericFactory::CreateInstance(REFNSIID aIID, void** aResult) { + return mCtor(aIID, aResult); +} + +} // namespace mozilla diff --git a/xpcom/components/GenericFactory.h b/xpcom/components/GenericFactory.h new file mode 100644 index 0000000000..3fb2186e12 --- /dev/null +++ b/xpcom/components/GenericFactory.h @@ -0,0 +1,38 @@ +/* -*- 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 mozilla_GenericFactory_h +#define mozilla_GenericFactory_h + +#include "nsIFactory.h" + +namespace mozilla { + +/** + * A generic factory which uses a constructor function to create instances. + * This class is intended for use by the component manager and the generic + * module. + */ +class GenericFactory final : public nsIFactory { + ~GenericFactory() = default; + + public: + typedef nsresult (*ConstructorProcPtr)(const nsIID& aIID, void** aResult); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit GenericFactory(ConstructorProcPtr aCtor) : mCtor(aCtor) { + NS_ASSERTION(mCtor, "GenericFactory with no constructor"); + } + + private: + ConstructorProcPtr mCtor; +}; + +} // namespace mozilla + +#endif // mozilla_GenericFactory_h diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp new file mode 100644 index 0000000000..88ee06d78d --- /dev/null +++ b/xpcom/components/ManifestParser.cpp @@ -0,0 +1,678 @@ +/* -*- 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 "mozilla/ArrayUtils.h" +#include "mozilla/Printf.h" +#include "mozilla/UniquePtr.h" + +#include "ManifestParser.h" + +#include <string.h> + +#include "prio.h" +#if defined(XP_WIN) +# include <windows.h> +#elif defined(MOZ_WIDGET_COCOA) +# include <CoreServices/CoreServices.h> +# include "nsCocoaFeatures.h" +#elif defined(MOZ_WIDGET_GTK) +# include <gtk/gtk.h> +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBuild.h" +# include "mozilla/java/GeckoAppShellWrappers.h" +#endif + +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif + +#include "mozilla/Services.h" + +#include "nsCRT.h" +#include "nsConsoleMessage.h" +#include "nsTextFormatter.h" +#include "nsVersionComparator.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsIXULAppInfo.h" +#include "nsIXULRuntime.h" + +using namespace mozilla; + +struct ManifestDirective { + const char* directive; + int argc; + + bool ischrome; + + // The contentaccessible flags only apply to content/resource directives. + bool contentflags; + + // Function to handle this directive. This isn't a union because C++ still + // hasn't learned how to initialize unions in a sane way. + void (nsComponentManagerImpl::*mgrfunc)( + nsComponentManagerImpl::ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void (nsChromeRegistry::*regfunc)( + nsChromeRegistry::ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv, int aFlags); +}; +static const ManifestDirective kParsingTable[] = { + // clang-format off + { + "manifest", 1, true, false, + &nsComponentManagerImpl::ManifestManifest, nullptr, + }, + { + "category", 3, false, false, + &nsComponentManagerImpl::ManifestCategory, nullptr, + }, + { + "content", 2, true, true, + nullptr, &nsChromeRegistry::ManifestContent, + }, + { + "locale", 3, true, false, + nullptr, &nsChromeRegistry::ManifestLocale, + }, + { + "skin", 3, true, false, + nullptr, &nsChromeRegistry::ManifestSkin, + }, + { + // NB: note that while skin manifests can use this, they are only allowed + // to use it for chrome://../skin/ URLs + "override", 2, true, false, + nullptr, &nsChromeRegistry::ManifestOverride, + }, + { + "resource", 2, false, true, + nullptr, &nsChromeRegistry::ManifestResource, + } + // clang-format on +}; + +static const char kWhitespace[] = "\t "; + +static bool IsNewline(char aChar) { return aChar == '\n' || aChar == '\r'; } + +void LogMessage(const char* aMsg, ...) { + MOZ_ASSERT(nsComponentManagerImpl::gComponentManager); + + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + va_list args; + va_start(args, aMsg); + SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args)); + va_end(args); + + nsCOMPtr<nsIConsoleMessage> error = + new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted.get())); + console->LogMessage(error); +} + +void LogMessageWithContext(FileLocation& aFile, uint32_t aLineNumber, + const char* aMsg, ...) { + va_list args; + va_start(args, aMsg); + SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args)); + va_end(args); + if (!formatted) { + return; + } + + MOZ_ASSERT(nsComponentManagerImpl::gComponentManager); + + nsCString file; + aFile.GetURIString(file); + + nsCOMPtr<nsIScriptError> error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (!error) { + // This can happen early in component registration. Fall back to a + // generic console message. + LogMessage("Warning: in '%s', line %i: %s", file.get(), aLineNumber, + formatted.get()); + return; + } + + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + nsresult rv = error->Init( + NS_ConvertUTF8toUTF16(formatted.get()), NS_ConvertUTF8toUTF16(file), + u""_ns, aLineNumber, 0, nsIScriptError::warningFlag, + "chrome registration"_ns, false /* from private window */, + true /* from chrome context */); + if (NS_FAILED(rv)) { + return; + } + + console->LogMessage(error); +} + +/** + * Check for a modifier flag of the following forms: + * "flag" (same as "true") + * "flag=yes|true|1" + * "flag="no|false|0" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aResult If the flag is found, the value is assigned here. + * @return Whether the flag was handled. + */ +static bool CheckFlag(const nsAString& aFlag, const nsAString& aData, + bool& aResult) { + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + if (aFlag.Length() == aData.Length()) { + // the data is simply "flag", which is the same as "flag=yes" + aResult = true; + return true; + } + + if (aData.CharAt(aFlag.Length()) != '=') { + // the data is "flag2=", which is not anything we care about + return false; + } + + if (aData.Length() == aFlag.Length() + 1) { + aResult = false; + return true; + } + + switch (aData.CharAt(aFlag.Length() + 1)) { + case '1': + case 't': // true + case 'y': // yes + aResult = true; + return true; + + case '0': + case 'f': // false + case 'n': // no + aResult = false; + return true; + } + + return false; +} + +enum TriState { eUnspecified, eBad, eOK }; + +/** + * Check for a modifier flag of the following form: + * "flag=string" + * "flag!=string" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. + * @param aResult If this is "ok" when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ +static bool CheckStringFlag(const nsAString& aFlag, const nsAString& aData, + const nsAString& aValue, TriState& aResult) { + if (aData.Length() < aFlag.Length() + 1) { + return false; + } + + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + bool comparison = true; + if (aData[aFlag.Length()] != '=') { + if (aData[aFlag.Length()] == '!' && aData.Length() >= aFlag.Length() + 2 && + aData[aFlag.Length() + 1] == '=') { + comparison = false; + } else { + return false; + } + } + + if (aResult != eOK) { + nsDependentSubstring testdata = + Substring(aData, aFlag.Length() + (comparison ? 1 : 2)); + if (testdata.Equals(aValue)) { + aResult = comparison ? eOK : eBad; + } else { + aResult = comparison ? eBad : eOK; + } + } + + return true; +} + +static bool CheckOsFlag(const nsAString& aFlag, const nsAString& aData, + const nsAString& aValue, TriState& aResult) { + bool result = CheckStringFlag(aFlag, aData, aValue, aResult); +#if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(ANDROID) + if (result && aResult == eBad) { + result = CheckStringFlag(aFlag, aData, u"likeunix"_ns, aResult); + } +#endif + return result; +} + +/** + * Check for a modifier flag of the following form: + * "flag=version" + * "flag<=version" + * "flag<version" + * "flag>=version" + * "flag>version" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. If this is empty then no + * comparison will match. + * @param aResult If this is eOK when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ + +#define COMPARE_EQ 1 << 0 +#define COMPARE_LT 1 << 1 +#define COMPARE_GT 1 << 2 + +static bool CheckVersionFlag(const nsString& aFlag, const nsString& aData, + const nsString& aValue, TriState& aResult) { + if (aData.Length() < aFlag.Length() + 2) { + return false; + } + + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + if (aValue.Length() == 0) { + if (aResult != eOK) { + aResult = eBad; + } + return true; + } + + uint32_t comparison; + nsAutoString testdata; + + switch (aData[aFlag.Length()]) { + case '=': + comparison = COMPARE_EQ; + testdata = Substring(aData, aFlag.Length() + 1); + break; + + case '<': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 2); + } else { + comparison = COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + case '>': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 2); + } else { + comparison = COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + default: + return false; + } + + if (testdata.Length() == 0) { + return false; + } + + if (aResult != eOK) { + int32_t c = mozilla::CompareVersions(NS_ConvertUTF16toUTF8(aValue).get(), + NS_ConvertUTF16toUTF8(testdata).get()); + if ((c == 0 && comparison & COMPARE_EQ) || + (c < 0 && comparison & COMPARE_LT) || + (c > 0 && comparison & COMPARE_GT)) { + aResult = eOK; + } else { + aResult = eBad; + } + } + + return true; +} + +// In-place conversion of ascii characters to lower case +static void ToLowerCase(char* aToken) { + for (; *aToken; ++aToken) { + *aToken = NS_ToLower(*aToken); + } +} + +namespace { + +struct CachedDirective { + int lineno; + char* argv[4]; +}; + +} // namespace + +void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, + bool aChromeOnly) { + nsComponentManagerImpl::ManifestProcessingContext mgrcx(aType, aFile, + aChromeOnly); + nsChromeRegistry::ManifestProcessingContext chromecx(aType, aFile); + nsresult rv; + + constexpr auto kContentAccessible = u"contentaccessible"_ns; + constexpr auto kRemoteEnabled = u"remoteenabled"_ns; + constexpr auto kRemoteRequired = u"remoterequired"_ns; + constexpr auto kApplication = u"application"_ns; + constexpr auto kAppVersion = u"appversion"_ns; + constexpr auto kGeckoVersion = u"platformversion"_ns; + constexpr auto kOs = u"os"_ns; + constexpr auto kOsVersion = u"osversion"_ns; + constexpr auto kABI = u"abi"_ns; + constexpr auto kProcess = u"process"_ns; +#if defined(MOZ_WIDGET_ANDROID) + constexpr auto kTablet = u"tablet"_ns; +#endif + // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, but it's not + // possible to have conditional manifest contents, so we need to recognize and + // discard these tokens even when MOZ_BACKGROUNDTASKS is not set. + constexpr auto kBackgroundTask = u"backgroundtask"_ns; + + constexpr auto kMain = u"main"_ns; + constexpr auto kContent = u"content"_ns; + + // Obsolete + constexpr auto kXPCNativeWrappers = u"xpcnativewrappers"_ns; + + nsAutoString appID; + nsAutoString appVersion; + nsAutoString geckoVersion; + nsAutoString osTarget; + nsAutoString abi; + nsAutoString process; + + nsCOMPtr<nsIXULAppInfo> xapp(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); + if (xapp) { + nsAutoCString s; + rv = xapp->GetID(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, appID); + } + + rv = xapp->GetVersion(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, appVersion); + } + + rv = xapp->GetPlatformVersion(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, geckoVersion); + } + + nsCOMPtr<nsIXULRuntime> xruntime(do_QueryInterface(xapp)); + if (xruntime) { + rv = xruntime->GetOS(s); + if (NS_SUCCEEDED(rv)) { + ToLowerCase(s); + CopyUTF8toUTF16(s, osTarget); + } + + rv = xruntime->GetXPCOMABI(s); + if (NS_SUCCEEDED(rv) && osTarget.Length()) { + ToLowerCase(s); + CopyUTF8toUTF16(s, abi); + abi.Insert(char16_t('_'), 0); + abi.Insert(osTarget, 0); + } + } + } + + nsAutoString osVersion; +#if defined(XP_WIN) +# pragma warning(push) +# pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx + OSVERSIONINFO info = {sizeof(OSVERSIONINFO)}; + if (GetVersionEx(&info)) { + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion, + info.dwMinorVersion); + } +# pragma warning(pop) +#elif defined(MOZ_WIDGET_COCOA) + SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor(); + SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor(); + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion); +#elif defined(MOZ_WIDGET_GTK) + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version, + gtk_minor_version); +#elif defined(MOZ_WIDGET_ANDROID) + bool isTablet = false; + if (jni::IsAvailable()) { + jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); + osVersion.Assign(release->ToString()); + isTablet = java::GeckoAppShell::IsTablet(); + } +#endif + + if (XRE_IsContentProcess()) { + process = kContent; + } else { + process = kMain; + } + + char* token; + char* newline = aBuf; + uint32_t line = 0; + + // outer loop tokenizes by newline + while (*newline) { + while (*newline && IsNewline(*newline)) { + ++newline; + ++line; + } + if (!*newline) { + break; + } + + token = newline; + while (*newline && !IsNewline(*newline)) { + ++newline; + } + + if (*newline) { + *newline = '\0'; + ++newline; + } + ++line; + + if (*token == '#') { // ignore lines that begin with # as comments + continue; + } + + char* whitespace = token; + token = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + if (!token) { + continue; + } + + const ManifestDirective* directive = nullptr; + for (const ManifestDirective* d = kParsingTable; + d < ArrayEnd(kParsingTable); ++d) { + if (!strcmp(d->directive, token)) { + directive = d; + break; + } + } + + if (!directive) { + LogMessageWithContext( + aFile, line, "Ignoring unrecognized chrome manifest directive '%s'.", + token); + continue; + } + + if (!directive->ischrome && NS_BOOTSTRAPPED_LOCATION == aType) { + LogMessageWithContext( + aFile, line, + "Bootstrapped manifest not allowed to use '%s' directive.", token); + continue; + } + + NS_ASSERTION(directive->argc < 4, "Need to reset argv array length"); + char* argv[4]; + for (int i = 0; i < directive->argc; ++i) { + argv[i] = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + } + + if (!argv[directive->argc - 1]) { + LogMessageWithContext(aFile, line, + "Not enough arguments for chrome manifest " + "directive '%s', expected %i.", + token, directive->argc); + continue; + } + + bool ok = true; + TriState stAppVersion = eUnspecified; + TriState stGeckoVersion = eUnspecified; + TriState stApp = eUnspecified; + TriState stOsVersion = eUnspecified; + TriState stOs = eUnspecified; + TriState stABI = eUnspecified; + TriState stProcess = eUnspecified; +#if defined(MOZ_WIDGET_ANDROID) + TriState stTablet = eUnspecified; +#endif +#ifdef MOZ_BACKGROUNDTASKS + // When in background task mode, default to not registering + // category directivies unless backgroundtask=1 is specified. + TriState stBackgroundTask = (BackgroundTasks::IsBackgroundTaskMode() && + strcmp("category", directive->directive) == 0) + ? eBad + : eUnspecified; +#endif + int flags = 0; + + while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && + ok) { + ToLowerCase(token); + NS_ConvertASCIItoUTF16 wtoken(token); + + if (CheckStringFlag(kApplication, wtoken, appID, stApp) || + CheckOsFlag(kOs, wtoken, osTarget, stOs) || + CheckStringFlag(kABI, wtoken, abi, stABI) || + CheckStringFlag(kProcess, wtoken, process, stProcess) || + CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || + CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || + CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, + stGeckoVersion)) { + continue; + } + +#if defined(MOZ_WIDGET_ANDROID) + bool tablet = false; + if (CheckFlag(kTablet, wtoken, tablet)) { + stTablet = (tablet == isTablet) ? eOK : eBad; + continue; + } +#endif + + // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, it's not + // possible to have conditional manifest contents. + bool flag; + if (CheckFlag(kBackgroundTask, wtoken, flag)) { +#if defined(MOZ_BACKGROUNDTASKS) + // Background task mode is active: filter. + stBackgroundTask = + (flag == BackgroundTasks::IsBackgroundTaskMode()) ? eOK : eBad; +#endif /* defined(MOZ_BACKGROUNDTASKS) */ + continue; + } + + if (directive->contentflags) { + bool flag; + if (CheckFlag(kContentAccessible, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::CONTENT_ACCESSIBLE; + continue; + } + if (CheckFlag(kRemoteEnabled, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::REMOTE_ALLOWED; + continue; + } + if (CheckFlag(kRemoteRequired, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::REMOTE_REQUIRED; + continue; + } + } + + bool xpcNativeWrappers = true; // Dummy for CheckFlag. + if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) { + LogMessageWithContext( + aFile, line, "Ignoring obsolete chrome registration modifier '%s'.", + token); + continue; + } + + LogMessageWithContext( + aFile, line, "Unrecognized chrome manifest modifier '%s'.", token); + ok = false; + } + + if (!ok || stApp == eBad || stAppVersion == eBad || + stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || +#ifdef MOZ_WIDGET_ANDROID + stTablet == eBad || +#endif +#ifdef MOZ_BACKGROUNDTASKS + stBackgroundTask == eBad || +#endif + stABI == eBad || stProcess == eBad) { + continue; + } + + if (directive->regfunc) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + continue; + } + + if (!nsChromeRegistry::gChromeRegistry) { + nsCOMPtr<nsIChromeRegistry> cr = mozilla::services::GetChromeRegistry(); + if (!nsChromeRegistry::gChromeRegistry) { + LogMessageWithContext(aFile, line, + "Chrome registry isn't available yet."); + continue; + } + } + + (nsChromeRegistry::gChromeRegistry->*(directive->regfunc))(chromecx, line, + argv, flags); + } else if (directive->ischrome || !aChromeOnly) { + (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc))( + mgrcx, line, argv); + } + } +} diff --git a/xpcom/components/ManifestParser.h b/xpcom/components/ManifestParser.h new file mode 100644 index 0000000000..e66cb58bcb --- /dev/null +++ b/xpcom/components/ManifestParser.h @@ -0,0 +1,23 @@ +/* -*- 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 ManifestParser_h +#define ManifestParser_h + +#include "nsComponentManager.h" +#include "nsChromeRegistry.h" +#include "mozilla/Attributes.h" +#include "mozilla/FileLocation.h" + +void ParseManifest(NSLocationType aType, mozilla::FileLocation& aFile, + char* aBuf, bool aChromeOnly); + +void LogMessage(const char* aMsg, ...) MOZ_FORMAT_PRINTF(1, 2); + +void LogMessageWithContext(mozilla::FileLocation& aFile, uint32_t aLineNumber, + const char* aMsg, ...) MOZ_FORMAT_PRINTF(3, 4); + +#endif // ManifestParser_h diff --git a/xpcom/components/Module.h b/xpcom/components/Module.h new file mode 100644 index 0000000000..044626b1ab --- /dev/null +++ b/xpcom/components/Module.h @@ -0,0 +1,76 @@ +/* -*- 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 mozilla_Module_h +#define mozilla_Module_h + +#include "nscore.h" + +namespace mozilla { + +namespace Module { +/** + * This selector allows components to be marked so that they're only loaded + * into certain kinds of processes. Selectors can be combined. + */ +// Note: This must be kept in sync with the selector matching in +// nsComponentManager.cpp. +enum ProcessSelector { + ANY_PROCESS = 0x0, + MAIN_PROCESS_ONLY = 0x1, + CONTENT_PROCESS_ONLY = 0x2, + + /** + * By default, modules are not loaded in the GPU, VR, Socket, RDD, Utility + * and IPDLUnitTest processes, even if ANY_PROCESS is specified. This flag + * enables a module in the relevant process. + * + * NOTE: IPDLUnitTest does not have its own flag, and will only load a + * module if it is enabled in all processes. + */ + ALLOW_IN_GPU_PROCESS = 0x4, + ALLOW_IN_VR_PROCESS = 0x8, + ALLOW_IN_SOCKET_PROCESS = 0x10, + ALLOW_IN_RDD_PROCESS = 0x20, + ALLOW_IN_UTILITY_PROCESS = 0x30, + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY, + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS, + ALLOW_IN_GPU_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_RDD_AND_SOCKET_PROCESS = + ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS | ALLOW_IN_UTILITY_PROCESS +}; + +static constexpr size_t kMaxProcessSelector = + size_t(ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS); + +/** + * This allows category entries to be marked so that they are or are + * not loaded when in backgroundtask mode. + */ +// Note: This must be kept in sync with the selector matching in +// StaticComponents.cpp.in. +enum BackgroundTasksSelector { + NO_TASKS = 0x0, + ALL_TASKS = 0xFFFF, +}; +}; // namespace Module + +} // namespace mozilla + +#endif // mozilla_Module_h diff --git a/xpcom/components/ModuleUtils.h b/xpcom/components/ModuleUtils.h new file mode 100644 index 0000000000..92ec8aa173 --- /dev/null +++ b/xpcom/components/ModuleUtils.h @@ -0,0 +1,81 @@ +/* -*- 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 mozilla_GenericModule_h +#define mozilla_GenericModule_h + +#include <type_traits> + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/Module.h" + +#define NS_GENERIC_FACTORY_CONSTRUCTOR(_InstanceClass) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + inst = new _InstanceClass(); \ + return inst->QueryInterface(aIID, aResult); \ + } + +#define NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(_InstanceClass, _InitMethod) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + nsresult rv; \ + \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + inst = new _InstanceClass(); \ + rv = inst->_InitMethod(); \ + if (NS_SUCCEEDED(rv)) { \ + rv = inst->QueryInterface(aIID, aResult); \ + } \ + \ + return rv; \ + } + +namespace mozilla { +namespace detail { + +template <typename T> +struct RemoveAlreadyAddRefed { + using Type = T; +}; + +template <typename T> +struct RemoveAlreadyAddRefed<already_AddRefed<T>> { + using Type = T; +}; + +} // namespace detail +} // namespace mozilla + +// 'Constructor' that uses an existing getter function that gets a singleton. +#define NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(_InstanceClass, _GetterProc) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + using T = \ + mozilla::detail::RemoveAlreadyAddRefed<decltype(_GetterProc())>::Type; \ + static_assert( \ + std::is_same_v<already_AddRefed<T>, decltype(_GetterProc())>, \ + "Singleton constructor must return already_AddRefed"); \ + static_assert( \ + std::is_base_of<_InstanceClass, T>::value, \ + "Singleton constructor must return correct already_AddRefed"); \ + inst = _GetterProc(); \ + if (nullptr == inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + return inst->QueryInterface(aIID, aResult); \ + } + +#endif // mozilla_GenericModule_h diff --git a/xpcom/components/StaticComponents.cpp.in b/xpcom/components/StaticComponents.cpp.in new file mode 100644 index 0000000000..7f3fee6859 --- /dev/null +++ b/xpcom/components/StaticComponents.cpp.in @@ -0,0 +1,410 @@ +/* -*- 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 "StaticComponents.h" + +#include "mozilla/ArrayUtils.h" +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif +#include "mozilla/PerfectHash.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozJSModuleLoader.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsContentUtils.h" +#include "nsIFactory.h" +#include "nsISupports.h" +#include "nsIXPConnect.h" +#include "nsString.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "xptdata.h" +#include "xptinfo.h" +#include "js/PropertyAndElement.h" // JS_GetProperty + +// Cleanup pollution from zipstruct.h +#undef UNSUPPORTED + +// Public includes +//# @includes@ + +// Relative includes +//# @relative_includes@ + +//# @decls@ + +namespace mozilla { + +using dom::AutoJSAPI; + +namespace xpcom { + +static constexpr uint32_t kNoContractID = 0xffffffff; + +namespace { +// Template helpers for constructor function sanity checks. +template <typename T> +struct RemoveAlreadyAddRefed { + using Type = T; +}; + +template <typename T> +struct RemoveAlreadyAddRefed<already_AddRefed<T>> { + using Type = T; +}; +} // anonymous namespace + + +uint8_t gInvalidContracts[kContractCount / 8 + 1]; + +static StaticRefPtr<nsISupports> gServiceInstances[kStaticModuleCount]; + +uint8_t gInitCalled[kModuleInitCount / 8 + 1]; + +static const char gStrings[] = +//# @strings@ + ""; + +const StaticCategory gStaticCategories[kStaticCategoryCount] = { +//# @categories@ +}; +const StaticCategoryEntry gStaticCategoryEntries[] = { +//# @category_entries@ +}; + +const nsXPTInterface gInterfaces[] = { +//# @interfaces@ +}; + +const StringOffset gComponentJSMs[] = { +//# @component_jsms@ +}; + +const StringOffset gComponentESModules[] = { +//# @component_esmodules@ +}; + +/** + * Returns a nsCString corresponding to the given entry in the `gStrings` string + * table. The resulting nsCString points directly to static storage, and does + * not incur any memory allocation overhead. + */ +static inline nsCString GetString(const StringOffset& aOffset) { + const char* str = &gStrings[aOffset.mOffset]; + nsCString result; + result.AssignLiteral(str, strlen(str)); + return result; +} + +nsCString ContractEntry::ContractID() const { + return GetString(mContractID); +} + +bool ContractEntry::Matches(const nsACString& aContractID) const { + return aContractID == ContractID() && Module().Active(); +} + +enum class ComponentType { JSM, ESM }; + +template <ComponentType type> +static nsresult ConstructJSMOrESMComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + if (!nsComponentManagerImpl::JSLoaderReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSObject*> exports(cx); + if constexpr (type == ComponentType::JSM) { + JS::Rooted<JSObject*> global(cx); + MOZ_TRY(mozJSModuleLoader::Get()->Import(cx, aURI, &global, &exports)); + } else { + MOZ_TRY(mozJSModuleLoader::Get()->ImportESModule(cx, aURI, &exports)); + } + + JS::Rooted<JS::Value> ctor(cx); + if (!JS_GetProperty(cx, exports, aConstructor, &ctor) || + !ctor.isObject()) { + return NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED; + } + + JS::Rooted<JSObject*> inst(cx); + if (!JS::Construct(cx, ctor, JS::HandleValueArray::empty(), &inst)) { + return NS_ERROR_FAILURE; + } + + return nsContentUtils::XPConnect()->WrapJS(cx, inst, NS_GET_IID(nsISupports), + (void**)aResult); +} + +static nsresult ConstructJSMComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + return ConstructJSMOrESMComponent<ComponentType::JSM>( + aURI, aConstructor, aResult); +} + +static nsresult ConstructESModuleComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + return ConstructJSMOrESMComponent<ComponentType::ESM>( + aURI, aConstructor, aResult); +} + +//# @module_cid_table@ + +//# @module_contract_id_table@ + +//# @js_services_table@ + +//# @protocol_handlers_table@ + +static inline bool CalledInit(size_t aIdx) { + return GetBit(gInitCalled, aIdx); +} + +static nsresult CallInitFunc(size_t aIdx) { + if (CalledInit(aIdx)) { + return NS_OK; + } + + nsresult rv = NS_OK; + switch (aIdx) { +//# @init_funcs@ + } + + SetBit(gInitCalled, aIdx); + + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return rv; +} + +static void CallUnloadFuncs() { +//# @unload_funcs@ +} + +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult) { + // The full set of constructors for all static modules. + // This switch statement will be compiled to a relative address jump table + // with no runtime relocations and a single indirect jump. + switch (aID) { +//# @constructors@ + } + + MOZ_ASSERT_UNREACHABLE("Constructor didn't return"); + return NS_ERROR_FAILURE; +} + + +namespace { + +class StaticModuleFactory final : public nsIFactory { + NS_DECL_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit StaticModuleFactory(ModuleID aID) : mID(aID) {} + +private: + ~StaticModuleFactory() = default; + + const ModuleID mID; +}; + +NS_IMPL_ISUPPORTS(StaticModuleFactory, nsIFactory) + +NS_IMETHODIMP StaticModuleFactory::CreateInstance(const nsIID& aIID, + void** aResult) { + return CreateInstanceImpl(mID, aIID, aResult); +} + +} // anonymous namespace + + +already_AddRefed<nsIFactory> StaticModule::GetFactory() const { + return do_AddRef(new StaticModuleFactory(ID())); +} + +bool StaticModule::Active() const { + return FastProcessSelectorMatches(mProcessSelector); +} + +bool StaticModule::Overridable() const { + return mContractID.mOffset != kNoContractID; +} + +nsCString StaticModule::ContractID() const { + MOZ_ASSERT(Overridable()); + return GetString(mContractID); +} + +nsresult StaticModule::CreateInstance(const nsIID& aIID, void** aResult) const { + return CreateInstanceImpl(ID(), aIID, aResult); +} + +GetServiceHelper StaticModule::GetService() const { + return { ID(), nullptr }; +} + +GetServiceHelper StaticModule::GetService(nsresult* aRv) const { + return { ID(), aRv }; +} + + +nsISupports* StaticModule::ServiceInstance() const { + return gServiceInstances[Idx()]; +} + +void StaticModule::SetServiceInstance( + already_AddRefed<nsISupports> aInst) const { + gServiceInstances[Idx()] = aInst; +} + + +nsCString StaticCategoryEntry::Entry() const { + return GetString(mEntry); +} + +nsCString StaticCategoryEntry::Value() const { + return GetString(mValue); +} + +bool StaticCategoryEntry::Active() const { + if (!FastProcessSelectorMatches(mProcessSelector)) { + return false; + } +#ifdef MOZ_BACKGROUNDTASKS + if (MOZ_UNLIKELY(BackgroundTasks::IsBackgroundTaskMode())) { + return mBackgroundTasksSelector != Module::BackgroundTasksSelector::NO_TASKS; + } +#endif /* MOZ_BACKGROUNDTASKS */ + return true; +} + +nsCString StaticCategory::Name() const { + return GetString(mName); +} + +nsCString JSServiceEntry::Name() const { + return GetString(mName); +} + +JSServiceEntry::InterfaceList JSServiceEntry::Interfaces() const { + InterfaceList iids; + iids.SetCapacity(mInterfaceCount); + + for (size_t i = 0; i < mInterfaceCount; i++) { + nsXPTInterface ifaceID = gInterfaces[mInterfaceOffset.mOffset + i]; + iids.AppendElement(&nsXPTInterfaceInfo::Get(ifaceID)->IID()); + } + return iids; +} + + +/* static */ +const JSServiceEntry* JSServiceEntry::Lookup(const nsACString& aName) { + return LookupJSService(aName); +} + +nsCString StaticProtocolHandler::Scheme() const { + return GetString(mScheme); +} + +/* static */ +const StaticProtocolHandler* StaticProtocolHandler::Lookup(const nsACString& aScheme) { + return LookupProtocolHandler(aScheme); +} + +/* static */ const StaticModule* StaticComponents::LookupByCID( + const nsID& aCID) { + return ModuleByCID(aCID); +} + +/* static */ const StaticModule* StaticComponents::LookupByContractID( + const nsACString& aContractID) { + if (const ContractEntry* entry = LookupContractID(aContractID)) { + if (!entry->Invalid()) { + return &entry->Module(); + } + } + return nullptr; +} + +/* static */ bool StaticComponents::InvalidateContractID( + const nsACString& aContractID, bool aInvalid) { + if (const ContractEntry* entry = LookupContractID(aContractID)) { + entry->SetInvalid(aInvalid); + return true; + } + return false; +} + +/* static */ already_AddRefed<nsIUTF8StringEnumerator> +StaticComponents::GetComponentJSMs() { + auto jsms = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentJSMs)); + + for (const auto& entry : gComponentJSMs) { + jsms->AppendElement(GetString(entry)); + } + + nsCOMPtr<nsIUTF8StringEnumerator> result; + MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result), + jsms.release())); + return result.forget(); +} + +/* static */ already_AddRefed<nsIUTF8StringEnumerator> +StaticComponents::GetComponentESModules() { + auto esModules = MakeUnique<nsTArray<nsCString>>(MOZ_ARRAY_LENGTH(gComponentESModules)); + + for (const auto& entry : gComponentESModules) { + esModules->AppendElement(GetString(entry)); + } + + nsCOMPtr<nsIUTF8StringEnumerator> result; + MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result), + esModules.release())); + return result.forget(); +} + +/* static */ Span<const JSServiceEntry> StaticComponents::GetJSServices() { + return { gJSServices, ArrayLength(gJSServices) }; +} + +/* static */ void StaticComponents::Shutdown() { + CallUnloadFuncs(); +} + +/* static */ const nsID& Components::GetCID(ModuleID aID) { + return gStaticModules[size_t(aID)].CID(); +} + +nsresult GetServiceHelper::operator()(const nsIID& aIID, void** aResult) const { + nsresult rv = + nsComponentManagerImpl::gComponentManager->GetService(mId, aIID, aResult); + return SetResult(rv); +} + +nsresult CreateInstanceHelper::operator()(const nsIID& aIID, + void** aResult) const { + const auto& entry = gStaticModules[size_t(mId)]; + if (!entry.Active()) { + return SetResult(NS_ERROR_FACTORY_NOT_REGISTERED); + } + + nsresult rv = entry.CreateInstance(aIID, aResult); + return SetResult(rv); +} + +} // namespace xpcom +} // namespace mozilla diff --git a/xpcom/components/StaticComponents.h b/xpcom/components/StaticComponents.h new file mode 100644 index 0000000000..ac4095f2f3 --- /dev/null +++ b/xpcom/components/StaticComponents.h @@ -0,0 +1,284 @@ +/* -*- 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 StaticComponents_h +#define StaticComponents_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Module.h" +#include "mozilla/Span.h" +#include "nsID.h" +#include "nsStringFwd.h" +#include "nscore.h" + +#include "mozilla/Components.h" +#include "StaticComponentData.h" + +class nsIFactory; +class nsIUTF8StringEnumerator; +class nsISupports; +template <typename T, size_t N> +class AutoTArray; + +namespace mozilla { +namespace xpcom { + +struct ContractEntry; +struct StaticModule; + +struct StaticCategoryEntry; +struct StaticCategory; + +struct StaticProtocolHandler; + +extern const StaticModule gStaticModules[kStaticModuleCount]; + +extern const ContractEntry gContractEntries[kContractCount]; +extern uint8_t gInvalidContracts[kContractCount / 8 + 1]; + +extern const StaticCategory gStaticCategories[kStaticCategoryCount]; +extern const StaticCategoryEntry gStaticCategoryEntries[]; + +extern const StaticProtocolHandler + gStaticProtocolHandlers[kStaticProtocolHandlerCount]; + +template <size_t N> +static inline bool GetBit(const uint8_t (&aBits)[N], size_t aBit) { + static constexpr size_t width = sizeof(aBits[0]) * 8; + + size_t idx = aBit / width; + MOZ_ASSERT(idx < N); + return aBits[idx] & (1 << (aBit % width)); +} + +template <size_t N> +static inline void SetBit(uint8_t (&aBits)[N], size_t aBit, + bool aValue = true) { + static constexpr size_t width = sizeof(aBits[0]) * 8; + + size_t idx = aBit / width; + MOZ_ASSERT(idx < N); + if (aValue) { + aBits[idx] |= 1 << (aBit % width); + } else { + aBits[idx] &= ~(1 << (aBit % width)); + } +} + +/** + * Represents a string entry in the static string table. Can be converted to a + * nsCString using GetString() in StaticComponents.cpp. + * + * This is a struct rather than a pure offset primarily for the purposes of type + * safety, but also so that it can easily be extended to include a static length + * in the future, if efficiency concerns warrant it. + */ +struct StringOffset final { + uint32_t mOffset; +}; + +/** + * Represents an offset into the interfaces table. + */ +struct InterfaceOffset final { + uint16_t mOffset; +}; + +/** + * Represents a static component entry defined in a `Classes` list in an XPCOM + * manifest. Handles creating instances of and caching service instances for + * that class. + */ +struct StaticModule { + nsID mCID; + StringOffset mContractID; + Module::ProcessSelector mProcessSelector; + + const nsID& CID() const { return mCID; } + + ModuleID ID() const { return ModuleID(this - gStaticModules); } + + /** + * Returns this entry's index in the gStaticModules array. + */ + size_t Idx() const { return size_t(ID()); } + + /** + * Returns true if this component's corresponding contract ID is expected to + * be overridden at runtime. If so, it should always be looked up by its + * ContractID() when retrieving its service instance. + */ + bool Overridable() const; + + /** + * If this entry is overridable, returns its associated contract ID string. + * The component should always be looked up by this contract ID when + * retrieving its service instance. + * + * Note: This may *only* be called if Overridable() returns true. + */ + nsCString ContractID() const; + + /** + * Returns true if this entry is active. Typically this will only return false + * if the entry's process selector does not match this process. + */ + bool Active() const; + + already_AddRefed<nsIFactory> GetFactory() const; + + nsresult CreateInstance(const nsIID& aIID, void** aResult) const; + + GetServiceHelper GetService() const; + GetServiceHelper GetService(nsresult*) const; + + nsISupports* ServiceInstance() const; + void SetServiceInstance(already_AddRefed<nsISupports> aInst) const; +}; + +/** + * Represents a static mapping between a contract ID string and a StaticModule + * entry. + */ +struct ContractEntry final { + StringOffset mContractID; + ModuleID mModuleID; + + size_t Idx() const { return this - gContractEntries; } + + nsCString ContractID() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } + + /** + * Returns true if this entry's underlying module is active, and its contract + * ID matches the given contract ID string. This is used by the PerfectHash + * function to determine whether to return a result for this entry. + */ + bool Matches(const nsACString& aContractID) const; + + /** + * Returns true if this entry has been invalidated, and should be ignored. + * + * Contract IDs may be overwritten at runtime. When that happens for a static + * contract ID, we mark its entry invalid, and ignore it thereafter. + */ + bool Invalid() const { return GetBit(gInvalidContracts, Idx()); } + + /** + * Marks this entry invalid (or unsets the invalid bit if aInvalid is false), + * after which it will be ignored in contract ID lookup attempts. See + * `Invalid()` above. + */ + void SetInvalid(bool aInvalid = true) const { + return SetBit(gInvalidContracts, Idx(), aInvalid); + } +}; + +/** + * Represents a declared category manager entry declared in an XPCOM manifest. + * + * The entire set of static category entries is read at startup and loaded into + * the category manager's dynamic hash tables, so there is memory and + * initialization overhead for each entry in these tables. This may be further + * optimized in the future to reduce some of that overhead. + */ +struct StaticCategoryEntry final { + StringOffset mEntry; + StringOffset mValue; + Module::BackgroundTasksSelector mBackgroundTasksSelector; + Module::ProcessSelector mProcessSelector; + + nsCString Entry() const; + nsCString Value() const; + bool Active() const; +}; + +struct StaticCategory final { + StringOffset mName; + uint16_t mStart; + uint16_t mCount; + + nsCString Name() const; + + const StaticCategoryEntry* begin() const { + return &gStaticCategoryEntries[mStart]; + } + const StaticCategoryEntry* end() const { + return &gStaticCategoryEntries[mStart + mCount]; + } +}; + +struct JSServiceEntry final { + using InterfaceList = AutoTArray<const nsIID*, 4>; + + static const JSServiceEntry* Lookup(const nsACString& aName); + + StringOffset mName; + ModuleID mModuleID; + + InterfaceOffset mInterfaceOffset; + + uint8_t mInterfaceCount; + + nsCString Name() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } + + InterfaceList Interfaces() const; +}; + +struct StaticProtocolHandler final { + static const StaticProtocolHandler* Lookup(const nsACString& aScheme); + static const StaticProtocolHandler& Default() { + return gStaticProtocolHandlers[kDefaultProtocolHandlerIndex]; + } + + StringOffset mScheme; + uint32_t mProtocolFlags; + int32_t mDefaultPort; + ModuleID mModuleID; + bool mHasDynamicFlags; + + nsCString Scheme() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } +}; + +class StaticComponents final { + public: + static const StaticModule* LookupByCID(const nsID& aCID); + + static const StaticModule* LookupByContractID(const nsACString& aContractID); + + /** + * Marks a static contract ID entry invalid (or unsets the invalid bit if + * aInvalid is false). See `CategoryEntry::Invalid()`. + */ + static bool InvalidateContractID(const nsACString& aContractID, + bool aInvalid = true); + + static already_AddRefed<nsIUTF8StringEnumerator> GetComponentJSMs(); + static already_AddRefed<nsIUTF8StringEnumerator> GetComponentESModules(); + + static Span<const JSServiceEntry> GetJSServices(); + + /** + * Calls any module unload from manifests whose components have been loaded. + */ + static void Shutdown(); +}; + +} // namespace xpcom +} // namespace mozilla + +#endif // defined StaticComponents_h diff --git a/xpcom/components/components.conf b/xpcom/components/components.conf new file mode 100644 index 0000000000..73cc177e8d --- /dev/null +++ b/xpcom/components/components.conf @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'js_name': 'catMan', + 'cid': '{16d222a6-1dd2-11b2-b693-f38b02c021b2}', + 'contract_ids': ['@mozilla.org/categorymanager;1'], + 'interfaces': ['nsICategoryManager'], + 'legacy_constructor': 'nsCategoryManager::Create', + 'headers': ['/xpcom/components/nsCategoryManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{91775d60-d5dc-11d2-92fb-00e09805570f}', + 'legacy_constructor': 'nsComponentManagerImpl::Create', + 'headers': ['/xpcom/components/nsComponentManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] diff --git a/xpcom/components/gen_static_components.py b/xpcom/components/gen_static_components.py new file mode 100644 index 0000000000..3d73eda603 --- /dev/null +++ b/xpcom/components/gen_static_components.py @@ -0,0 +1,1206 @@ +import json +import os +import re +import struct +from collections import defaultdict +from uuid import UUID + +import buildconfig +from mozbuild.util import FileAvoidWrite +from perfecthash import PerfectHash + +NO_CONTRACT_ID = 0xFFFFFFFF + +PHF_SIZE = 512 + +TINY_PHF_SIZE = 16 + +# In tests, we might not have a (complete) buildconfig. +ENDIAN = ( + "<" if buildconfig.substs.get("TARGET_ENDIANNESS", "little") == "little" else ">" +) + + +# Represents a UUID in the format used internally by Gecko, and supports +# serializing it in that format to both C++ source and raw byte arrays. +class UUIDRepr(object): + def __init__(self, uuid): + self.uuid = uuid + + fields = uuid.fields + + self.a = fields[0] + self.b = fields[1] + self.c = fields[2] + + d = list(fields[3:5]) + for i in range(0, 6): + d.append(fields[5] >> (8 * (5 - i)) & 0xFF) + + self.d = tuple(d) + + def __str__(self): + return str(self.uuid) + + @property + def bytes(self): + return struct.pack(ENDIAN + "IHHBBBBBBBB", self.a, self.b, self.c, *self.d) + + def to_cxx(self): + rest = ", ".join("0x%02x" % b for b in self.d) + + return "{ 0x%x, 0x%x, 0x%x, { %s } }" % (self.a, self.b, self.c, rest) + + +# Corresponds to the Module::ProcessSelector enum in Module.h. The actual +# values don't matter, since the code generator emits symbolic constants for +# these values, but we use the same values as the enum constants for clarity. +class ProcessSelector: + ANY_PROCESS = 0x0 + MAIN_PROCESS_ONLY = 0x1 + CONTENT_PROCESS_ONLY = 0x2 + ALLOW_IN_GPU_PROCESS = 0x4 + ALLOW_IN_VR_PROCESS = 0x8 + ALLOW_IN_SOCKET_PROCESS = 0x10 + ALLOW_IN_RDD_PROCESS = 0x20 + ALLOW_IN_UTILITY_PROCESS = 0x30 + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY + ALLOW_IN_GPU_AND_SOCKET_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_RDD_AND_SOCKET_PROCESS = ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + + +# Maps ProcessSelector constants to the name of the corresponding +# Module::ProcessSelector enum value. +PROCESSES = { + ProcessSelector.ANY_PROCESS: "ANY_PROCESS", + ProcessSelector.MAIN_PROCESS_ONLY: "MAIN_PROCESS_ONLY", + ProcessSelector.CONTENT_PROCESS_ONLY: "CONTENT_PROCESS_ONLY", + ProcessSelector.ALLOW_IN_GPU_PROCESS: "ALLOW_IN_GPU_PROCESS", + ProcessSelector.ALLOW_IN_VR_PROCESS: "ALLOW_IN_VR_PROCESS", + ProcessSelector.ALLOW_IN_SOCKET_PROCESS: "ALLOW_IN_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_PROCESS: "ALLOW_IN_RDD_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS: "ALLOW_IN_GPU_AND_MAIN_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_VR_PROCESS: "ALLOW_IN_GPU_AND_VR_PROCESS", + ProcessSelector.ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 +} + + +# Emits the C++ symbolic constant corresponding to a ProcessSelector constant. +def lower_processes(processes): + return "Module::ProcessSelector::%s" % PROCESSES[processes] + + +# Emits the C++ symbolic constant for a ModuleEntry's ModuleID enum entry. +def lower_module_id(module): + return "ModuleID::%s" % module.name + + +# Corresponds to the Module::BackgroundTasksSelector enum in Module.h. The +# actual values don't matter, since the code generator emits symbolic constants +# for these values, but we use the same values as the enum constants for +# clarity. +class BackgroundTasksSelector: + NO_TASKS = 0x0 + ALL_TASKS = 0xFFFF + + +# Maps BackgroundTasksSelector constants to the name of the corresponding +# Module::BackgroundTasksSelector enum value. +BACKGROUNDTASKS = { + BackgroundTasksSelector.ALL_TASKS: "ALL_TASKS", + BackgroundTasksSelector.NO_TASKS: "NO_TASKS", +} + + +# Emits the C++ symbolic constant corresponding to a BackgroundTasks constant. +def lower_backgroundtasks(backgroundtasks): + return "Module::BackgroundTasksSelector::%s" % BACKGROUNDTASKS[backgroundtasks] + + +# Represents a static string table, indexed by offset. This allows us to +# reference strings from static data structures without requiring runtime +# relocations. +class StringTable(object): + def __init__(self): + self.entries = {} + self.entry_list = [] + self.size = 0 + + self._serialized = False + + # Returns the index of the given string in the `entry_list` array. If + # no entry for the string exists, it first creates one. + def get_idx(self, string): + idx = self.entries.get(string, None) + if idx is not None: + return idx + + assert not self._serialized + + assert len(string) == len(string.encode("utf-8")) + + idx = self.size + self.size += len(string) + 1 + + self.entries[string] = idx + self.entry_list.append(string) + return idx + + # Returns the C++ code representing string data of this string table, as a + # single string literal. This must only be called after the last call to + # `get_idx()` or `entry_to_cxx()` for this instance. + def to_cxx(self): + self._serialized = True + + lines = [] + + idx = 0 + for entry in self.entry_list: + str_ = entry.replace("\\", "\\\\").replace('"', r"\"").replace("\n", r"\n") + + lines.append(' /* 0x%x */ "%s\\0"\n' % (idx, str_)) + + idx += len(entry) + 1 + + return "".join(lines) + + # Returns a `StringEntry` struct initializer for the string table entry + # corresponding to the given string. If no matching entry exists, it is + # first created. + def entry_to_cxx(self, string): + idx = self.get_idx(string) + return "{ 0x%x } /* %s */" % (idx, pretty_string(string)) + + +strings = StringTable() + +interfaces = [] + + +# Represents a C++ namespace, containing a set of classes and potentially +# sub-namespaces. This is used to generate pre-declarations for incomplete +# types referenced in XPCOM manifests. +class Namespace(object): + def __init__(self, name=None): + self.name = name + self.classes = set() + self.namespaces = {} + + # Returns a Namespace object for the sub-namespace with the given name. + def sub(self, name): + assert name not in self.classes + + if name not in self.namespaces: + self.namespaces[name] = Namespace(name) + return self.namespaces[name] + + # Generates C++ code to pre-declare all classes in this namespace and all + # of its sub-namespaces. + def to_cxx(self): + res = "" + if self.name: + res += "namespace %s {\n" % self.name + + for clas in sorted(self.classes): + res += "class %s;\n" % clas + + for ns in sorted(self.namespaces.keys()): + res += self.namespaces[ns].to_cxx() + + if self.name: + res += "} // namespace %s\n" % self.name + + return res + + +# Represents a component defined in an XPCOM manifest's `Classes` array. +class ModuleEntry(object): + next_anon_id = 0 + + def __init__(self, data, init_idx): + self.cid = UUIDRepr(UUID(data["cid"])) + self.contract_ids = data.get("contract_ids", []) + self.type = data.get("type", "nsISupports") + self.categories = data.get("categories", {}) + self.processes = data.get("processes", 0) + self.headers = data.get("headers", []) + + self.js_name = data.get("js_name", None) + self.interfaces = data.get("interfaces", []) + + if len(self.interfaces) > 255: + raise Exception( + "JS service %s may not have more than 255 " "interfaces" % self.js_name + ) + + self.interfaces_offset = len(interfaces) + for iface in self.interfaces: + interfaces.append(iface) + + # If the manifest declares Init or Unload functions, this contains its + # index, as understood by the `CallInitFunc()` function. + # + # If it contains any value other than `None`, a corresponding + # `CallInitFunc(init_idx)` call will be genrated before calling this + # module's constructor. + self.init_idx = init_idx + + self.constructor = data.get("constructor", None) + self.legacy_constructor = data.get("legacy_constructor", None) + self.init_method = data.get("init_method", []) + + self.jsm = data.get("jsm", None) + self.esModule = data.get("esModule", None) + + self.external = data.get( + "external", not (self.headers or self.legacy_constructor) + ) + self.singleton = data.get("singleton", False) + self.overridable = data.get("overridable", False) + + self.protocol_config = data.get("protocol_config", None) + + if "name" in data: + self.anonymous = False + self.name = data["name"] + else: + self.anonymous = True + self.name = "Anonymous%03d" % ModuleEntry.next_anon_id + ModuleEntry.next_anon_id += 1 + + def error(str_): + raise Exception( + "Error defining component %s (%s): %s" + % (str(self.cid), ", ".join(map(repr, self.contract_ids)), str_) + ) + + if self.jsm: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.esModule: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.external: + if self.constructor or self.legacy_constructor: + error( + "Externally-constructed components may not specify " + "'constructor' or 'legacy_constructor' properties" + ) + if self.init_method: + error( + "Externally-constructed components may not specify " + "'init_method' properties" + ) + if self.type == "nsISupports": + error( + "Externally-constructed components must specify a type " + "other than nsISupports" + ) + + if self.constructor and self.legacy_constructor: + error( + "The 'constructor' and 'legacy_constructor' properties " + "are mutually exclusive" + ) + + if self.overridable and not self.contract_ids: + error("Overridable components must specify at least one contract " "ID") + + @property + def contract_id(self): + return self.contract_ids[0] + + # Generates the C++ code for a StaticModule struct initializer + # representing this component. + def to_cxx(self): + contract_id = ( + strings.entry_to_cxx(self.contract_id) + if self.overridable + else "{ 0x%x }" % NO_CONTRACT_ID + ) + + return """ + /* {name} */ {{ + /* {{{cid_string}}} */ + {cid}, + {contract_id}, + {processes}, + }}""".format( + name=self.name, + cid=self.cid.to_cxx(), + cid_string=str(self.cid), + contract_id=contract_id, + processes=lower_processes(self.processes), + ) + + # Generates the C++ code for a JSServiceEntry representing this module. + def lower_js_service(self): + return """ + {{ + {js_name}, + ModuleID::{name}, + {{ {iface_offset} }}, + {iface_count} + }}""".format( + js_name=strings.entry_to_cxx(self.js_name), + name=self.name, + iface_offset=self.interfaces_offset, + iface_count=len(self.interfaces), + ) + + # Generates the C++ code necessary to construct an instance of this + # component. + # + # This code lives in a function with the following arguments: + # + # - aIID: The `const nsIID&` interface ID that the resulting instance + # will be queried to. + # + # - aResult: The `void**` pointer in which to store the result. + # + # And which returns an `nsresult` indicating success or failure. + def lower_constructor(self): + res = "" + + if self.init_idx is not None: + res += " MOZ_TRY(CallInitFunc(%d));\n" % self.init_idx + + if self.legacy_constructor: + res += ( + " return /* legacy */ %s(aIID, aResult);\n" + % self.legacy_constructor + ) + return res + + if self.jsm: + res += ( + " nsCOMPtr<nsISupports> inst;\n" + " MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.jsm), json.dumps(self.constructor)) + ) + elif self.esModule: + res += ( + " nsCOMPtr<nsISupports> inst;\n" + " MOZ_TRY(ConstructESModuleComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.esModule), json.dumps(self.constructor)) + ) + elif self.external: + res += ( + " nsCOMPtr<nsISupports> inst = " + "mozCreateComponent<%s>();\n" % self.type + ) + # The custom constructor may return null, so check before calling + # any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_FAILURE);\n" + else: + res += " RefPtr<%s> inst = " % self.type + + if not self.constructor: + res += "new %s();\n" % self.type + else: + res += "%s();\n" % self.constructor + # The `new` operator is infallible, so we don't need to worry + # about it returning null, but custom constructors may, so + # check before calling any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);\n" + + # Check that the constructor function returns an appropriate + # `already_AddRefed` value for our declared type. + res += """ + using T = + RemoveAlreadyAddRefed<decltype(%(constructor)s())>::Type; + static_assert( + std::is_same_v<already_AddRefed<T>, decltype(%(constructor)s())>, + "Singleton constructor must return already_AddRefed"); + static_assert( + std::is_base_of<%(type)s, T>::value, + "Singleton constructor must return correct already_AddRefed"); + +""" % { + "type": self.type, + "constructor": self.constructor, + } + + if self.init_method: + res += " MOZ_TRY(inst->%s());\n" % self.init_method + + res += " return inst->QueryInterface(aIID, aResult);\n" + + return res + + # Generates the C++ code for the `mozilla::components::<name>` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "::mozilla::xpcom::ModuleID::%s" % self.name, + } + + res = ( + """ +namespace %(name)s { +static inline const nsID& CID() { + return ::mozilla::xpcom::Components::GetCID(%(id)s); +} + +static inline ::mozilla::xpcom::GetServiceHelper Service(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + if not self.singleton: + res += ( + """ +static inline ::mozilla::xpcom::CreateInstanceHelper Create(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + res += ( + """\ +} // namespace %(name)s +""" + % substs + ) + + return res + + # Generates the rust code for the `xpcom::components::<name>` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters_rust(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "super::ModuleID::%s" % self.name, + } + + res = ( + """ +#[allow(non_snake_case)] +pub mod %(name)s { + /// Get the singleton service instance for this component. + pub fn service<T: crate::XpCom>() -> Result<crate::RefPtr<T>, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::<T>::new(); + let rv = unsafe { super::Gecko_GetServiceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + if not self.singleton: + res += ( + """ + /// Create a new instance of this component. + pub fn create<T: crate::XpCom>() -> Result<crate::RefPtr<T>, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::<T>::new(); + let rv = unsafe { super::Gecko_CreateInstanceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + res += """\ +} +""" + + return res + + +# Returns a quoted string literal representing the given raw string, with +# certain special characters replaced so that it can be used in a C++-style +# (/* ... */) comment. +def pretty_string(string): + return json.dumps(string).replace("*/", r"*\/").replace("/*", r"/\*") + + +# Represents a static contract ID entry, corresponding to a C++ ContractEntry +# struct, mapping a contract ID to a static module entry. +class ContractEntry(object): + def __init__(self, contract, module): + self.contract = contract + self.module = module + + def to_cxx(self): + return """ + {{ + {contract}, + {module_id}, + }}""".format( + contract=strings.entry_to_cxx(self.contract), + module_id=lower_module_id(self.module), + ) + + +# Represents a static ProtocolHandler entry, corresponding to a C++ +# ProtocolEntry struct, mapping a scheme to a static module entry and metadata. +class ProtocolHandler(object): + def __init__(self, config, module): + def error(str_): + raise Exception( + "Error defining protocol handler %s (%s): %s" + % (str(module.cid), ", ".join(map(repr, module.contract_ids)), str_) + ) + + self.module = module + self.scheme = config.get("scheme", None) + if self.scheme is None: + error("No scheme defined for protocol component") + self.flags = config.get("flags", None) + if self.flags is None: + error("No flags defined for protocol component") + self.default_port = config.get("default_port", -1) + self.has_dynamic_flags = config.get("has_dynamic_flags", False) + + def to_cxx(self): + return """ + {{ + .mScheme = {scheme}, + .mProtocolFlags = {flags}, + .mDefaultPort = {default_port}, + .mModuleID = {module_id}, + .mHasDynamicFlags = {has_dynamic_flags}, + }} + """.format( + scheme=strings.entry_to_cxx(self.scheme), + module_id=lower_module_id(self.module), + flags=" | ".join("nsIProtocolHandler::%s" % flag for flag in self.flags), + default_port=self.default_port, + has_dynamic_flags="true" if self.has_dynamic_flags else "false", + ) + + +# Generates the C++ code for the StaticCategoryEntry and StaticCategory +# structs for all category entries declared in XPCOM manifests. +def gen_categories(substs, categories): + cats = [] + ents = [] + + count = 0 + for category, entries in sorted(categories.items()): + + def k(entry): + return tuple(entry[0]["name"]) + entry[1:] + + entries.sort(key=k) + + cats.append( + " { %s,\n" + " %d, %d },\n" % (strings.entry_to_cxx(category), count, len(entries)) + ) + count += len(entries) + + ents.append(" /* %s */\n" % pretty_string(category)) + for entry, value, processes in entries: + name = entry["name"] + backgroundtasks = entry.get( + "backgroundtasks", BackgroundTasksSelector.NO_TASKS + ) + + ents.append( + " { %s,\n" + " %s,\n" + " %s,\n" + " %s },\n" + % ( + strings.entry_to_cxx(name), + strings.entry_to_cxx(value), + lower_backgroundtasks(backgroundtasks), + lower_processes(processes), + ) + ) + ents.append("\n") + ents.pop() + + substs["category_count"] = len(cats) + substs["categories"] = "".join(cats) + substs["category_entries"] = "".join(ents) + + +# Generates the C++ code for all Init and Unload functions declared in XPCOM +# manifests. These form the bodies of the `CallInitFunc()` and `CallUnload` +# functions in StaticComponents.cpp. +def gen_module_funcs(substs, funcs): + inits = [] + unloads = [] + + template = """\ + case %d: + %s + break; +""" + + for i, (init, unload) in enumerate(funcs): + init_code = "%s();" % init if init else "/* empty */" + inits.append(template % (i, init_code)) + + if unload: + unloads.append( + """\ + if (CalledInit(%d)) { + %s(); + } +""" + % (i, unload) + ) + + substs["init_funcs"] = "".join(inits) + substs["unload_funcs"] = "".join(unloads) + substs["init_count"] = len(funcs) + + +def gen_interfaces(ifaces): + res = [] + for iface in ifaces: + res.append(" nsXPTInterface::%s,\n" % iface) + return "".join(res) + + +# Generates class pre-declarations for any types referenced in `Classes` array +# entries which do not have corresponding `headers` entries to fully declare +# their types. +def gen_decls(types): + root_ns = Namespace() + + for type_ in sorted(types): + parts = type_.split("::") + + ns = root_ns + for part in parts[:-1]: + ns = ns.sub(part) + ns.classes.add(parts[-1]) + + return root_ns.to_cxx() + + +# Generates the `switch` body for the `CreateInstanceImpl()` function, with a +# `case` for each value in ModuleID to construct an instance of the +# corresponding component. +def gen_constructors(entries): + constructors = [] + for entry in entries: + constructors.append( + """\ + case {id}: {{ +{constructor}\ + }} +""".format( + id=lower_module_id(entry), constructor=entry.lower_constructor() + ) + ) + + return "".join(constructors) + + +# Generates the getter code for each named component entry in the +# `mozilla::components::` namespace. +def gen_getters(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join(entry.lower_getters() for entry in entries if not entry.anonymous) + + +# Generates the rust getter code for each named component entry in the +# `xpcom::components::` module. +def gen_getters_rust(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join( + entry.lower_getters_rust() for entry in entries if not entry.anonymous + ) + + +def gen_includes(substs, all_headers): + headers = set() + absolute_headers = set() + + for header in all_headers: + if header.startswith("/"): + absolute_headers.add(header) + else: + headers.add(header) + + includes = ['#include "%s"' % header for header in sorted(headers)] + substs["includes"] = "\n".join(includes) + "\n" + + relative_includes = [ + '#include "../..%s"' % header for header in sorted(absolute_headers) + ] + substs["relative_includes"] = "\n".join(relative_includes) + "\n" + + +def to_category_list(val): + # Entries can be bare strings (like `"m-browser"`), lists of bare strings, + # or dictionaries (like `{"name": "m-browser", "backgroundtasks": + # BackgroundTasksSelector.ALL_TASKS}`), somewhat recursively. + + def ensure_dict(v): + # Turn `v` into `{"name": v}` if it's not already a dict. + if isinstance(v, dict): + return v + return {"name": v} + + if isinstance(val, (list, tuple)): + return tuple(ensure_dict(v) for v in val) + + if isinstance(val, dict): + # Explode `{"name": ["x", "y"], "backgroundtasks": ...}` into + # `[{"name": "x", "backgroundtasks": ...}, {"name": "y", "backgroundtasks": ...}]`. + names = val.pop("name") + + vals = [] + for entry in to_category_list(names): + d = dict(val) + d["name"] = entry["name"] + vals.append(d) + + return tuple(vals) + + return (ensure_dict(val),) + + +def gen_substs(manifests): + module_funcs = [] + + headers = set() + + modules = [] + categories = defaultdict(list) + + for manifest in manifests: + headers |= set(manifest.get("Headers", [])) + + init_idx = None + init = manifest.get("InitFunc") + unload = manifest.get("UnloadFunc") + if init or unload: + init_idx = len(module_funcs) + module_funcs.append((init, unload)) + + for clas in manifest["Classes"]: + modules.append(ModuleEntry(clas, init_idx)) + + for category, entries in manifest.get("Categories", {}).items(): + for key, entry in entries.items(): + if isinstance(entry, tuple): + value, process = entry + else: + value, process = entry, 0 + categories[category].append(({"name": key}, value, process)) + + cids = set() + contracts = [] + contract_map = {} + js_services = {} + protocol_handlers = {} + + jsms = set() + esModules = set() + + types = set() + + for mod in modules: + headers |= set(mod.headers) + + for contract_id in mod.contract_ids: + if contract_id in contract_map: + raise Exception("Duplicate contract ID: %s" % contract_id) + + entry = ContractEntry(contract_id, mod) + contracts.append(entry) + contract_map[contract_id] = entry + + for category, entries in mod.categories.items(): + for entry in to_category_list(entries): + categories[category].append((entry, mod.contract_id, mod.processes)) + + if mod.type and not mod.headers: + types.add(mod.type) + + if mod.jsm: + jsms.add(mod.jsm) + + if mod.esModule: + esModules.add(mod.esModule) + + if mod.js_name: + if mod.js_name in js_services: + raise Exception("Duplicate JS service name: %s" % mod.js_name) + js_services[mod.js_name] = mod + + if mod.protocol_config: + handler = ProtocolHandler(mod.protocol_config, mod) + if handler.scheme in protocol_handlers: + raise Exception("Duplicate protocol handler: %s" % handler.scheme) + protocol_handlers[handler.scheme] = handler + + if str(mod.cid) in cids: + raise Exception("Duplicate cid: %s" % str(mod.cid)) + cids.add(str(mod.cid)) + + cid_phf = PerfectHash(modules, PHF_SIZE, key=lambda module: module.cid.bytes) + + contract_phf = PerfectHash(contracts, PHF_SIZE, key=lambda entry: entry.contract) + + js_services_phf = PerfectHash( + list(js_services.values()), PHF_SIZE, key=lambda entry: entry.js_name + ) + + protocol_handlers_phf = PerfectHash( + list(protocol_handlers.values()), TINY_PHF_SIZE, key=lambda entry: entry.scheme + ) + + js_services_json = {} + for entry in js_services.values(): + for iface in entry.interfaces: + js_services_json[iface] = entry.js_name + + substs = {} + + gen_categories(substs, categories) + + substs["module_ids"] = "".join(" %s,\n" % entry.name for entry in cid_phf.entries) + + substs["module_count"] = len(modules) + substs["contract_count"] = len(contracts) + substs["protocol_handler_count"] = len(protocol_handlers) + + substs["default_protocol_handler_idx"] = protocol_handlers_phf.get_index("default") + + gen_module_funcs(substs, module_funcs) + + gen_includes(substs, headers) + + substs["component_jsms"] = ( + "\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n" + ) + substs["component_esmodules"] = ( + "\n".join( + " %s," % strings.entry_to_cxx(esModule) for esModule in sorted(esModules) + ) + + "\n" + ) + + substs["interfaces"] = gen_interfaces(interfaces) + + substs["decls"] = gen_decls(types) + + substs["constructors"] = gen_constructors(cid_phf.entries) + + substs["component_getters"] = gen_getters(cid_phf.entries) + + substs["component_getters_rust"] = gen_getters_rust(cid_phf.entries) + + substs["module_cid_table"] = cid_phf.cxx_codegen( + name="ModuleByCID", + entry_type="StaticModule", + entries_name="gStaticModules", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticModule*", + return_entry=( + "return entry.CID().Equals(aKey) && entry.Active()" " ? &entry : nullptr;" + ), + key_type="const nsID&", + key_bytes="reinterpret_cast<const char*>(&aKey)", + key_length="sizeof(nsID)", + ) + + substs["module_contract_id_table"] = contract_phf.cxx_codegen( + name="LookupContractID", + entry_type="ContractEntry", + entries_name="gContractEntries", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const ContractEntry*", + return_entry="return entry.Matches(aKey) ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_table"] = js_services_phf.cxx_codegen( + name="LookupJSService", + entry_type="JSServiceEntry", + entries_name="gJSServices", + lower_entry=lambda entry: entry.lower_js_service(), + return_type="const JSServiceEntry*", + return_entry="return entry.Name() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["protocol_handlers_table"] = protocol_handlers_phf.cxx_codegen( + name="LookupProtocolHandler", + entry_type="StaticProtocolHandler", + entries_name="gStaticProtocolHandlers", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticProtocolHandler*", + return_entry="return entry.Scheme() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_json"] = json.dumps(js_services_json, sort_keys=True, indent=4) + + # Do this only after everything else has been emitted so we're sure the + # string table is complete. + substs["strings"] = strings.to_cxx() + return substs + + +# Returns true if the given build config substitution is defined and truthy. +def defined(subst): + return bool(buildconfig.substs.get(subst)) + + +def read_manifest(filename): + glbl = { + "buildconfig": buildconfig, + "defined": defined, + "ProcessSelector": ProcessSelector, + "BackgroundTasksSelector": BackgroundTasksSelector, + } + code = compile(open(filename).read(), filename, "exec") + exec(code, glbl) + return glbl + + +def main(fd, conf_file, template_file): + def open_output(filename): + return FileAvoidWrite(os.path.join(os.path.dirname(fd.name), filename)) + + conf = json.load(open(conf_file, "r")) + + deps = set() + + manifests = [] + for filename in conf["manifests"]: + deps.add(filename) + manifest = read_manifest(filename) + manifests.append(manifest) + manifest.setdefault("Priority", 50) + manifest["__filename__"] = filename + + manifests.sort(key=lambda man: (man["Priority"], man["__filename__"])) + + substs = gen_substs(manifests) + + def replacer(match): + return substs[match.group(1)] + + with open_output("StaticComponents.cpp") as fh: + with open(template_file, "r") as tfh: + template = tfh.read() + + fh.write(re.sub(r"//# @([a-zA-Z_]+)@\n", replacer, template)) + + with open_output("StaticComponentData.h") as fh: + fh.write( + """\ +/* -*- 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 StaticComponentData_h +#define StaticComponentData_h + +#include <stddef.h> + +namespace mozilla { +namespace xpcom { + +static constexpr size_t kStaticModuleCount = %(module_count)d; + +static constexpr size_t kContractCount = %(contract_count)d; + +static constexpr size_t kStaticCategoryCount = %(category_count)d; + +static constexpr size_t kModuleInitCount = %(init_count)d; + +static constexpr size_t kStaticProtocolHandlerCount = %(protocol_handler_count)d; + +static constexpr size_t kDefaultProtocolHandlerIndex = %(default_protocol_handler_idx)d; + +} // namespace xpcom +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("components.rs") as fh: + fh.write( + """\ +/// Unique IDs for each statically-registered module. +#[repr(u16)] +pub enum ModuleID { +%(module_ids)s +} + +%(component_getters_rust)s +""" + % substs + ) + + fd.write( + """\ +/* -*- 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 mozilla_Components_h +#define mozilla_Components_h + +#include "nsCOMPtr.h" + +struct nsID; + +#define NS_IMPL_COMPONENT_FACTORY(iface) \\ + template <> \\ + already_AddRefed<nsISupports> mozCreateComponent<iface>() + +template <typename T> +already_AddRefed<nsISupports> mozCreateComponent(); + +namespace mozilla { +namespace xpcom { + +enum class ModuleID : uint16_t { +%(module_ids)s +}; + +// May be added as a friend function to allow constructing services via +// private constructors and init methods. +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult); + +class MOZ_STACK_CLASS StaticModuleHelper : public nsCOMPtr_helper { + public: + StaticModuleHelper(ModuleID aId, nsresult* aErrorPtr) + : mId(aId), mErrorPtr(aErrorPtr) {} + + protected: + nsresult SetResult(nsresult aRv) const { + if (mErrorPtr) { + *mErrorPtr = aRv; + } + return aRv; + } + + ModuleID mId; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS GetServiceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class MOZ_STACK_CLASS CreateInstanceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class Components final { + public: + static const nsID& GetCID(ModuleID aID); +}; + +} // namespace xpcom + +namespace components { +%(component_getters)s +} // namespace components + +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("services.json") as fh: + fh.write(substs["js_services_json"]) + + return deps diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build new file mode 100644 index 0000000000..95ee64e985 --- /dev/null +++ b/xpcom/components/moz.build @@ -0,0 +1,85 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + "nsICategoryManager.idl", + "nsIClassInfo.idl", + "nsIComponentManager.idl", + "nsIComponentRegistrar.idl", + "nsIFactory.idl", + "nsIServiceManager.idl", +] + +XPIDL_MODULE = "xpcom_components" + +EXPORTS += [ + "nsCategoryCache.h", + "nsCategoryManagerUtils.h", + "nsComponentManagerUtils.h", + "nsServiceManagerUtils.h", +] + +EXPORTS.mozilla += [ + "GenericFactory.h", + "Module.h", + "ModuleUtils.h", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +if CONFIG["COMPILE_ENVIRONMENT"]: + EXPORTS.mozilla += [ + "!Components.h", + ] + + GeneratedFile( + "Components.h", + "StaticComponentData.h", + "StaticComponents.cpp", + "services.json", + "components.rs", + script="gen_static_components.py", + inputs=["!manifest-lists.json", "StaticComponents.cpp.in"], + ) + +UNIFIED_SOURCES += [ + "GenericFactory.cpp", + "ManifestParser.cpp", + "nsCategoryCache.cpp", + "nsCategoryManager.cpp", + "nsComponentManager.cpp", + "nsComponentManagerUtils.cpp", +] + +SOURCES += [ + "!StaticComponents.cpp", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!..", + "../base", + "../build", + "../ds", + "/chrome", + "/js/xpconnect/loader", + "/layout/build", + "/modules/libjar", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + if CONFIG["MOZ_ENABLE_DBUS"]: + CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] + +include("/ipc/chromium/chromium-config.mozbuild") + +PYTHON_UNITTEST_MANIFESTS += [ + "test/python.ini", +] diff --git a/xpcom/components/nsCategoryCache.cpp b/xpcom/components/nsCategoryCache.cpp new file mode 100644 index 0000000000..35f555fa51 --- /dev/null +++ b/xpcom/components/nsCategoryCache.cpp @@ -0,0 +1,162 @@ +/* -*- 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 "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" + +#include "nsXPCOMCID.h" + +#include "nsCategoryCache.h" + +using mozilla::SimpleEnumerator; + +nsCategoryObserver::nsCategoryObserver(const nsACString& aCategory) + : mCategory(aCategory), + mCallback(nullptr), + mClosure(nullptr), + mObserversRemoved(false) { + MOZ_ASSERT(NS_IsMainThread()); + // First, enumerate the currently existing entries + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsresult rv = + catMan->EnumerateCategory(aCategory, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) { + nsAutoCString entryValue; + categoryEntry->GetValue(entryValue); + + if (nsCOMPtr<nsISupports> service = do_GetService(entryValue.get())) { + nsAutoCString entryName; + categoryEntry->GetEntry(entryName); + + mHash.InsertOrUpdate(entryName, service); + } + } + + // Now, listen for changes + nsCOMPtr<nsIObserverService> serv = mozilla::services::GetObserverService(); + if (serv) { + serv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, false); + } +} + +nsCategoryObserver::~nsCategoryObserver() = default; + +NS_IMPL_ISUPPORTS(nsCategoryObserver, nsIObserver) + +void nsCategoryObserver::ListenerDied() { + MOZ_ASSERT(NS_IsMainThread()); + RemoveObservers(); + mCallback = nullptr; + mClosure = nullptr; +} + +void nsCategoryObserver::SetListener(void(aCallback)(void*), void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + mCallback = aCallback; + mClosure = aClosure; +} + +void nsCategoryObserver::RemoveObservers() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mObserversRemoved) { + return; + } + + if (mCallback) { + mCallback(mClosure); + } + + mObserversRemoved = true; + nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID); + } +} + +NS_IMETHODIMP +nsCategoryObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + mHash.Clear(); + RemoveObservers(); + + return NS_OK; + } + + if (!aData || + !nsDependentString(aData).Equals(NS_ConvertASCIItoUTF16(mCategory))) { + return NS_OK; + } + + nsAutoCString str; + nsCOMPtr<nsISupportsCString> strWrapper(do_QueryInterface(aSubject)); + if (strWrapper) { + strWrapper->GetData(str); + } + + if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID) == 0) { + // We may get an add notification even when we already have an entry. This + // is due to the notification happening asynchronously, so if the entry gets + // added and an nsCategoryObserver gets instantiated before events get + // processed, we'd get the notification for an existing entry. + // Do nothing in that case. + if (mHash.GetWeak(str)) { + return NS_OK; + } + + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return NS_OK; + } + + nsCString entryValue; + catMan->GetCategoryEntry(mCategory, str, entryValue); + + nsCOMPtr<nsISupports> service = do_GetService(entryValue.get()); + + if (service) { + mHash.InsertOrUpdate(str, service); + } + if (mCallback) { + mCallback(mClosure); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID) == 0) { + mHash.Remove(str); + if (mCallback) { + mCallback(mClosure); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID) == 0) { + mHash.Clear(); + if (mCallback) { + mCallback(mClosure); + } + } + return NS_OK; +} diff --git a/xpcom/components/nsCategoryCache.h b/xpcom/components/nsCategoryCache.h new file mode 100644 index 0000000000..f2d7ba32a3 --- /dev/null +++ b/xpcom/components/nsCategoryCache.h @@ -0,0 +1,123 @@ +/* -*- 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 nsCategoryCache_h_ +#define nsCategoryCache_h_ + +#include "mozilla/Attributes.h" + +#include "nsIObserver.h" + +#include "nsServiceManagerUtils.h" + +#include "nsCOMArray.h" +#include "nsInterfaceHashtable.h" + +#include "nsXPCOM.h" +#include "MainThreadUtils.h" + +class nsCategoryObserver final : public nsIObserver { + ~nsCategoryObserver(); + + public: + explicit nsCategoryObserver(const nsACString& aCategory); + + void ListenerDied(); + void SetListener(void(aCallback)(void*), void* aClosure); + nsInterfaceHashtable<nsCStringHashKey, nsISupports>& GetHash() { + return mHash; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + void RemoveObservers(); + + nsInterfaceHashtable<nsCStringHashKey, nsISupports> mHash; + nsCString mCategory; + void (*mCallback)(void*); + void* mClosure; + bool mObserversRemoved; +}; + +/** + * This is a helper class that caches services that are registered in a certain + * category. The intended usage is that a service stores a variable of type + * nsCategoryCache<nsIFoo> in a member variable, where nsIFoo is the interface + * that these services should implement. The constructor of this class should + * then get the name of the category. + */ +template <class T> +class nsCategoryCache final { + public: + explicit nsCategoryCache(const char* aCategory) : mCategoryName(aCategory) { + MOZ_ASSERT(NS_IsMainThread()); + } + ~nsCategoryCache() { + MOZ_ASSERT(NS_IsMainThread()); + if (mObserver) { + mObserver->ListenerDied(); + } + } + + void GetEntries(nsCOMArray<T>& aResult) { + MOZ_ASSERT(NS_IsMainThread()); + LazyInit(); + + AddEntries(aResult); + } + + /** + * This function returns an nsCOMArray of interface pointers to the cached + * category enries for the requested category. This was added in order to be + * used in call sites where the overhead of excessive allocations can be + * unacceptable. See bug 1360971 for an example. + */ + const nsCOMArray<T>& GetCachedEntries() { + MOZ_ASSERT(NS_IsMainThread()); + LazyInit(); + + if (mCachedEntries.IsEmpty()) { + AddEntries(mCachedEntries); + } + return mCachedEntries; + } + + private: + void LazyInit() { + // Lazy initialization, so that services in this category can't + // cause reentrant getService (bug 386376) + if (!mObserver) { + mObserver = new nsCategoryObserver(mCategoryName); + mObserver->SetListener(nsCategoryCache<T>::OnCategoryChanged, this); + } + } + + void AddEntries(nsCOMArray<T>& aResult) { + for (nsISupports* entry : mObserver->GetHash().Values()) { + nsCOMPtr<T> service = do_QueryInterface(entry); + if (service) { + aResult.AppendElement(service.forget()); + } + } + } + + static void OnCategoryChanged(void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + auto self = static_cast<nsCategoryCache<T>*>(aClosure); + self->mCachedEntries.Clear(); + } + + private: + // Not to be implemented + nsCategoryCache(const nsCategoryCache<T>&); + + nsCString mCategoryName; + RefPtr<nsCategoryObserver> mObserver; + nsCOMArray<T> mCachedEntries; +}; + +#endif diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp new file mode 100644 index 0000000000..a12fd510f5 --- /dev/null +++ b/xpcom/components/nsCategoryManager.cpp @@ -0,0 +1,692 @@ +/* -*- 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 "nsCategoryManager.h" +#include "nsCategoryManagerUtils.h" + +#include "prio.h" +#include "prlock.h" +#include "nsArrayEnumerator.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsClassHashtable.h" +#include "nsStringEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsPrintfCString.h" +#include "nsQuickSort.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ArenaAllocatorExtensions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "ManifestParser.h" +#include "nsSimpleEnumerator.h" + +using namespace mozilla; +class nsIComponentLoaderManager; + +/* + CategoryDatabase + contains 0 or more 1-1 mappings of string to Category + each Category contains 0 or more 1-1 mappings of string keys to string values + + In other words, the CategoryDatabase is a tree, whose root is a hashtable. + Internal nodes (or Categories) are hashtables. Leaf nodes are strings. + + The leaf strings are allocated in an arena, because we assume they're not + going to change much ;) +*/ + +// +// CategoryEnumerator class +// + +class CategoryEnumerator : public nsSimpleEnumerator, + private nsStringEnumeratorBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + + using nsStringEnumeratorBase::GetNext; + + const nsID& DefaultInterface() override { + return NS_GET_IID(nsISupportsCString); + } + + static CategoryEnumerator* Create( + nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable); + + protected: + CategoryEnumerator() + : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {} + + ~CategoryEnumerator() override { delete[] mArray; } + + const char** mArray; + uint32_t mCount; + uint32_t mSimpleCurItem; + uint32_t mStringCurItem; +}; + +NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator, nsSimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +NS_IMETHODIMP +CategoryEnumerator::HasMoreElements(bool* aResult) { + *aResult = (mSimpleCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsISupports** aResult) { + if (mSimpleCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]); + + *aResult = str; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::HasMore(bool* aResult) { + *aResult = (mStringCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsACString& aResult) { + if (mStringCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + aResult = nsDependentCString(mArray[mStringCurItem++]); + return NS_OK; +} + +CategoryEnumerator* CategoryEnumerator::Create( + nsClassHashtable<nsDepCharHashKey, CategoryNode>& aTable) { + auto* enumObj = new CategoryEnumerator(); + if (!enumObj) { + return nullptr; + } + + enumObj->mArray = new const char*[aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + for (const auto& entry : aTable) { + // if a category has no entries, we pretend it doesn't exist + CategoryNode* aNode = entry.GetWeak(); + if (aNode->Count()) { + enumObj->mArray[enumObj->mCount++] = entry.GetKey(); + } + } + + return enumObj; +} + +class CategoryEntry final : public nsICategoryEntry { + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYENTRY + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSISUPPORTSPRIMITIVE + + CategoryEntry(const char* aKey, const char* aValue) + : mKey(aKey), mValue(aValue) {} + + const char* Key() const { return mKey; } + + static CategoryEntry* Cast(nsICategoryEntry* aEntry) { + return static_cast<CategoryEntry*>(aEntry); + } + + private: + ~CategoryEntry() = default; + + const char* mKey; + const char* mValue; +}; + +NS_IMPL_ISUPPORTS(CategoryEntry, nsICategoryEntry, nsISupportsCString) + +nsresult CategoryEntry::ToString(char** aResult) { + *aResult = moz_xstrdup(mKey); + return NS_OK; +} + +nsresult CategoryEntry::GetType(uint16_t* aType) { + *aType = TYPE_CSTRING; + return NS_OK; +} + +nsresult CategoryEntry::GetData(nsACString& aData) { + aData = mKey; + return NS_OK; +} + +nsresult CategoryEntry::SetData(const nsACString& aData) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult CategoryEntry::GetEntry(nsACString& aEntry) { + aEntry = mKey; + return NS_OK; +} + +nsresult CategoryEntry::GetValue(nsACString& aValue) { + aValue = mValue; + return NS_OK; +} + +static nsresult CreateEntryEnumerator(nsTHashtable<CategoryLeaf>& aTable, + nsISimpleEnumerator** aResult) { + nsCOMArray<nsICategoryEntry> entries(aTable.Count()); + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + CategoryLeaf* leaf = iter.Get(); + if (leaf->value) { + entries.AppendElement(new CategoryEntry(leaf->GetKey(), leaf->value)); + } + } + + entries.Sort( + [](nsICategoryEntry* aA, nsICategoryEntry* aB, void*) { + return strcmp(CategoryEntry::Cast(aA)->Key(), + CategoryEntry::Cast(aB)->Key()); + }, + nullptr); + + return NS_NewArrayEnumerator(aResult, entries, NS_GET_IID(nsICategoryEntry)); +} + +// +// CategoryNode implementations +// + +CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) { + return new (aArena) CategoryNode(); +} + +CategoryNode::~CategoryNode() = default; + +void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) { + return aArena->Allocate(aSize, mozilla::fallible); +} + +static inline const char* MaybeStrdup(const nsACString& aStr, + CategoryAllocator* aArena) { + if (aStr.IsLiteral()) { + return aStr.BeginReading(); + } + return ArenaStrdup(PromiseFlatCString(aStr).get(), *aArena); +} + +nsresult CategoryNode::GetLeaf(const nsACString& aEntryName, + nsACString& aResult) { + MutexAutoLock lock(mLock); + nsresult rv = NS_ERROR_NOT_AVAILABLE; + CategoryLeaf* ent = mTable.GetEntry(PromiseFlatCString(aEntryName).get()); + + if (ent && ent->value) { + aResult.Assign(ent->value); + return NS_OK; + } + + return rv; +} + +nsresult CategoryNode::AddLeaf(const nsACString& aEntryName, + const nsACString& aValue, bool aReplace, + nsACString& aResult, CategoryAllocator* aArena) { + aResult.SetIsVoid(true); + + auto entryName = PromiseFlatCString(aEntryName); + + MutexAutoLock lock(mLock); + CategoryLeaf* leaf = mTable.GetEntry(entryName.get()); + + if (!leaf) { + leaf = mTable.PutEntry(MaybeStrdup(aEntryName, aArena)); + if (!leaf) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (leaf->value && !aReplace) { + return NS_ERROR_INVALID_ARG; + } + + if (leaf->value) { + aResult.AssignLiteral(leaf->value, strlen(leaf->value)); + } else { + aResult.SetIsVoid(true); + } + leaf->value = MaybeStrdup(aValue, aArena); + return NS_OK; +} + +void CategoryNode::DeleteLeaf(const nsACString& aEntryName) { + // we don't throw any errors, because it normally doesn't matter + // and it makes JS a lot cleaner + MutexAutoLock lock(mLock); + + // we can just remove the entire hash entry without introspection + mTable.RemoveEntry(PromiseFlatCString(aEntryName).get()); +} + +nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) { + MutexAutoLock lock(mLock); + return CreateEntryEnumerator(mTable, aResult); +} + +size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + // We don't measure the strings pointed to by the entries because the + // pointers are non-owning. + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +// +// nsCategoryManager implementations +// + +NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager, + nsIMemoryReporter) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::Release() { return 1; } + +nsCategoryManager* nsCategoryManager::gCategoryManager; + +/* static */ +nsCategoryManager* nsCategoryManager::GetSingleton() { + if (!gCategoryManager) { + gCategoryManager = new nsCategoryManager(); + } + return gCategoryManager; +} + +/* static */ +void nsCategoryManager::Destroy() { + // The nsMemoryReporterManager gets destroyed before the nsCategoryManager, + // so we don't need to unregister the nsCategoryManager as a memory reporter. + // In debug builds we assert that unregistering fails, as a way (imperfect + // but better than nothing) of testing the "destroyed before" part. + MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager))); + + delete gCategoryManager; + gCategoryManager = nullptr; +} + +nsresult nsCategoryManager::Create(REFNSIID aIID, void** aResult) { + return GetSingleton()->QueryInterface(aIID, aResult); +} + +nsCategoryManager::nsCategoryManager() + : mArena(), + mTable(), + mLock("nsCategoryManager"), + mSuppressNotifications(false) {} + +void nsCategoryManager::InitMemoryReporter() { + RegisterWeakMemoryReporter(this); +} + +nsCategoryManager::~nsCategoryManager() { + // the hashtable contains entries that must be deleted before the arena is + // destroyed, or else you will have PRLocks undestroyed and other Really + // Bad Stuff (TM) + mTable.Clear(); +} + +inline CategoryNode* nsCategoryManager::get_category(const nsACString& aName) { + CategoryNode* node; + if (!mTable.Get(PromiseFlatCString(aName).get(), &node)) { + return nullptr; + } + return node; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf) + +NS_IMETHODIMP +nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(CategoryManagerMallocSizeOf), + "Memory used for the XPCOM category manager."); + + return NS_OK; +} + +size_t nsCategoryManager::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + + n += mArena.SizeOfExcludingThis(aMallocSizeOf); + + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mTable.Values()) { + // We don't measure the key string because it's a non-owning pointer. + n += data->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +namespace { + +class CategoryNotificationRunnable : public Runnable { + public: + CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic, + const nsACString& aData) + : Runnable("CategoryNotificationRunnable"), + mSubject(aSubject), + mTopic(aTopic), + mData(aData) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsISupports> mSubject; + const char* mTopic; + NS_ConvertUTF8toUTF16 mData; +}; + +NS_IMETHODIMP +CategoryNotificationRunnable::Run() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(mSubject, mTopic, mData.get()); + } + + return NS_OK; +} + +} // namespace + +void nsCategoryManager::NotifyObservers(const char* aTopic, + const nsACString& aCategoryName, + const nsACString& aEntryName) { + if (mSuppressNotifications) { + return; + } + + RefPtr<CategoryNotificationRunnable> r; + + if (aEntryName.Length()) { + nsCOMPtr<nsISupportsCString> entry = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (!entry) { + return; + } + + nsresult rv = entry->SetData(aEntryName); + if (NS_FAILED(rv)) { + return; + } + + r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName); + } else { + r = new CategoryNotificationRunnable( + NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName); + } + + NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +nsCategoryManager::GetCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + nsACString& aResult) { + nsresult status = NS_ERROR_NOT_AVAILABLE; + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + status = category->GetLeaf(aEntryName, aResult); + } + + return status; +} + +NS_IMETHODIMP +nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, bool aPersist, + bool aReplace, nsACString& aResult) { + if (aPersist) { + NS_ERROR("Category manager doesn't support persistence."); + return NS_ERROR_INVALID_ARG; + } + + AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult); + return NS_OK; +} + +void nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, + bool aReplace, nsACString& aOldValue) { + aOldValue.SetIsVoid(true); + + // Before we can insert a new entry, we'll need to + // find the |CategoryNode| to put it in... + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + + if (!category) { + // That category doesn't exist yet; let's make it. + category = mTable + .InsertOrUpdate( + MaybeStrdup(aCategoryName, &mArena), + UniquePtr<CategoryNode>{CategoryNode::Create(&mArena)}) + .get(); + } + } + + if (!category) { + return; + } + + nsresult rv = + category->AddLeaf(aEntryName, aValue, aReplace, aOldValue, &mArena); + + if (NS_SUCCEEDED(rv)) { + if (!aOldValue.IsEmpty()) { + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, + aCategoryName, aEntryName); + } + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName, + aEntryName); + } +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + bool aDontPersist) { + /* + Note: no errors are reported since failure to delete + probably won't hurt you, and returning errors seriously + inconveniences JS clients + */ + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->DeleteLeaf(aEntryName); + + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName, + aEntryName); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategory(const nsACString& aCategoryName) { + // the categories are arena-allocated, so we don't + // actually delete them. We just remove all of the + // leaf nodes. + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->Clear(); + NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName, + VoidCString()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategory(const nsACString& aCategoryName, + nsISimpleEnumerator** aResult) { + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (!category) { + return NS_NewEmptyEnumerator(aResult); + } + + return category->Enumerate(aResult); +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mLock); + CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable); + + if (!enumObj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *aResult = enumObj; + NS_ADDREF(*aResult); + return NS_OK; +} + +struct writecat_struct { + PRFileDesc* fd; + bool success; +}; + +nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) { + mSuppressNotifications = aSuppress; + return NS_OK; +} + +/* + * CreateServicesFromCategory() + * + * Given a category, this convenience functions enumerates the category and + * creates a service of every CID or ContractID registered under the category. + * If observerTopic is non null and the service implements nsIObserver, + * this will attempt to notify the observer with the origin, observerTopic + * string as parameter. + */ +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData) { + nsresult rv; + + nsCOMPtr<nsICategoryManager> categoryManager = + do_GetService("@mozilla.org/categorymanager;1"); + if (!categoryManager) { + return; + } + + nsDependentCString category(aCategory); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) { + // From here on just skip any error we get. + nsAutoCString entryString; + categoryEntry->GetEntry(entryString); + + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + nsCOMPtr<nsISupports> instance = do_GetService(contractID.get()); + if (!instance) { + LogMessage( + "While creating services from category '%s', could not create " + "service for entry '%s', contract ID '%s'", + aCategory, entryString.get(), contractID.get()); + continue; + } + + if (aObserverTopic) { + // try an observer, if it implements it. + nsCOMPtr<nsIObserver> observer = do_QueryInterface(instance); + if (observer) { + nsPrintfCString profilerStr("%s (%s)", aObserverTopic, + entryString.get()); + AUTO_PROFILER_MARKER_TEXT("Category observer notification", OTHER, + MarkerStack::Capture(), profilerStr); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "Category observer notification -", OTHER, profilerStr); + + observer->Observe(aOrigin, aObserverTopic, + aObserverData ? aObserverData : u""); + } else { + LogMessage( + "While creating services from category '%s', service for entry " + "'%s', contract ID '%s' does not implement nsIObserver.", + aCategory, entryString.get(), contractID.get()); + } + } + } +} diff --git a/xpcom/components/nsCategoryManager.h b/xpcom/components/nsCategoryManager.h new file mode 100644 index 0000000000..e15760768d --- /dev/null +++ b/xpcom/components/nsCategoryManager.h @@ -0,0 +1,145 @@ +/* -*- 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 NSCATEGORYMANAGER_H +#define NSCATEGORYMANAGER_H + +#include "prio.h" +#include "nsClassHashtable.h" +#include "nsICategoryManager.h" +#include "nsIMemoryReporter.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" + +class nsIMemoryReporter; + +typedef mozilla::ArenaAllocator<1024 * 8, 8> CategoryAllocator; + +/* 16d222a6-1dd2-11b2-b693-f38b02c021b2 */ +#define NS_CATEGORYMANAGER_CID \ + { \ + 0x16d222a6, 0x1dd2, 0x11b2, { \ + 0xb6, 0x93, 0xf3, 0x8b, 0x02, 0xc0, 0x21, 0xb2 \ + } \ + } + +/** + * a "leaf-node", managed by the nsCategoryNode hashtable. + * + * we need to keep a "persistent value" (which will be written to the registry) + * and a non-persistent value (for the current runtime): these are usually + * the same, except when aPersist==false. The strings are permanently arena- + * allocated, and will never go away. + */ +class CategoryLeaf : public nsDepCharHashKey { + public: + explicit CategoryLeaf(const char* aKey) + : nsDepCharHashKey(aKey), value(nullptr) {} + const char* value; +}; + +/** + * CategoryNode keeps a hashtable of its entries. + * the CategoryNode itself is permanently allocated in + * the arena. + */ +class CategoryNode { + public: + nsresult GetLeaf(const nsACString& aEntryName, nsACString& aResult); + + nsresult AddLeaf(const nsACString& aEntryName, const nsACString& aValue, + bool aReplace, nsACString& aResult, + CategoryAllocator* aArena); + + void DeleteLeaf(const nsACString& aEntryName); + + void Clear() { + mozilla::MutexAutoLock lock(mLock); + mTable.Clear(); + } + + uint32_t Count() { + mozilla::MutexAutoLock lock(mLock); + uint32_t tCount = mTable.Count(); + return tCount; + } + + nsresult Enumerate(nsISimpleEnumerator** aResult); + + // CategoryNode is arena-allocated, with the strings + static CategoryNode* Create(CategoryAllocator* aArena); + ~CategoryNode(); + void operator delete(void*) {} + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + private: + CategoryNode() : mLock("CategoryLeaf") {} + + void* operator new(size_t aSize, CategoryAllocator* aArena); + + nsTHashtable<CategoryLeaf> mTable; + mozilla::Mutex mLock MOZ_UNANNOTATED; +}; + +/** + * The main implementation of nsICategoryManager. + * + * This implementation is thread-safe. + */ +class nsCategoryManager final : public nsICategoryManager, + public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYMANAGER + NS_DECL_NSIMEMORYREPORTER + + /** + * Suppress or unsuppress notifications of category changes to the + * observer service. This is to be used by nsComponentManagerImpl + * on startup while reading the stored category list. + */ + nsresult SuppressNotifications(bool aSuppress); + + void AddCategoryEntry(const nsACString& aCategory, const nsACString& aKey, + const nsACString& aValue, bool aReplace, + nsACString& aOldValue); + + void AddCategoryEntry(const nsACString& aCategory, const nsACString& aKey, + const nsACString& aValue, bool aReplace = true) { + nsCString oldValue; + return AddCategoryEntry(aCategory, aKey, aValue, aReplace, oldValue); + } + + static nsresult Create(REFNSIID aIID, void** aResult); + void InitMemoryReporter(); + + static nsCategoryManager* GetSingleton(); + static void Destroy(); + + private: + static nsCategoryManager* gCategoryManager; + + nsCategoryManager(); + ~nsCategoryManager(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + CategoryNode* get_category(const nsACString& aName); + void NotifyObservers( + const char* aTopic, + const nsACString& aCategoryName, // must be a static string + const nsACString& aEntryName); + + CategoryAllocator mArena; + nsClassHashtable<nsDepCharHashKey, CategoryNode> mTable; + mozilla::Mutex mLock MOZ_UNANNOTATED; + bool mSuppressNotifications; +}; + +#endif diff --git a/xpcom/components/nsCategoryManagerUtils.h b/xpcom/components/nsCategoryManagerUtils.h new file mode 100644 index 0000000000..30db31bab6 --- /dev/null +++ b/xpcom/components/nsCategoryManagerUtils.h @@ -0,0 +1,14 @@ +/* -*- 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 nsCategoryManagerUtils_h__ +#define nsCategoryManagerUtils_h__ + +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData = nullptr); + +#endif diff --git a/xpcom/components/nsComponentManager.cpp b/xpcom/components/nsComponentManager.cpp new file mode 100644 index 0000000000..35954b1702 --- /dev/null +++ b/xpcom/components/nsComponentManager.cpp @@ -0,0 +1,1575 @@ +/* -*- 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 <stdlib.h> +#include "nscore.h" +#include "nsISupports.h" +#include "nspr.h" +#include "nsCRT.h" // for atoll + +#include "StaticComponents.h" + +#include "nsCategoryManager.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsLayoutModule.h" +#include "mozilla/MemoryReporting.h" +#include "nsIObserverService.h" +#include "nsIStringEnumerator.h" +#include "nsXPCOM.h" +#include "nsXPCOMPrivate.h" +#include "nsISupportsPrimitives.h" +#include "nsLocalFile.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "prcmon.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "prthread.h" +#include "private/pprthred.h" +#include "nsTArray.h" +#include "prio.h" +#include "ManifestParser.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" + +#include "mozilla/GenericFactory.h" +#include "nsSupportsPrimitives.h" +#include "nsArray.h" +#include "nsIMutableArray.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FileUtils.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/URLPreloader.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" + +#include <new> // for placement new + +#include "mozilla/Omnijar.h" + +#include "mozilla/Logging.h" +#include "LogModulePrefWatcher.h" + +#ifdef MOZ_MEMORY +# include "mozmemory.h" +#endif + +using namespace mozilla; +using namespace mozilla::xpcom; + +static LazyLogModule nsComponentManagerLog("nsComponentManager"); + +#if 0 +# define SHOW_DENIED_ON_SHUTDOWN +# define SHOW_CI_ON_EXISTING_SERVICE +#endif + +namespace { + +class AutoIDString : public nsAutoCStringN<NSID_LENGTH> { + public: + explicit AutoIDString(const nsID& aID) { + SetLength(NSID_LENGTH - 1); + aID.ToProvidedString( + *reinterpret_cast<char(*)[NSID_LENGTH]>(BeginWriting())); + } +}; + +} // namespace + +namespace mozilla { +namespace xpcom { + +using ProcessSelector = Module::ProcessSelector; + +// Note: These must be kept in sync with the ProcessSelector definition in +// Module.h. +bool ProcessSelectorMatches(ProcessSelector aSelector) { + GeckoProcessType type = XRE_GetProcessType(); + if (type == GeckoProcessType_GPU) { + return !!(aSelector & Module::ALLOW_IN_GPU_PROCESS); + } + + if (type == GeckoProcessType_RDD) { + return !!(aSelector & Module::ALLOW_IN_RDD_PROCESS); + } + + if (type == GeckoProcessType_Socket) { + return !!(aSelector & (Module::ALLOW_IN_SOCKET_PROCESS)); + } + + if (type == GeckoProcessType_VR) { + return !!(aSelector & Module::ALLOW_IN_VR_PROCESS); + } + + if (type == GeckoProcessType_Utility) { + return !!(aSelector & Module::ALLOW_IN_UTILITY_PROCESS); + } + + // Only allow XPCOM modules which can be loaded in all processes to be loaded + // in the IPDLUnitTest process. + if (type == GeckoProcessType_IPDLUnitTest) { + return size_t(aSelector) == Module::kMaxProcessSelector; + } + + if (aSelector & Module::MAIN_PROCESS_ONLY) { + return type == GeckoProcessType_Default; + } + if (aSelector & Module::CONTENT_PROCESS_ONLY) { + return type == GeckoProcessType_Content; + } + return true; +} + +static bool gProcessMatchTable[Module::kMaxProcessSelector + 1]; + +bool FastProcessSelectorMatches(ProcessSelector aSelector) { + return gProcessMatchTable[size_t(aSelector)]; +} + +} // namespace xpcom +} // namespace mozilla + +namespace { + +/** + * A wrapper simple wrapper class, which can hold either a dynamic + * nsFactoryEntry instance, or a static StaticModule entry, and transparently + * forwards method calls to the wrapped object. + * + * This allows the same code to work with either static or dynamic modules + * without caring about the difference. + */ +class MOZ_STACK_CLASS EntryWrapper final { + public: + explicit EntryWrapper(nsFactoryEntry* aEntry) : mEntry(aEntry) {} + + explicit EntryWrapper(const StaticModule* aEntry) : mEntry(aEntry) {} + +#define MATCH(type, ifFactory, ifStatic) \ + struct Matcher { \ + type operator()(nsFactoryEntry* entry) { ifFactory; } \ + type operator()(const StaticModule* entry) { ifStatic; } \ + }; \ + return mEntry.match((Matcher())) + + const nsID& CID() { + MATCH(const nsID&, return entry->mCID, return entry->CID()); + } + + already_AddRefed<nsIFactory> GetFactory() { + MATCH(already_AddRefed<nsIFactory>, return entry->GetFactory(), + return entry->GetFactory()); + } + + /** + * Creates an instance of the underlying component. This should be used in + * preference to GetFactory()->CreateInstance() where appropriate, since it + * side-steps the necessity of creating a nsIFactory instance for static + * modules. + */ + nsresult CreateInstance(const nsIID& aIID, void** aResult) { + if (mEntry.is<nsFactoryEntry*>()) { + return mEntry.as<nsFactoryEntry*>()->CreateInstance(aIID, aResult); + } + return mEntry.as<const StaticModule*>()->CreateInstance(aIID, aResult); + } + + /** + * Returns the cached service instance for this entry, if any. This should + * only be accessed while mLock is held. + */ + nsISupports* ServiceInstance() { + MATCH(nsISupports*, return entry->mServiceObject, + return entry->ServiceInstance()); + } + void SetServiceInstance(already_AddRefed<nsISupports> aInst) { + if (mEntry.is<nsFactoryEntry*>()) { + mEntry.as<nsFactoryEntry*>()->mServiceObject = aInst; + } else { + return mEntry.as<const StaticModule*>()->SetServiceInstance( + std::move(aInst)); + } + } + + /** + * Returns the description string for the module this entry belongs to. + * Currently always returns "<unknown module>". + */ + nsCString ModuleDescription() { return "<unknown module>"_ns; } + + private: + Variant<nsFactoryEntry*, const StaticModule*> mEntry; +}; + +} // namespace + +// this is safe to call during InitXPCOM +static already_AddRefed<nsIFile> GetLocationFromDirectoryService( + const char* aProp) { + nsCOMPtr<nsIProperties> directoryService; + nsDirectoryService::Create(NS_GET_IID(nsIProperties), + getter_AddRefs(directoryService)); + + if (!directoryService) { + return nullptr; + } + + nsCOMPtr<nsIFile> file; + nsresult rv = + directoryService->Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return file.forget(); +} + +static already_AddRefed<nsIFile> CloneAndAppend(nsIFile* aBase, + const nsACString& aAppend) { + nsCOMPtr<nsIFile> f; + aBase->Clone(getter_AddRefs(f)); + if (!f) { + return nullptr; + } + + f->AppendNative(aAppend); + return f.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsComponentManagerImpl +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsComponentManagerImpl::Create(REFNSIID aIID, void** aResult) { + if (!gComponentManager) { + return NS_ERROR_FAILURE; + } + + return gComponentManager->QueryInterface(aIID, aResult); +} + +static const int CONTRACTID_HASHTABLE_INITIAL_LENGTH = 8; + +nsComponentManagerImpl::nsComponentManagerImpl() + : mFactories(CONTRACTID_HASHTABLE_INITIAL_LENGTH), + mContractIDs(CONTRACTID_HASHTABLE_INITIAL_LENGTH), + mLock("nsComponentManagerImpl.mLock"), + mStatus(NOT_INITIALIZED) {} + +nsTArray<nsComponentManagerImpl::ComponentLocation>* + nsComponentManagerImpl::sModuleLocations; + +/* static */ +void nsComponentManagerImpl::InitializeModuleLocations() { + if (sModuleLocations) { + return; + } + + sModuleLocations = new nsTArray<ComponentLocation>; +} + +nsresult nsComponentManagerImpl::Init() { + { + gProcessMatchTable[size_t(ProcessSelector::ANY_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ANY_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::MAIN_PROCESS_ONLY)] = + ProcessSelectorMatches(ProcessSelector::MAIN_PROCESS_ONLY); + gProcessMatchTable[size_t(ProcessSelector::CONTENT_PROCESS_ONLY)] = + ProcessSelectorMatches(ProcessSelector::CONTENT_PROCESS_ONLY); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_VR_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_VR_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_SOCKET_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_SOCKET_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_RDD_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_RDD_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_AND_MAIN_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_AND_MAIN_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_AND_VR_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_AND_VR_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_RDD_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_RDD_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS); + } + + MOZ_ASSERT(NOT_INITIALIZED == mStatus); + + nsCOMPtr<nsIFile> greDir = GetLocationFromDirectoryService(NS_GRE_DIR); + nsCOMPtr<nsIFile> appDir = + GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR); + + nsCategoryManager::GetSingleton()->SuppressNotifications(true); + + auto* catMan = nsCategoryManager::GetSingleton(); + for (const auto& cat : gStaticCategories) { + for (const auto& entry : cat) { + if (entry.Active()) { + catMan->AddCategoryEntry(cat.Name(), entry.Entry(), entry.Value()); + } + } + } + + bool loadChromeManifests; + switch (XRE_GetProcessType()) { + // We are going to assume that only a select few (see below) process types + // want to load chrome manifests, and that any new process types will not + // want to load them, because they're not going to be executing JS. + case GeckoProcessType_RemoteSandboxBroker: + default: + loadChromeManifests = false; + break; + + // XXX The check this code replaced implicitly let through all of these + // process types, but presumably only the default (parent) and content + // processes really need chrome manifests...? + case GeckoProcessType_Default: + case GeckoProcessType_Content: + case GeckoProcessType_GMPlugin: + loadChromeManifests = true; + break; + } + + if (loadChromeManifests) { + // This needs to be called very early, before anything in nsLayoutModule is + // used, and before any calls are made into the JS engine. + nsLayoutModuleInitialize(); + + mJSLoaderReady = true; + + // The overall order in which chrome.manifests are expected to be treated + // is the following: + // - greDir's omni.ja or greDir + // - appDir's omni.ja or appDir + + InitializeModuleLocations(); + ComponentLocation* cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + RefPtr<nsZipArchive> greOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (greOmnijar) { + cl->location.Init(greOmnijar, "chrome.manifest"); + } else { + nsCOMPtr<nsIFile> lf = CloneAndAppend(greDir, "chrome.manifest"_ns); + cl->location.Init(lf); + } + + RefPtr<nsZipArchive> appOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); + if (appOmnijar) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + cl->location.Init(appOmnijar, "chrome.manifest"); + } else { + bool equals = false; + appDir->Equals(greDir, &equals); + if (!equals) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + nsCOMPtr<nsIFile> lf = CloneAndAppend(appDir, "chrome.manifest"_ns); + cl->location.Init(lf); + } + } + + RereadChromeManifests(false); + } + + nsCategoryManager::GetSingleton()->SuppressNotifications(false); + + RegisterWeakMemoryReporter(this); + + // NB: The logging preference watcher needs to be registered late enough in + // startup that it's okay to use the preference system, but also as soon as + // possible so that log modules enabled via preferences are turned on as + // early as possible. + // + // We can't initialize the preference watcher when the log module manager is + // initialized, as a number of things attempt to start logging before the + // preference system is initialized. + // + // The preference system is registered as a component so at this point during + // component manager initialization we know it is setup and we can register + // for notifications. + LogModulePrefWatcher::RegisterPrefWatcher(); + + // Unfortunately, we can't register the nsCategoryManager memory reporter + // in its constructor (which is triggered by the GetSingleton() call + // above) because the memory reporter manager isn't initialized at that + // point. So we wait until now. + nsCategoryManager::GetSingleton()->InitMemoryReporter(); + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Initialized.")); + + mStatus = NORMAL; + + MOZ_ASSERT(!XRE_IsContentProcess() || + CONTRACTID_HASHTABLE_INITIAL_LENGTH <= 8 || + mFactories.Count() > CONTRACTID_HASHTABLE_INITIAL_LENGTH / 3, + "Initial component hashtable size is too large"); + + return NS_OK; +} + +template <typename T> +static void AssertNotMallocAllocated(T* aPtr) { +#if defined(DEBUG) && defined(MOZ_MEMORY) + jemalloc_ptr_info_t info; + jemalloc_ptr_info((void*)aPtr, &info); + MOZ_ASSERT(info.tag == TagUnknown); +#endif +} + +template <typename T> +static void AssertNotStackAllocated(T* aPtr) { + // On all of our supported platforms, the stack grows down. Any address + // located below the address of our argument is therefore guaranteed not to be + // stack-allocated by the caller. + // + // For addresses above our argument, things get trickier. The main thread + // stack is traditionally placed at the top of the program's address space, + // but that is becoming less reliable as more and more systems adopt address + // space layout randomization strategies, so we have to guess how much space + // above our argument pointer we need to care about. + // + // On most systems, we're guaranteed at least several KiB at the top of each + // stack for TLS. We'd probably be safe assuming at least 4KiB in the stack + // segment above our argument address, but safer is... well, safer. + // + // For threads with huge stacks, it's theoretically possible that we could + // wind up being passed a stack-allocated string from farther up the stack, + // but this is a best-effort thing, so we'll assume we only care about the + // immediate caller. For that case, max 2KiB per stack frame is probably a + // reasonable guess most of the time, and is less than the ~4KiB that we + // expect for TLS, so go with that to avoid the risk of bumping into heap + // data just above the stack. +#ifdef DEBUG + static constexpr size_t kFuzz = 2048; + + MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr) || + uintptr_t(aPtr) > uintptr_t(&aPtr) + kFuzz); +#endif +} + +static void DoRegisterManifest(NSLocationType aType, FileLocation& aFile, + bool aChromeOnly) { + auto result = URLPreloader::Read(aFile); + if (result.isOk()) { + nsCString buf(result.unwrap()); + ParseManifest(aType, aFile, buf.BeginWriting(), aChromeOnly); + } else if (NS_BOOTSTRAPPED_LOCATION != aType) { + nsCString uri; + aFile.GetURIString(uri); + LogMessage("Could not read chrome manifest '%s'.", uri.get()); + } +} + +void nsComponentManagerImpl::RegisterManifest(NSLocationType aType, + FileLocation& aFile, + bool aChromeOnly) { + DoRegisterManifest(aType, aFile, aChromeOnly); +} + +void nsComponentManagerImpl::ManifestManifest(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) { + char* file = aArgv[0]; + FileLocation f(aCx.mFile, file); + RegisterManifest(aCx.mType, f, aCx.mChromeOnly); +} + +void nsComponentManagerImpl::ManifestCategory(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) { + char* category = aArgv[0]; + char* key = aArgv[1]; + char* value = aArgv[2]; + + nsCategoryManager::GetSingleton()->AddCategoryEntry( + nsDependentCString(category), nsDependentCString(key), + nsDependentCString(value)); +} + +void nsComponentManagerImpl::RereadChromeManifests(bool aChromeOnly) { + for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) { + ComponentLocation& l = sModuleLocations->ElementAt(i); + RegisterManifest(l.type, l.location, aChromeOnly); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "chrome-manifests-loaded", nullptr); + } +} + +nsresult nsComponentManagerImpl::Shutdown(void) { + MOZ_ASSERT(NORMAL == mStatus); + + mStatus = SHUTDOWN_IN_PROGRESS; + + // Shutdown the component manager + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Beginning Shutdown.")); + + UnregisterWeakMemoryReporter(this); + + // Release all cached factories + mContractIDs.Clear(); + mFactories.Clear(); // XXX release the objects, don't just clear + + StaticComponents::Shutdown(); + + delete sModuleLocations; + + mStatus = SHUTDOWN_COMPLETE; + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Shutdown complete.")); + + return NS_OK; +} + +nsComponentManagerImpl::~nsComponentManagerImpl() { + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Beginning destruction.")); + + if (SHUTDOWN_COMPLETE != mStatus) { + Shutdown(); + } + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Destroyed.")); +} + +NS_IMPL_ISUPPORTS(nsComponentManagerImpl, nsIComponentManager, + nsIServiceManager, nsIComponentRegistrar, + nsISupportsWeakReference, nsIInterfaceRequestor, + nsIMemoryReporter) + +nsresult nsComponentManagerImpl::GetInterface(const nsIID& aUuid, + void** aResult) { + NS_WARNING("This isn't supported"); + // fall through to QI as anything QIable is a superset of what can be + // got via the GetInterface() + return QueryInterface(aUuid, aResult); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByCID(const nsID& aCID) { + return LookupByCID(MonitorAutoLock(mLock), aCID); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByCID(const MonitorAutoLock&, + const nsID& aCID) { + if (const StaticModule* module = StaticComponents::LookupByCID(aCID)) { + return Some(EntryWrapper(module)); + } + if (nsFactoryEntry* entry = mFactories.Get(&aCID)) { + return Some(EntryWrapper(entry)); + } + return Nothing(); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByContractID( + const nsACString& aContractID) { + return LookupByContractID(MonitorAutoLock(mLock), aContractID); +} + +Maybe<EntryWrapper> nsComponentManagerImpl::LookupByContractID( + const MonitorAutoLock&, const nsACString& aContractID) { + if (const StaticModule* module = + StaticComponents::LookupByContractID(aContractID)) { + return Some(EntryWrapper(module)); + } + if (nsFactoryEntry* entry = mContractIDs.Get(aContractID)) { + // UnregisterFactory might have left a stale nsFactoryEntry in + // mContractIDs, so we should check to see whether this entry has + // anything useful. + if (entry->mFactory || entry->mServiceObject) { + return Some(EntryWrapper(entry)); + } + } + return Nothing(); +} + +already_AddRefed<nsIFactory> nsComponentManagerImpl::FindFactory( + const nsCID& aClass) { + Maybe<EntryWrapper> e = LookupByCID(aClass); + if (!e) { + return nullptr; + } + + return e->GetFactory(); +} + +already_AddRefed<nsIFactory> nsComponentManagerImpl::FindFactory( + const char* aContractID, uint32_t aContractIDLen) { + Maybe<EntryWrapper> entry = + LookupByContractID(nsDependentCString(aContractID, aContractIDLen)); + if (!entry) { + return nullptr; + } + + return entry->GetFactory(); +} + +/** + * GetClassObject() + * + * Given a classID, this finds the singleton ClassObject that implements the + * CID. Returns an interface of type aIID off the singleton classobject. + */ +NS_IMETHODIMP +nsComponentManagerImpl::GetClassObject(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + nsresult rv; + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Debug)) { + char buf[NSID_LENGTH]; + aClass.ToProvidedString(buf); + PR_LogPrint("nsComponentManager: GetClassObject(%s)", buf); + } + + MOZ_ASSERT(aResult != nullptr); + + nsCOMPtr<nsIFactory> factory = FindFactory(aClass); + if (!factory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + rv = factory->QueryInterface(aIID, aResult); + + MOZ_LOG( + nsComponentManagerLog, LogLevel::Warning, + ("\t\tGetClassObject() %s", NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetClassObjectByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aContractID)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: GetClassObjectByContractID(%s)", aContractID)); + + nsCOMPtr<nsIFactory> factory = FindFactory(aContractID, strlen(aContractID)); + if (!factory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + rv = factory->QueryInterface(aIID, aResult); + + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("\t\tGetClassObjectByContractID() %s", + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +/** + * CreateInstance() + * + * Create an instance of an object that implements an interface and belongs + * to the implementation aClass using the factory. The factory is immediately + * released and not held onto for any longer. + */ +NS_IMETHODIMP +nsComponentManagerImpl::CreateInstance(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in creating a component during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Creating new instance on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + Maybe<EntryWrapper> entry = LookupByCID(aClass); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->ServiceInstance()) { + nsAutoCString message; + message = "You are calling CreateInstance \""_ns + AutoIDString(aClass) + + "\" when a service for this CID already exists!"_ns; + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr<nsIFactory> factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Warning)) { + char buf[NSID_LENGTH]; + aClass.ToProvidedString(buf); + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstance(%s) %s", buf, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + } + + return rv; +} + +/** + * CreateInstanceByContractID() + * + * A variant of CreateInstance() that creates an instance of the object that + * implements the interface aIID and whose implementation has a contractID + * aContractID. + * + * This is only a convenience routine that turns around can calls the + * CreateInstance() with classid and iid. + */ +NS_IMETHODIMP +nsComponentManagerImpl::CreateInstanceByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + if (NS_WARN_IF(!aContractID)) { + return NS_ERROR_INVALID_ARG; + } + + // test this first, since there's no point in creating a component during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Creating new instance on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + Maybe<EntryWrapper> entry = + LookupByContractID(nsDependentCString(aContractID)); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->ServiceInstance()) { + nsAutoCString message; + message = + "You are calling CreateInstance \""_ns + + nsDependentCString(aContractID) + + nsLiteralCString( + "\" when a service for this CID already exists! " + "Add it to abusedContracts to track down the service consumer."); + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr<nsIFactory> factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstanceByContractID(%s) %s", aContractID, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +nsresult nsComponentManagerImpl::FreeServices() { + NS_ASSERTION(gXPCOMShuttingDown, + "Must be shutting down in order to free all services"); + + if (!gXPCOMShuttingDown) { + return NS_ERROR_FAILURE; + } + + for (nsFactoryEntry* entry : mFactories.Values()) { + entry->mFactory = nullptr; + entry->mServiceObject = nullptr; + } + + for (const auto& module : gStaticModules) { + module.SetServiceInstance(nullptr); + } + + return NS_OK; +} + +// This should only ever be called within the monitor! +nsComponentManagerImpl::PendingServiceInfo* +nsComponentManagerImpl::AddPendingService(const nsCID& aServiceCID, + PRThread* aThread) { + PendingServiceInfo* newInfo = mPendingServices.AppendElement(); + if (newInfo) { + newInfo->cid = &aServiceCID; + newInfo->thread = aThread; + } + return newInfo; +} + +// This should only ever be called within the monitor! +void nsComponentManagerImpl::RemovePendingService(MonitorAutoLock& aLock, + const nsCID& aServiceCID) { + uint32_t pendingCount = mPendingServices.Length(); + for (uint32_t index = 0; index < pendingCount; ++index) { + const PendingServiceInfo& info = mPendingServices.ElementAt(index); + if (info.cid->Equals(aServiceCID)) { + mPendingServices.RemoveElementAt(index); + aLock.NotifyAll(); + return; + } + } +} + +// This should only ever be called within the monitor! +PRThread* nsComponentManagerImpl::GetPendingServiceThread( + const nsCID& aServiceCID) const { + uint32_t pendingCount = mPendingServices.Length(); + for (uint32_t index = 0; index < pendingCount; ++index) { + const PendingServiceInfo& info = mPendingServices.ElementAt(index); + if (info.cid->Equals(aServiceCID)) { + return info.thread; + } + } + return nullptr; +} + +nsresult nsComponentManagerImpl::GetServiceLocked(Maybe<MonitorAutoLock>& aLock, + EntryWrapper& aEntry, + const nsIID& aIID, + void** aResult) { + MOZ_ASSERT(aLock.isSome()); + if (!aLock.isSome()) { + return NS_ERROR_INVALID_ARG; + } + + if (auto* service = aEntry.ServiceInstance()) { + aLock.reset(); + return service->QueryInterface(aIID, aResult); + } + + PRThread* currentPRThread = PR_GetCurrentThread(); + MOZ_ASSERT(currentPRThread, "This should never be null!"); + + PRThread* pendingPRThread; + while ((pendingPRThread = GetPendingServiceThread(aEntry.CID()))) { + if (pendingPRThread == currentPRThread) { + NS_ERROR("Recursive GetService!"); + return NS_ERROR_NOT_AVAILABLE; + } + + aLock->Wait(); + } + + // It's still possible that the other thread failed to create the + // service so we're not guaranteed to have an entry or service yet. + if (auto* service = aEntry.ServiceInstance()) { + aLock.reset(); + return service->QueryInterface(aIID, aResult); + } + + DebugOnly<PendingServiceInfo*> newInfo = + AddPendingService(aEntry.CID(), currentPRThread); + NS_ASSERTION(newInfo, "Failed to add info to the array!"); + + // We need to not be holding the service manager's lock while calling + // CreateInstance, because it invokes user code which could try to re-enter + // the service manager: + + nsCOMPtr<nsISupports> service; + auto cleanup = MakeScopeExit([&]() { + // `service` must be released after the lock is released, so if we fail and + // still have a reference, release the lock before releasing it. + if (service) { + MOZ_ASSERT(aLock.isSome()); + aLock.reset(); + service = nullptr; + } + }); + nsresult rv; + mLock.AssertCurrentThreadOwns(); + { + MonitorAutoUnlock unlock(mLock); + AUTO_PROFILER_MARKER_TEXT( + "GetService", OTHER, MarkerStack::Capture(), + nsDependentCString(nsIDToCString(aEntry.CID()).get())); + rv = aEntry.CreateInstance(aIID, getter_AddRefs(service)); + } + if (NS_SUCCEEDED(rv) && !service) { + NS_ERROR("Factory did not return an object but returned success"); + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + +#ifdef DEBUG + pendingPRThread = GetPendingServiceThread(aEntry.CID()); + MOZ_ASSERT(pendingPRThread == currentPRThread, + "Pending service array has been changed!"); +#endif + MOZ_ASSERT(aLock.isSome()); + RemovePendingService(*aLock, aEntry.CID()); + + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!aEntry.ServiceInstance(), + "Created two instances of a service!"); + + aEntry.SetServiceInstance(service.forget()); + + aLock.reset(); + + *aResult = do_AddRef(aEntry.ServiceInstance()).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetService(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + Maybe<MonitorAutoLock> lock(std::in_place, mLock); + + Maybe<EntryWrapper> entry = LookupByCID(*lock, aClass); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + return GetServiceLocked(lock, *entry, aIID, aResult); +} + +nsresult nsComponentManagerImpl::GetService(ModuleID aId, const nsIID& aIID, + void** aResult) { + const auto& entry = gStaticModules[size_t(aId)]; + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(entry.CID()).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + Maybe<MonitorAutoLock> lock(std::in_place, mLock); + + Maybe<EntryWrapper> wrapper; + if (entry.Overridable()) { + // If we expect this service to be overridden by test code, we need to look + // it up by contract ID every time. + wrapper = LookupByContractID(*lock, entry.ContractID()); + if (!wrapper) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + } else if (!entry.Active()) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } else { + wrapper.emplace(&entry); + } + return GetServiceLocked(lock, *wrapper, aIID, aResult); +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsServiceInstantiated(const nsCID& aClass, + const nsIID& aIID, + bool* aResult) { + // Now we want to get the service if we already got it. If not, we don't want + // to create an instance of it. mmh! + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Checking for service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (Maybe<EntryWrapper> entry = LookupByCID(aClass)) { + if (auto* service = entry->ServiceInstance()) { + nsCOMPtr<nsISupports> instance; + nsresult rv = service->QueryInterface(aIID, getter_AddRefs(instance)); + *aResult = (instance != nullptr); + return rv; + } + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsServiceInstantiatedByContractID( + const char* aContractID, const nsIID& aIID, bool* aResult) { + // Now we want to get the service if we already got it. If not, we don't want + // to create an instance of it. mmh! + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Checking for service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (Maybe<EntryWrapper> entry = + LookupByContractID(nsDependentCString(aContractID))) { + if (auto* service = entry->ServiceInstance()) { + nsCOMPtr<nsISupports> instance; + nsresult rv = service->QueryInterface(aIID, getter_AddRefs(instance)); + *aResult = (instance != nullptr); + return rv; + } + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetServiceByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE("GetServiceByContractID", OTHER, + aContractID); + Maybe<MonitorAutoLock> lock(std::in_place, mLock); + + Maybe<EntryWrapper> entry = + LookupByContractID(*lock, nsDependentCString(aContractID)); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + return GetServiceLocked(lock, *entry, aIID, aResult); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RegisterFactory(const nsCID& aClass, const char* aName, + const char* aContractID, + nsIFactory* aFactory) { + if (!aFactory) { + // If a null factory is passed in, this call just wants to reset + // the contract ID to point to an existing CID entry. + if (!aContractID) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString contractID(aContractID); + + MonitorAutoLock lock(mLock); + nsFactoryEntry* oldf = mFactories.Get(&aClass); + if (oldf) { + StaticComponents::InvalidateContractID(contractID); + mContractIDs.InsertOrUpdate(contractID, oldf); + return NS_OK; + } + + if (StaticComponents::LookupByCID(aClass)) { + // If this is the CID of a static module, just reset the invalid bit of + // the static entry for this contract ID, and assume it points to the + // correct class. + if (StaticComponents::InvalidateContractID(contractID, false)) { + mContractIDs.Remove(contractID); + return NS_OK; + } + } + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + auto f = MakeUnique<nsFactoryEntry>(aClass, aFactory); + + MonitorAutoLock lock(mLock); + return mFactories.WithEntryHandle(&f->mCID, [&](auto&& entry) { + if (entry) { + return NS_ERROR_FACTORY_EXISTS; + } + if (StaticComponents::LookupByCID(f->mCID)) { + return NS_ERROR_FACTORY_EXISTS; + } + if (aContractID) { + nsDependentCString contractID(aContractID); + mContractIDs.InsertOrUpdate(contractID, f.get()); + // We allow dynamically-registered contract IDs to override static + // entries, so invalidate any static entry for this contract ID. + StaticComponents::InvalidateContractID(contractID); + } + entry.Insert(f.release()); + + return NS_OK; + }); +} + +NS_IMETHODIMP +nsComponentManagerImpl::UnregisterFactory(const nsCID& aClass, + nsIFactory* aFactory) { + // Don't release the dying factory or service object until releasing + // the component manager monitor. + nsCOMPtr<nsIFactory> dyingFactory; + nsCOMPtr<nsISupports> dyingServiceObject; + + { + MonitorAutoLock lock(mLock); + auto entry = mFactories.Lookup(&aClass); + nsFactoryEntry* f = entry ? entry.Data() : nullptr; + if (!f || f->mFactory != aFactory) { + // Note: We do not support unregistering static factories. + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + entry.Remove(); + + // This might leave a stale contractid -> factory mapping in + // place, so null out the factory entry (see + // nsFactoryEntry::GetFactory) + f->mFactory.swap(dyingFactory); + f->mServiceObject.swap(dyingServiceObject); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AutoRegister(nsIFile* aLocation) { + XRE_AddManifestLocation(NS_EXTENSION_LOCATION, aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsCIDRegistered(const nsCID& aClass, bool* aResult) { + *aResult = LookupByCID(aClass).isSome(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsContractIDRegistered(const char* aClass, + bool* aResult) { + if (NS_WARN_IF(!aClass)) { + return NS_ERROR_INVALID_ARG; + } + + Maybe<EntryWrapper> entry = LookupByContractID(nsDependentCString(aClass)); + + *aResult = entry.isSome(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetContractIDs(nsTArray<nsCString>& aResult) { + aResult = ToTArray<nsTArray<nsCString>>(mContractIDs.Keys()); + + for (const auto& entry : gContractEntries) { + if (!entry.Invalid()) { + aResult.AppendElement(entry.ContractID()); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::ContractIDToCID(const char* aContractID, + nsCID** aResult) { + { + MonitorAutoLock lock(mLock); + Maybe<EntryWrapper> entry = + LookupByContractID(lock, nsDependentCString(aContractID)); + if (entry) { + *aResult = (nsCID*)moz_xmalloc(sizeof(nsCID)); + **aResult = entry->CID(); + return NS_OK; + } + } + *aResult = nullptr; + return NS_ERROR_FACTORY_NOT_REGISTERED; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(ComponentManagerMallocSizeOf) + +NS_IMETHODIMP +nsComponentManagerImpl::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/component-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(ComponentManagerMallocSizeOf), + "Memory used for the XPCOM component manager."); + + return NS_OK; +} + +size_t nsComponentManagerImpl::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + + n += mFactories.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mFactories.Values()) { + n += data->SizeOfIncludingThis(aMallocSizeOf); + } + + n += mContractIDs.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& key : mContractIDs.Keys()) { + // We don't measure the nsFactoryEntry data because it's owned by + // mFactories (which is measured above). + n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + if (sModuleLocations) { + n += sModuleLocations->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + + n += mPendingServices.ShallowSizeOfExcludingThis(aMallocSizeOf); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mMon + // - sModuleLocations' entries + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFactoryEntry +//////////////////////////////////////////////////////////////////////////////// + +nsFactoryEntry::nsFactoryEntry(const nsCID& aCID, nsIFactory* aFactory) + : mCID(aCID), mFactory(aFactory) {} + +already_AddRefed<nsIFactory> nsFactoryEntry::GetFactory() { + nsComponentManagerImpl::gComponentManager->mLock.AssertNotCurrentThreadOwns(); + + nsCOMPtr<nsIFactory> factory = mFactory; + return factory.forget(); +} + +nsresult nsFactoryEntry::CreateInstance(const nsIID& aIID, void** aResult) { + nsCOMPtr<nsIFactory> factory = GetFactory(); + NS_ENSURE_TRUE(factory, NS_ERROR_FAILURE); + return factory->CreateInstance(aIID, aResult); +} + +size_t nsFactoryEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mCID; + // - mFactory; + // - mServiceObject; + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// Static Access Functions +//////////////////////////////////////////////////////////////////////////////// + +nsresult NS_GetComponentManager(nsIComponentManager** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +nsresult NS_GetServiceManager(nsIServiceManager** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +nsresult NS_GetComponentRegistrar(nsIComponentRegistrar** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsString path; + nsresult rv = aLocation->GetPath(path); + if (NS_FAILED(rv)) { + return rv; + } + + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + return XRE_AddJarManifestLocation(NS_BOOTSTRAPPED_LOCATION, aLocation); + } + + nsCOMPtr<nsIFile> manifest = CloneAndAppend(aLocation, "chrome.manifest"_ns); + return XRE_AddManifestLocation(NS_BOOTSTRAPPED_LOCATION, manifest); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsCOMPtr<nsIChromeRegistry> cr = mozilla::services::GetChromeRegistry(); + if (!cr) { + return NS_ERROR_FAILURE; + } + + nsString path; + nsresult rv = aLocation->GetPath(path); + if (NS_FAILED(rv)) { + return rv; + } + + nsComponentManagerImpl::ComponentLocation elem; + elem.type = NS_BOOTSTRAPPED_LOCATION; + + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + elem.location.Init(aLocation, "chrome.manifest"); + } else { + nsCOMPtr<nsIFile> lf = CloneAndAppend(aLocation, "chrome.manifest"_ns); + elem.location.Init(lf); + } + + // Remove reference. + nsComponentManagerImpl::sModuleLocations->RemoveElement( + elem, ComponentLocationComparator()); + + rv = cr->CheckForNewChrome(); + return rv; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetComponentJSMs(nsIUTF8StringEnumerator** aJSMs) { + nsCOMPtr<nsIUTF8StringEnumerator> result = + StaticComponents::GetComponentJSMs(); + result.forget(aJSMs); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetComponentESModules( + nsIUTF8StringEnumerator** aESModules) { + nsCOMPtr<nsIUTF8StringEnumerator> result = + StaticComponents::GetComponentESModules(); + result.forget(aESModules); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetManifestLocations(nsIArray** aLocations) { + NS_ENSURE_ARG_POINTER(aLocations); + *aLocations = nullptr; + + if (!sModuleLocations) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsIMutableArray> locations = nsArray::Create(); + nsresult rv; + for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) { + ComponentLocation& l = sModuleLocations->ElementAt(i); + FileLocation loc = l.location; + nsCString uriString; + loc.GetURIString(uriString); + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), uriString); + if (NS_SUCCEEDED(rv)) { + locations->AppendElement(uri); + } + } + + locations.forget(aLocations); + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation) { + nsComponentManagerImpl::InitializeModuleLocations(); + nsComponentManagerImpl::ComponentLocation* c = + nsComponentManagerImpl::sModuleLocations->AppendElement(); + c->type = aType; + c->location.Init(aLocation); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterManifest( + aType, c->location, false); + } + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation) { + nsComponentManagerImpl::InitializeModuleLocations(); + nsComponentManagerImpl::ComponentLocation* c = + nsComponentManagerImpl::sModuleLocations->AppendElement(); + + c->type = aType; + c->location.Init(aLocation, "chrome.manifest"); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterManifest( + aType, c->location, false); + } + + return NS_OK; +} + +// Expose some important global interfaces to rust for the rust xpcom API. These +// methods return a non-owning reference to the component manager, which should +// live for the lifetime of XPCOM. +extern "C" { + +const nsIComponentManager* Gecko_GetComponentManager() { + return nsComponentManagerImpl::gComponentManager; +} + +const nsIServiceManager* Gecko_GetServiceManager() { + return nsComponentManagerImpl::gComponentManager; +} + +const nsIComponentRegistrar* Gecko_GetComponentRegistrar() { + return nsComponentManagerImpl::gComponentManager; +} + +// FFI-compatible version of `GetServiceHelper::operator()`. +nsresult Gecko_GetServiceByModuleID(ModuleID aId, const nsIID* aIID, + void** aResult) { + return nsComponentManagerImpl::gComponentManager->GetService(aId, *aIID, + aResult); +} + +// FFI-compatible version of `CreateInstanceHelper::operator()`. +nsresult Gecko_CreateInstanceByModuleID(ModuleID aId, const nsIID* aIID, + void** aResult) { + const auto& entry = gStaticModules[size_t(aId)]; + if (!entry.Active()) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + nsresult rv = entry.CreateInstance(*aIID, aResult); + return rv; +} +} diff --git a/xpcom/components/nsComponentManager.h b/xpcom/components/nsComponentManager.h new file mode 100644 index 0000000000..5ad555b53b --- /dev/null +++ b/xpcom/components/nsComponentManager.h @@ -0,0 +1,218 @@ +/* -*- 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 nsComponentManager_h__ +#define nsComponentManager_h__ + +#include "nsXPCOM.h" + +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIMemoryReporter.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Module.h" +#include "mozilla/Monitor.h" +#include "mozilla/UniquePtr.h" +#include "nsXULAppAPI.h" +#include "nsIFactory.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "PLDHashTable.h" +#include "prtime.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" +#include "nsCOMArray.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsTArray.h" + +#include "mozilla/Components.h" +#include "mozilla/Maybe.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Attributes.h" + +struct nsFactoryEntry; +struct PRThread; + +#define NS_COMPONENTMANAGER_CID \ + { /* 91775d60-d5dc-11d2-92fb-00e09805570f */ \ + 0x91775d60, 0xd5dc, 0x11d2, { \ + 0x92, 0xfb, 0x00, 0xe0, 0x98, 0x05, 0x57, 0x0f \ + } \ + } + +//////////////////////////////////////////////////////////////////////////////// + +namespace { +class EntryWrapper; +} // namespace + +namespace mozilla { +namespace xpcom { + +bool ProcessSelectorMatches(Module::ProcessSelector aSelector); +bool FastProcessSelectorMatches(Module::ProcessSelector aSelector); + +} // namespace xpcom +} // namespace mozilla + +class nsComponentManagerImpl final : public nsIComponentManager, + public nsIServiceManager, + public nsSupportsWeakReference, + public nsIComponentRegistrar, + public nsIInterfaceRequestor, + public nsIMemoryReporter { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICOMPONENTMANAGER + NS_DECL_NSICOMPONENTREGISTRAR + NS_DECL_NSIMEMORYREPORTER + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsresult RegistryLocationForFile(nsIFile* aFile, nsCString& aResult); + nsresult FileForRegistryLocation(const nsCString& aLocation, nsIFile** aSpec); + + NS_DECL_NSISERVICEMANAGER + + // nsComponentManagerImpl methods: + nsComponentManagerImpl(); + + static nsComponentManagerImpl* gComponentManager; + nsresult Init(); + + nsresult Shutdown(void); + + nsresult FreeServices(); + + already_AddRefed<nsIFactory> FindFactory(const nsCID& aClass); + already_AddRefed<nsIFactory> FindFactory(const char* aContractID, + uint32_t aContractIDLen); + + already_AddRefed<nsIFactory> LoadFactory(nsFactoryEntry* aEntry); + + nsTHashMap<nsIDPointerHashKey, nsFactoryEntry*> mFactories; + nsTHashMap<nsCStringHashKey, nsFactoryEntry*> mContractIDs; + + mozilla::Monitor mLock MOZ_UNANNOTATED; + + mozilla::Maybe<EntryWrapper> LookupByCID(const nsID& aCID); + mozilla::Maybe<EntryWrapper> LookupByCID(const mozilla::MonitorAutoLock&, + const nsID& aCID); + + mozilla::Maybe<EntryWrapper> LookupByContractID( + const nsACString& aContractID); + mozilla::Maybe<EntryWrapper> LookupByContractID( + const mozilla::MonitorAutoLock&, const nsACString& aContractID); + + nsresult GetService(mozilla::xpcom::ModuleID, const nsIID& aIID, + void** aResult); + + static bool JSLoaderReady() { return gComponentManager->mJSLoaderReady; } + + static void InitializeStaticModules(); + static void InitializeModuleLocations(); + + struct ComponentLocation { + NSLocationType type; + mozilla::FileLocation location; + }; + + class ComponentLocationComparator { + public: + bool Equals(const ComponentLocation& aA, + const ComponentLocation& aB) const { + return (aA.type == aB.type && aA.location.Equals(aB.location)); + } + }; + + static nsTArray<ComponentLocation>* sModuleLocations; + + // Mutex not held + void RegisterManifest(NSLocationType aType, mozilla::FileLocation& aFile, + bool aChromeOnly); + + struct ManifestProcessingContext { + ManifestProcessingContext(NSLocationType aType, + mozilla::FileLocation& aFile, bool aChromeOnly) + : mType(aType), mFile(aFile), mChromeOnly(aChromeOnly) {} + + ~ManifestProcessingContext() = default; + + NSLocationType mType; + mozilla::FileLocation mFile; + bool mChromeOnly; + }; + + void ManifestManifest(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestCategory(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + + void RereadChromeManifests(bool aChromeOnly = true); + + // Shutdown + enum { + NOT_INITIALIZED, + NORMAL, + SHUTDOWN_IN_PROGRESS, + SHUTDOWN_COMPLETE + } mStatus; + + struct PendingServiceInfo { + const nsCID* cid; + PRThread* thread; + }; + + inline PendingServiceInfo* AddPendingService(const nsCID& aServiceCID, + PRThread* aThread); + inline void RemovePendingService(mozilla::MonitorAutoLock& aLock, + const nsCID& aServiceCID); + inline PRThread* GetPendingServiceThread(const nsCID& aServiceCID) const; + + nsTArray<PendingServiceInfo> mPendingServices; + + bool mJSLoaderReady = false; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + ~nsComponentManagerImpl(); + + nsresult GetServiceLocked(mozilla::Maybe<mozilla::MonitorAutoLock>& aLock, + EntryWrapper& aEntry, const nsIID& aIID, + void** aResult); +}; + +#define NS_MAX_FILENAME_LEN 1024 + +#define NS_ERROR_IS_DIR NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_XPCOM, 24) + +struct nsFactoryEntry { + // nsIComponentRegistrar.registerFactory support + nsFactoryEntry(const nsCID& aClass, nsIFactory* aFactory); + + ~nsFactoryEntry() = default; + + already_AddRefed<nsIFactory> GetFactory(); + + nsresult CreateInstance(const nsIID& aIID, void** aResult); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + const nsCID mCID; + + nsCOMPtr<nsIFactory> mFactory; + nsCOMPtr<nsISupports> mServiceObject; +}; + +#endif // nsComponentManager_h__ diff --git a/xpcom/components/nsComponentManagerUtils.cpp b/xpcom/components/nsComponentManagerUtils.cpp new file mode 100644 index 0000000000..1977e4b3dd --- /dev/null +++ b/xpcom/components/nsComponentManagerUtils.cpp @@ -0,0 +1,259 @@ +/* -*- 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 nsXPCOM_h__ +# include "nsXPCOM.h" +#endif + +#ifndef nsCOMPtr_h__ +# include "nsCOMPtr.h" +#endif + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIComponentManager.h" + +#ifndef MOZILLA_INTERNAL_API + +nsresult CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) { + nsCOMPtr<nsIServiceManager> servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetService(aCID, aIID, aResult); + } + return status; +} + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIServiceManager> servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetServiceByContractID(aContractID, aIID, aResult); + } + return status; +} + +#else + +# include "nsComponentManager.h" + +nsresult CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetService(aCID, aIID, aResult); +} + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetServiceByContractID(aContractID, + aIID, aResult); +} + +#endif + +#ifndef MOZILLA_INTERNAL_API + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->CreateInstance(aCID, aIID, aResult); + } + return status; +} + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->CreateInstanceByContractID(aContractID, aIID, aResult); + return status; +} + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->GetClassObject(aCID, aIID, aResult); + } + return status; +} + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->GetClassObjectByContractID(aContractID, aIID, aResult); + return status; +} + +#else + +# include "nsComponentManager.h" + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstance(aCID, aIID, aResult); +} + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstanceByContractID( + aContractID, aIID, aResult); +} + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObject(aCID, aIID, aResult); +} + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObjectByContractID( + aContractID, aIID, aResult); +} + +#endif + +nsresult nsCreateInstanceByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallCreateInstance(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsCreateInstanceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallCreateInstance(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsCreateInstanceFromFactory::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = mFactory->CreateInstance(aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetClassObjectByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetClassObject(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetClassObjectByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetClassObject(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetServiceByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult nsGetServiceByCIDWithError::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetServiceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult nsGetServiceByContractIDWithError::operator()( + const nsIID& aIID, void** aInstancePtr) const { + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/components/nsComponentManagerUtils.h b/xpcom/components/nsComponentManagerUtils.h new file mode 100644 index 0000000000..b6f15aabaa --- /dev/null +++ b/xpcom/components/nsComponentManagerUtils.h @@ -0,0 +1,170 @@ +/* -*- 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 nsComponentManagerUtils_h__ +#define nsComponentManagerUtils_h__ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIFactory.h" + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult); + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult); + +class MOZ_STACK_CLASS nsCreateInstanceByCID final : public nsCOMPtr_helper { + public: + nsCreateInstanceByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceByContractID final + : public nsCOMPtr_helper { + public: + nsCreateInstanceByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceFromFactory final + : public nsCOMPtr_helper { + public: + nsCreateInstanceFromFactory(nsIFactory* aFactory, nsresult* aErrorPtr) + : mFactory(aFactory), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsIFactory* MOZ_NON_OWNING_REF mFactory; + nsresult* mErrorPtr; +}; + +inline const nsCreateInstanceByCID do_CreateInstance(const nsCID& aCID, + nsresult* aError = 0) { + return nsCreateInstanceByCID(aCID, aError); +} + +inline const nsCreateInstanceByContractID do_CreateInstance( + const char* aContractID, nsresult* aError = 0) { + return nsCreateInstanceByContractID(aContractID, aError); +} + +inline const nsCreateInstanceFromFactory do_CreateInstance( + nsIFactory* aFactory, nsresult* aError = 0) { + return nsCreateInstanceFromFactory(aFactory, aError); +} + +class MOZ_STACK_CLASS nsGetClassObjectByCID final : public nsCOMPtr_helper { + public: + nsGetClassObjectByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsGetClassObjectByContractID final + : public nsCOMPtr_helper { + public: + nsGetClassObjectByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +/** + * do_GetClassObject can be used to improve performance of callers + * that call |CreateInstance| many times. They can cache the factory + * and call do_CreateInstance or CallCreateInstance with the cached + * factory rather than having the component manager retrieve it every + * time. + */ +inline const nsGetClassObjectByCID do_GetClassObject(const nsCID& aCID, + nsresult* aError = 0) { + return nsGetClassObjectByCID(aCID, aError); +} + +inline const nsGetClassObjectByContractID do_GetClassObject( + const char* aContractID, nsresult* aError = 0) { + return nsGetClassObjectByContractID(aContractID, aError); +} + +// type-safe shortcuts for calling |CreateInstance| +template <class DestinationType> +inline nsresult CallCreateInstance(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallCreateInstance(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallCreateInstance(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aContractID, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallCreateInstance(nsIFactory* aFactory, + DestinationType** aDestination) { + MOZ_ASSERT(aFactory, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aFactory->CreateInstance(nullptr, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallGetClassObject(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetClassObject(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallGetClassObject(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetClassObject(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +#endif /* nsComponentManagerUtils_h__ */ diff --git a/xpcom/components/nsICategoryManager.idl b/xpcom/components/nsICategoryManager.idl new file mode 100644 index 0000000000..12c1a9e4a9 --- /dev/null +++ b/xpcom/components/nsICategoryManager.idl @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsISupportsPrimitives.idl" + +interface nsISimpleEnumerator; + +%{C++ +#include "nsString.h" +%} + +/* + * nsICategoryManager + */ + +[scriptable, builtinclass, uuid(de021d54-57a3-4025-ae63-4c8eedbe74c0)] +interface nsICategoryEntry : nsISupportsCString +{ + readonly attribute ACString entry; + + readonly attribute ACString value; +}; + +[builtinclass, scriptable, uuid(3275b2cd-af6d-429a-80d7-f0c5120342ac)] +interface nsICategoryManager : nsISupports +{ + /** + * Get the value for the given category's entry. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry you're looking for ("http") + * @return The value. + */ + ACString getCategoryEntry(in ACString aCategory, in ACString aEntry); + + /** + * Add an entry to a category. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry to be added ("http") + * @param aValue The value for the entry ("moz.httprulez.1") + * @param aPersist Should this data persist between invocations? + * @param aReplace Should we replace an existing entry? + * @return Previous entry, if any + */ + ACString addCategoryEntry(in ACString aCategory, in ACString aEntry, + in ACString aValue, in boolean aPersist, + in boolean aReplace); + + /** + * Delete an entry from the category. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry to be added ("http") + * @param aPersist Delete persistent data from registry, if present? + */ + void deleteCategoryEntry(in ACString aCategory, in ACString aEntry, + in boolean aPersist); + + /** + * Delete a category and all entries. + * @param aCategory The category to be deleted. + */ + void deleteCategory(in ACString aCategory); + + /** + * Enumerate the entries in a category. + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsICategoryEntry. + */ + nsISimpleEnumerator enumerateCategory(in ACString aCategory); + + + /** + * Enumerate all existing categories + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsISupportsCString. + */ + nsISimpleEnumerator enumerateCategories(); + + %{C++ + template<size_t N> + nsresult + GetCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, + nsACString& aResult) + { + return GetCategoryEntry(nsLiteralCString(aCategory), + aEntry, aResult); + } + + template<size_t N, size_t M> + nsresult + GetCategoryEntry(const char (&aCategory)[N], const char (&aEntry)[M], + nsACString& aResult) + { + return GetCategoryEntry(nsLiteralCString(aCategory), + nsLiteralCString(aEntry), + aResult); + } + + nsresult + AddCategoryEntry(const nsACString& aCategory, const nsACString& aEntry, + const nsACString& aValue, bool aPersist, bool aReplace) + { + nsCString oldValue; + return AddCategoryEntry(aCategory, aEntry, aValue, aPersist, aReplace, + oldValue); + } + + template<size_t N> + nsresult + AddCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, + const nsACString& aValue, bool aPersist, bool aReplace) + { + nsCString oldValue; + return AddCategoryEntry(nsLiteralCString(aCategory), aEntry, aValue, + aPersist, aReplace, oldValue); + } + + template<size_t N> + nsresult + DeleteCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, bool aPersist) + { + return DeleteCategoryEntry(nsLiteralCString(aCategory), aEntry, aPersist); + } + + + template<size_t N> + nsresult + EnumerateCategory(const char (&aCategory)[N], nsISimpleEnumerator** aResult) + { + return EnumerateCategory(nsLiteralCString(aCategory), aResult); + } + %} +}; diff --git a/xpcom/components/nsIClassInfo.idl b/xpcom/components/nsIClassInfo.idl new file mode 100644 index 0000000000..1ebef34ecf --- /dev/null +++ b/xpcom/components/nsIClassInfo.idl @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIXPCScriptable; + +/** + * Provides information about a specific implementation class. If you want + * your class to implement nsIClassInfo, see nsIClassInfoImpl.h for + * instructions--you most likely do not want to inherit from nsIClassInfo. + */ + +[scriptable, uuid(a60569d7-d401-4677-ba63-2aa5971af25d)] +interface nsIClassInfo : nsISupports +{ + /** + * Returns a list of the interfaces which instances of this class promise + * to implement. Note that nsISupports is an implicit member of any such + * list, and need not be included. + */ + readonly attribute Array<nsIIDRef> interfaces; + + /** + * Return an object to assist XPConnect in supplying JavaScript-specific + * behavior to callers of the instance object, or null if not needed. + */ + nsIXPCScriptable getScriptableHelper(); + + /** + * A contract ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|), or null/void. + */ + readonly attribute AUTF8String contractID; + + /** + * A human readable string naming the class, or null/void. + */ + readonly attribute AUTF8String classDescription; + + /** + * A class ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|), or null. + */ + readonly attribute nsCIDPtr classID; + + /** + * Bitflags for 'flags' attribute. + */ + const uint32_t SINGLETON = 1 << 0; + const uint32_t THREADSAFE = 1 << 1; + const uint32_t SINGLETON_CLASSINFO = 1 << 5; + + // The high order bit is RESERVED for consumers of these flags. + // No implementor of this interface should ever return flags + // with this bit set. + const uint32_t RESERVED = 1 << 31; + + + readonly attribute uint32_t flags; + + /** + * Also a class ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|). If the class does + * not have a CID, it should return NS_ERROR_NOT_AVAILABLE. This attribute + * exists so C++ callers can avoid allocating and freeing a CID, as would + * happen if they used classID. + */ + [noscript] readonly attribute nsCID classIDNoAlloc; + +}; diff --git a/xpcom/components/nsIComponentManager.idl b/xpcom/components/nsIComponentManager.idl new file mode 100644 index 0000000000..9abde9960c --- /dev/null +++ b/xpcom/components/nsIComponentManager.idl @@ -0,0 +1,117 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The nsIComponentManager interface. + */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIFactory; +interface nsIArray; +interface nsIUTF8StringEnumerator; + +[scriptable, builtinclass, uuid(d604ffc3-1ba3-4f6c-b65f-1ed4199364c3)] +interface nsIComponentManager : nsISupports +{ + /** + * getClassObject + * + * Returns the factory object that can be used to create instances of + * CID aClass + * + * @param aClass The classid of the factory that is being requested + */ + void getClassObject(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * getClassObjectByContractID + * + * Returns the factory object that can be used to create instances of + * CID aClass + * + * @param aClass The classid of the factory that is being requested + */ + void getClassObjectByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + + /** + * createInstance + * + * Create an instance of the CID aClass and return the interface aIID. + * + * @param aClass : ClassID of object instance requested + * @param aIID : IID of interface requested + */ + [noscript] + void createInstance(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * createInstanceByContractID + * + * Create an instance of the CID that implements aContractID and return the + * interface aIID. + * + * @param aContractID : aContractID of object instance requested + * @param aIID : IID of interface requested + */ + [noscript] + void createInstanceByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * addBootstrappedManifestLocation + * + * Adds a bootstrapped manifest location on runtime. + * + * @param aLocation : A directory where chrome.manifest resides, + * or an XPI with it on the root. + */ + void addBootstrappedManifestLocation(in nsIFile aLocation); + + /** + * removeBootstrappedManifestLocation + * + * Removes a bootstrapped manifest location on runtime. + * + * @param aLocation : A directory where chrome.manifest resides, + * or an XPI with it on the root. + */ + void removeBootstrappedManifestLocation(in nsIFile aLocation); + + /** + * getManifestLocations + * + * Get an array of nsIURIs of all registered and builtin manifest locations. + */ + nsIArray getManifestLocations(); + + /** + * Returns a list of JSM URLs which are used to create components. This + * should only be used in automation. + */ + nsIUTF8StringEnumerator getComponentJSMs(); + + /** + * Returns a list of ESM URLs which are used to create components. This + * should only be used in automation. + */ + nsIUTF8StringEnumerator getComponentESModules(); +}; + + +%{ C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsComponentManagerUtils.h" +#endif +%} C++ diff --git a/xpcom/components/nsIComponentRegistrar.idl b/xpcom/components/nsIComponentRegistrar.idl new file mode 100644 index 0000000000..6b85caffab --- /dev/null +++ b/xpcom/components/nsIComponentRegistrar.idl @@ -0,0 +1,100 @@ +/* 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/. */ + +/** + * The nsIComponentRegistrar interface. + */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIFactory; + +[builtinclass, scriptable, uuid(2417cbfe-65ad-48a6-b4b6-eb84db174392)] +interface nsIComponentRegistrar : nsISupports +{ + /** + * autoRegister + * + * Register a .manifest file, or an entire directory containing + * these files. Registration lasts for this run only, and is not cached. + * + * @note Formerly this method would register component files directly. This + * is no longer supported. + */ + void autoRegister(in nsIFile aSpec); + + /** + * registerFactory + * + * Register a factory with a given ContractID, CID and Class Name. + * + * @param aClass : CID of object + * @param aClassName : Class Name of CID (unused) + * @param aContractID : ContractID associated with CID aClass. May be null + * if no contract ID is needed. + * @param aFactory : Factory that will be registered for CID aClass. + * If aFactory is null, the contract will be associated + * with a previously registered CID. + */ + void registerFactory(in nsCIDRef aClass, + in string aClassName, + in string aContractID, + in nsIFactory aFactory); + + /** + * unregisterFactory + * + * Unregister a factory associated with CID aClass. + * + * @param aClass : CID being unregistered + * @param aFactory : Factory previously registered to create instances of + * CID aClass. + * + * @throws NS_ERROR* Method failure. + */ + void unregisterFactory(in nsCIDRef aClass, + in nsIFactory aFactory); + + /** + * isCIDRegistered + * + * Returns true if a factory is registered for the CID. + * + * @param aClass : CID queried for registeration + * @return : true if a factory is registered for CID + * false otherwise. + */ + boolean isCIDRegistered(in nsCIDRef aClass); + + /** + * isContractIDRegistered + * + * Returns true if a factory is registered for the contract id. + * + * @param aClass : contract id queried for registeration + * @return : true if a factory is registered for contract id + * false otherwise. + */ + boolean isContractIDRegistered(in string aContractID); + + /** + * getContractIDs + * + * Return the list of all registered ContractIDs. + * + * @return : Array of ContractIDs. Elements of the array are the string + * encoding of Contract IDs. + */ + Array<ACString> getContractIDs(); + + /** + * contractIDToCID + * + * Returns the CID for a given Contract ID, if one exists and is registered. + * + * @return : Contract ID. + */ + nsCIDPtr contractIDToCID(in string aContractID); +}; diff --git a/xpcom/components/nsIFactory.idl b/xpcom/components/nsIFactory.idl new file mode 100644 index 0000000000..e0b3e6e756 --- /dev/null +++ b/xpcom/components/nsIFactory.idl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * A class factory allows the creation of nsISupports derived + * components without specifying a concrete base class. + */ + +[scriptable, object, uuid(1bb40a56-9223-41e6-97d4-da97bdeb6a4d)] +interface nsIFactory : nsISupports { + /** + * Creates an instance of a component. + * + * @param iid The IID of the interface being requested in + * the component which is being currently created. + * @param result [out] Pointer to the newly created instance, if successful. + * @throws NS_NOINTERFACE - Interface not accessible. + * NS_ERROR* - Method failure. + */ + void createInstance(in nsIIDRef iid, + [retval, iid_is(iid)] out nsQIResult result); + +}; diff --git a/xpcom/components/nsIServiceManager.idl b/xpcom/components/nsIServiceManager.idl new file mode 100644 index 0000000000..dd613070e9 --- /dev/null +++ b/xpcom/components/nsIServiceManager.idl @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsISupports.idl" + +/** + * The nsIServiceManager manager interface provides a means to obtain + * global services in an application. The service manager depends on the + * repository to find and instantiate factories to obtain services. + * + * Users of the service manager must first obtain a pointer to the global + * service manager by calling NS_GetServiceManager. After that, + * they can request specific services by calling GetService. When they are + * finished they can NS_RELEASE() the service as usual. + * + * A user of a service may keep references to particular services indefinitely + * and only must call Release when it shuts down. + */ + +[builtinclass, scriptable, uuid(8bb35ed9-e332-462d-9155-4a002ab5c958)] +interface nsIServiceManager : nsISupports +{ + /** + * getServiceByContractID + * + * Returns the instance that implements aClass or aContractID and the + * interface aIID. This may result in the instance being created. + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @param result : resulting service + */ + void getService(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + void getServiceByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * isServiceInstantiated + * + * isServiceInstantiated will return a true if the service has already + * been created, or false otherwise. Throws if the service does not + * implement the given IID. + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @throws NS_NOINTERFACE if the IID given isn't supported by the object + */ + boolean isServiceInstantiated(in nsCIDRef aClass, in nsIIDRef aIID); + boolean isServiceInstantiatedByContractID(in string aContractID, in nsIIDRef aIID); +}; + + +%{C++ +// Observing xpcom autoregistration. Topics will be 'start' and 'stop'. +#define NS_XPCOM_AUTOREGISTRATION_OBSERVER_ID "xpcom-autoregistration" + +#ifdef MOZILLA_INTERNAL_API +#include "nsXPCOM.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#endif +%} diff --git a/xpcom/components/nsServiceManagerUtils.h b/xpcom/components/nsServiceManagerUtils.h new file mode 100644 index 0000000000..4bfbc804cc --- /dev/null +++ b/xpcom/components/nsServiceManagerUtils.h @@ -0,0 +1,56 @@ +/* -*- 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 nsServiceManagerUtils_h__ +#define nsServiceManagerUtils_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" + +inline nsGetServiceByCID do_GetService(const nsCID& aCID) { + return nsGetServiceByCID(aCID); +} + +inline nsGetServiceByCIDWithError do_GetService(const nsCID& aCID, + nsresult* aError) { + return nsGetServiceByCIDWithError(aCID, aError); +} + +inline nsGetServiceByContractID do_GetService(const char* aContractID) { + return nsGetServiceByContractID(aContractID); +} + +inline nsGetServiceByContractIDWithError do_GetService(const char* aContractID, + nsresult* aError) { + return nsGetServiceByContractIDWithError(aContractID, aError); +} + +nsresult CallGetService(const nsCID& aClass, const nsIID& aIID, void** aResult); + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult); + +// type-safe shortcuts for calling |GetService| +template <class DestinationType> +inline nsresult CallGetService(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetService(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class DestinationType> +inline nsresult CallGetService(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aContractID, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetService(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +#endif diff --git a/xpcom/components/test/python.ini b/xpcom/components/test/python.ini new file mode 100644 index 0000000000..9792769c24 --- /dev/null +++ b/xpcom/components/test/python.ini @@ -0,0 +1,4 @@ +[DEFAULT] +subsuite = xpcom + +[test_gen_static_components.py] diff --git a/xpcom/components/test/test_gen_static_components.py b/xpcom/components/test/test_gen_static_components.py new file mode 100644 index 0000000000..85f731c082 --- /dev/null +++ b/xpcom/components/test/test_gen_static_components.py @@ -0,0 +1,150 @@ +# 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/. + +import os +import sys +import unittest + +import mozunit + +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import gen_static_components +from gen_static_components import BackgroundTasksSelector + + +class TestGenStaticComponents(unittest.TestCase): + def test_string(self): + # A string: we default to NO_TASKS. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": ["m-dummy1", "m-dummy2"], + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + def test_dict(self): + # A dict, but no backgroundtasks selector: we default to NO_TASKS. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": { + "name": ["m-dummy1", "m-dummy2"], + }, + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + def test_dict_with_selector(self): + # A dict with a selector. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": { + "name": ["m-dummy1", "m-dummy2"], + "backgroundtasks": BackgroundTasksSelector.ALL_TASKS, + }, + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::ALL_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::ALL_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + +if __name__ == "__main__": + mozunit.main() |