summaryrefslogtreecommitdiffstats
path: root/xpcom/components/gen_static_components.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /xpcom/components/gen_static_components.py
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/components/gen_static_components.py')
-rw-r--r--xpcom/components/gen_static_components.py1216
1 files changed, 1216 insertions, 0 deletions
diff --git a/xpcom/components/gen_static_components.py b/xpcom/components/gen_static_components.py
new file mode 100644
index 0000000000..f759dc3132
--- /dev/null
+++ b/xpcom/components/gen_static_components.py
@@ -0,0 +1,1216 @@
+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 = 0
+ MAIN_PROCESS_ONLY = 1 << 0
+ CONTENT_PROCESS_ONLY = 1 << 1
+ ALLOW_IN_GPU_PROCESS = 1 << 2
+ ALLOW_IN_VR_PROCESS = 1 << 3
+ ALLOW_IN_SOCKET_PROCESS = 1 << 4
+ ALLOW_IN_RDD_PROCESS = 1 << 5
+ ALLOW_IN_UTILITY_PROCESS = 1 << 6
+ ALLOW_IN_GMPLUGIN_PROCESS = 1 << 7
+ 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
+ )
+ ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS = (
+ ALLOW_IN_GPU_PROCESS
+ | ALLOW_IN_RDD_PROCESS
+ | ALLOW_IN_VR_PROCESS
+ | ALLOW_IN_SOCKET_PROCESS
+ | ALLOW_IN_UTILITY_PROCESS
+ | ALLOW_IN_GMPLUGIN_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
+ ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_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