#!/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)