Source code

Revision control

Copy as Markdown

Other Tools

#!/usr/bin/env python
# jsonxpt.py - Generate json XPT typelib files from IDL.
#
# 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/.
"""Generate a json XPT typelib for an IDL file"""
import itertools
import json
from xpidl import xpidl
# A map of xpidl.py types to xpt enum variants
TypeMap = {
# builtins
"boolean": "TD_BOOL",
"void": "TD_VOID",
"int8_t": "TD_INT8",
"int16_t": "TD_INT16",
"int32_t": "TD_INT32",
"int64_t": "TD_INT64",
"uint8_t": "TD_UINT8",
"uint16_t": "TD_UINT16",
"uint32_t": "TD_UINT32",
"uint64_t": "TD_UINT64",
"nsresult": "TD_UINT32",
"float": "TD_FLOAT",
"double": "TD_DOUBLE",
"char": "TD_CHAR",
"string": "TD_PSTRING",
"wchar": "TD_WCHAR",
"wstring": "TD_PWSTRING",
"char16_t": "TD_UINT16",
# special types
"nsid": "TD_NSID",
"astring": "TD_ASTRING",
"utf8string": "TD_UTF8STRING",
"cstring": "TD_CSTRING",
"jsval": "TD_JSVAL",
"promise": "TD_PROMISE",
}
def flags(*flags):
return [flag for flag, cond in flags if cond]
def get_type(type, calltype, iid_is=None, size_is=None, needs_scriptable=None):
while isinstance(type, xpidl.Typedef):
type = type.realtype
if isinstance(type, xpidl.Builtin):
ret = {"tag": TypeMap[type.name]}
if type.name in ["string", "wstring"] and size_is is not None:
ret["tag"] += "_SIZE_IS"
ret["size_is"] = size_is
return ret
if isinstance(type, xpidl.Array):
# NB: For a Array<T> we pass down the iid_is to get the type of T.
# This allows Arrays of InterfaceIs types to work.
return {
"tag": "TD_ARRAY",
"element": get_type(type.type, calltype, iid_is, None, needs_scriptable),
}
if isinstance(type, xpidl.LegacyArray):
# NB: For a Legacy [array] T we pass down iid_is to get the type of T.
# This allows [array] of InterfaceIs types to work.
return {
"tag": "TD_LEGACY_ARRAY",
"size_is": size_is,
"element": get_type(type.type, calltype, iid_is, None, needs_scriptable),
}
if isinstance(type, xpidl.Interface) or isinstance(type, xpidl.Forward):
if isinstance(needs_scriptable, set):
needs_scriptable.add(type.name)
return {
"tag": "TD_INTERFACE_TYPE",
"name": type.name,
}
if isinstance(type, xpidl.WebIDL):
return {
"tag": "TD_DOMOBJECT",
"name": type.name,
"native": type.native,
"headerFile": type.headerFile,
}
if isinstance(type, xpidl.Native):
if type.specialtype == "nsid" and type.isPtr(calltype):
return {"tag": "TD_NSIDPTR"}
elif type.specialtype:
return {"tag": TypeMap[type.specialtype]}
elif iid_is is not None:
return {
"tag": "TD_INTERFACE_IS_TYPE",
"iid_is": iid_is,
}
else:
return {"tag": "TD_VOID"}
if isinstance(type, xpidl.CEnum):
# As far as XPConnect is concerned, cenums are just unsigned integers.
return {"tag": "TD_UINT%d" % type.width}
raise Exception("Unknown type!")
def mk_param(type, in_=0, out=0, optional=0):
return {
"type": type,
"flags": flags(
("in", in_),
("out", out),
("optional", optional),
),
}
def mk_method(method, params, getter=0, setter=0, optargc=0, hasretval=0, symbol=0):
return {
"name": method.name,
# NOTE: We don't include any return value information here, as we'll
# never call the methods if they're marked notxpcom, and all xpcom
# methods return the same type (nsresult).
# XXX: If we ever use these files for other purposes than xptcodegen we
# may want to write that info.
"params": params,
"flags": flags(
("getter", getter),
("setter", setter),
("hidden", not method.isScriptable()),
("optargc", optargc),
("jscontext", method.implicit_jscontext),
("hasretval", hasretval),
("symbol", method.symbol),
),
}
def attr_param_idx(p, m, attr):
attr_val = getattr(p, attr, None)
if not attr_val:
return None
for i, param in enumerate(m.params):
if param.name == attr_val:
return i
raise Exception(f"Need parameter named '{attr_val}' for attribute '{attr}'")
def build_interface(iface):
if iface.namemap is None:
raise Exception("Interface was not resolved.")
assert (
iface.attributes.scriptable
), "Don't generate XPT info for non-scriptable interfaces"
# State used while building an interface
consts = []
methods = []
# Interfaces referenced from scriptable members need to be [scriptable].
needs_scriptable = set()
def build_const(c):
consts.append(
{
"name": c.name,
"type": get_type(c.basetype, ""),
"value": c.getValue(), # All of our consts are numbers
}
)
def build_cenum(b):
for var in b.variants:
consts.append(
{
"name": var.name,
"type": get_type(b, "in"),
"value": var.value,
}
)
def build_method(m, needs_scriptable=None):
params = []
for p in m.params:
params.append(
mk_param(
get_type(
p.realtype,
p.paramtype,
iid_is=attr_param_idx(p, m, "iid_is"),
size_is=attr_param_idx(p, m, "size_is"),
needs_scriptable=needs_scriptable,
),
in_=p.paramtype.count("in"),
out=p.paramtype.count("out"),
optional=p.optional,
)
)
hasretval = len(m.params) > 0 and m.params[-1].retval
if not m.notxpcom and m.realtype.name != "void":
hasretval = True
type = get_type(m.realtype, "out", needs_scriptable=needs_scriptable)
params.append(mk_param(type, out=1))
methods.append(
mk_method(m, params, optargc=m.optional_argc, hasretval=hasretval)
)
def build_attr(a, needs_scriptable=None):
assert a.realtype.name != "void"
# Write the getter
getter_params = []
if not a.notxpcom:
type = get_type(a.realtype, "out", needs_scriptable=needs_scriptable)
getter_params.append(mk_param(type, out=1))
methods.append(mk_method(a, getter_params, getter=1, hasretval=1))
# And maybe the setter
if not a.readonly:
type = get_type(a.realtype, "in", needs_scriptable=needs_scriptable)
methods.append(mk_method(a, [mk_param(type, in_=1)], setter=1))
for member in iface.members:
if isinstance(member, xpidl.ConstMember):
build_const(member)
elif isinstance(member, xpidl.Attribute):
build_attr(member, member.isScriptable() and needs_scriptable)
elif isinstance(member, xpidl.Method):
build_method(member, member.isScriptable() and needs_scriptable)
elif isinstance(member, xpidl.CEnum):
build_cenum(member)
elif isinstance(member, xpidl.CDATA):
pass
else:
raise Exception("Unexpected interface member: %s" % member)
for ref in set(needs_scriptable):
p = iface.idl.getName(xpidl.TypeId(ref), None)
if isinstance(p, xpidl.Interface):
needs_scriptable.remove(ref)
if not p.attributes.scriptable:
raise Exception(
f"Scriptable member in {iface.name} references non-scriptable {ref}. "
"The interface must be marked as [scriptable], "
"or the referencing member with [noscript]."
)
return {
"name": iface.name,
"uuid": iface.attributes.uuid,
"methods": methods,
"consts": consts,
"parent": iface.base,
"needs_scriptable": sorted(needs_scriptable),
"flags": flags(
("function", iface.attributes.function),
("builtinclass", iface.attributes.builtinclass),
("main_process_only", iface.attributes.main_process_scriptable_only),
),
}
# These functions are the public interface of this module. They are very simple
# functions, but are exported so that if we need to do something more
# complex in them in the future we can.
def build_typelib(idl):
"""Given a parsed IDL file, generate and return the typelib"""
return [
build_interface(p)
for p in idl.productions
if p.kind == "interface" and p.attributes.scriptable
]
def link(typelibs):
"""Link a list of typelibs together into a single typelib"""
linked = list(itertools.chain.from_iterable(typelibs))
assert len(set(iface["name"] for iface in linked)) == len(
linked
), "Multiple typelibs containing the same interface were linked together"
return linked
def write(typelib, fd):
"""Write typelib into fd"""
json.dump(typelib, fd, indent=2, sort_keys=True)