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