mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
3962 lines
158 KiB
Python
3962 lines
158 KiB
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/.
|
|
|
|
# Common codegen classes.
|
|
|
|
import os
|
|
import string
|
|
|
|
from WebIDL import *
|
|
from Configuration import NoSuchDescriptorError
|
|
|
|
AUTOGENERATED_WARNING_COMMENT = \
|
|
"/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
|
|
ADDPROPERTY_HOOK_NAME = '_addProperty'
|
|
FINALIZE_HOOK_NAME = '_finalize'
|
|
TRACE_HOOK_NAME = '_trace'
|
|
CONSTRUCT_HOOK_NAME = '_constructor'
|
|
HASINSTANCE_HOOK_NAME = '_hasInstance'
|
|
|
|
def replaceFileIfChanged(filename, newContents):
|
|
"""
|
|
Read a copy of the old file, so that we don't touch it if it hasn't changed.
|
|
Returns True if the file was updated, false otherwise.
|
|
"""
|
|
oldFileContents = ""
|
|
try:
|
|
oldFile = open(filename, 'rb')
|
|
oldFileContents = ''.join(oldFile.readlines())
|
|
oldFile.close()
|
|
except:
|
|
pass
|
|
|
|
if newContents == oldFileContents:
|
|
return False
|
|
|
|
f = open(filename, 'wb')
|
|
f.write(newContents)
|
|
f.close()
|
|
|
|
def toStringBool(arg):
|
|
return str(not not arg).lower()
|
|
|
|
def toBindingNamespace(arg):
|
|
return re.sub("((_workers)?$)", "Binding\\1", arg);
|
|
|
|
class CGThing():
|
|
"""
|
|
Abstract base class for things that spit out code.
|
|
"""
|
|
def __init__(self):
|
|
pass # Nothing for now
|
|
def declare(self):
|
|
"""Produce code for a header file."""
|
|
assert(False) # Override me!
|
|
def define(self):
|
|
"""Produce code for a cpp file."""
|
|
assert(False) # Override me!
|
|
|
|
class CGNativePropertyHooks(CGThing):
|
|
"""
|
|
Generate a NativePropertyHooks for a given descriptor
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
def declare(self):
|
|
if self.descriptor.workers:
|
|
return ""
|
|
return " extern const NativePropertyHooks NativeHooks;\n"
|
|
def define(self):
|
|
if self.descriptor.workers:
|
|
return ""
|
|
parent = self.descriptor.interface.parent
|
|
parentHooks = ("&" + toBindingNamespace(parent.identifier.name) + "::NativeHooks"
|
|
if parent else 'NULL')
|
|
return """
|
|
const NativePropertyHooks NativeHooks = { ResolveProperty, EnumerateProperties, %s };
|
|
""" % parentHooks
|
|
|
|
class CGDOMJSClass(CGThing):
|
|
"""
|
|
Generate a DOMJSClass for a given descriptor
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
def declare(self):
|
|
return " extern DOMJSClass Class;\n"
|
|
def define(self):
|
|
traceHook = TRACE_HOOK_NAME if self.descriptor.customTrace else 'NULL'
|
|
protoList = ['prototypes::id::' + proto for proto in self.descriptor.prototypeChain]
|
|
# Pad out the list to the right length with _ID_Count so we
|
|
# guarantee that all the lists are the same length. _ID_Count
|
|
# is never the ID of any prototype, so it's safe to use as
|
|
# padding.
|
|
while len(protoList) < self.descriptor.config.maxProtoChainLength:
|
|
protoList.append('prototypes::id::_ID_Count')
|
|
prototypeChainString = ', '.join(protoList)
|
|
nativeHooks = "NULL" if self.descriptor.workers else "&NativeHooks"
|
|
return """
|
|
DOMJSClass Class = {
|
|
{ "%s",
|
|
JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1),
|
|
%s, /* addProperty */
|
|
JS_PropertyStub, /* delProperty */
|
|
JS_PropertyStub, /* getProperty */
|
|
JS_StrictPropertyStub, /* setProperty */
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
%s, /* finalize */
|
|
NULL, /* checkAccess */
|
|
NULL, /* call */
|
|
NULL, /* hasInstance */
|
|
NULL, /* construct */
|
|
%s, /* trace */
|
|
JSCLASS_NO_INTERNAL_MEMBERS
|
|
},
|
|
{ %s },
|
|
-1, %s,
|
|
%s
|
|
};
|
|
""" % (self.descriptor.interface.identifier.name,
|
|
ADDPROPERTY_HOOK_NAME if self.descriptor.concrete and not self.descriptor.workers and self.descriptor.wrapperCache else 'JS_PropertyStub',
|
|
FINALIZE_HOOK_NAME, traceHook, prototypeChainString,
|
|
str(self.descriptor.nativeIsISupports).lower(),
|
|
nativeHooks)
|
|
|
|
class CGPrototypeJSClass(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
def declare(self):
|
|
# We're purely for internal consumption
|
|
return ""
|
|
def define(self):
|
|
return """
|
|
static JSClass PrototypeClass = {
|
|
"%sPrototype", 0,
|
|
JS_PropertyStub, /* addProperty */
|
|
JS_PropertyStub, /* delProperty */
|
|
JS_PropertyStub, /* getProperty */
|
|
JS_StrictPropertyStub, /* setProperty */
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
NULL, /* finalize */
|
|
NULL, /* checkAccess */
|
|
NULL, /* call */
|
|
NULL, /* hasInstance */
|
|
NULL, /* construct */
|
|
NULL, /* trace */
|
|
JSCLASS_NO_INTERNAL_MEMBERS
|
|
};
|
|
""" % (self.descriptor.interface.identifier.name)
|
|
|
|
class CGInterfaceObjectJSClass(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
def declare(self):
|
|
# We're purely for internal consumption
|
|
return ""
|
|
def define(self):
|
|
if not self.descriptor.hasInstanceInterface:
|
|
return ""
|
|
ctorname = "NULL" if not self.descriptor.interface.ctor() else CONSTRUCT_HOOK_NAME
|
|
hasinstance = HASINSTANCE_HOOK_NAME
|
|
return """
|
|
static JSClass InterfaceObjectClass = {
|
|
"Function", 0,
|
|
JS_PropertyStub, /* addProperty */
|
|
JS_PropertyStub, /* delProperty */
|
|
JS_PropertyStub, /* getProperty */
|
|
JS_StrictPropertyStub, /* setProperty */
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
NULL, /* finalize */
|
|
NULL, /* checkAccess */
|
|
%s, /* call */
|
|
%s, /* hasInstance */
|
|
%s, /* construct */
|
|
NULL, /* trace */
|
|
JSCLASS_NO_INTERNAL_MEMBERS
|
|
};
|
|
""" % (ctorname, hasinstance, ctorname)
|
|
|
|
class CGList(CGThing):
|
|
"""
|
|
Generate code for a list of GCThings. Just concatenates them together, with
|
|
an optional joiner string. "\n" is a common joiner.
|
|
"""
|
|
def __init__(self, children, joiner=""):
|
|
CGThing.__init__(self)
|
|
self.children = children
|
|
self.joiner = joiner
|
|
def append(self, child):
|
|
self.children.append(child)
|
|
def prepend(self, child):
|
|
self.children.insert(0, child)
|
|
def declare(self):
|
|
return self.joiner.join([child.declare() for child in self.children
|
|
if child is not None])
|
|
def define(self):
|
|
return self.joiner.join([child.define() for child in self.children
|
|
if child is not None])
|
|
|
|
class CGGeneric(CGThing):
|
|
"""
|
|
A class that spits out a fixed string into the codegen. Can spit out a
|
|
separate string for the declaration too.
|
|
"""
|
|
def __init__(self, define="", declare=""):
|
|
self.declareText = declare
|
|
self.defineText = define
|
|
def declare(self):
|
|
return self.declareText
|
|
def define(self):
|
|
return self.defineText
|
|
|
|
# We'll want to insert the indent at the beginnings of lines, but we
|
|
# don't want to indent empty lines. So only indent lines that have a
|
|
# non-newline character on them.
|
|
lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
|
|
class CGIndenter(CGThing):
|
|
"""
|
|
A class that takes another CGThing and generates code that indents that
|
|
CGThing by some number of spaces. The default indent is two spaces.
|
|
"""
|
|
def __init__(self, child, indentLevel=2):
|
|
CGThing.__init__(self)
|
|
self.child = child
|
|
self.indent = " " * indentLevel
|
|
def declare(self):
|
|
decl = self.child.declare()
|
|
if decl is not "":
|
|
return re.sub(lineStartDetector, self.indent, decl)
|
|
else:
|
|
return ""
|
|
def define(self):
|
|
defn = self.child.define()
|
|
if defn is not "":
|
|
return re.sub(lineStartDetector, self.indent, defn)
|
|
else:
|
|
return ""
|
|
|
|
class CGWrapper(CGThing):
|
|
"""
|
|
Generic CGThing that wraps other CGThings with pre and post text.
|
|
"""
|
|
def __init__(self, child, pre="", post="", declarePre=None,
|
|
declarePost=None, definePre=None, definePost=None,
|
|
declareOnly=False, defineOnly=False, reindent=False):
|
|
CGThing.__init__(self)
|
|
self.child = child
|
|
self.declarePre = declarePre or pre
|
|
self.declarePost = declarePost or post
|
|
self.definePre = definePre or pre
|
|
self.definePost = definePost or post
|
|
self.declareOnly = declareOnly
|
|
self.defineOnly = defineOnly
|
|
self.reindent = reindent
|
|
def declare(self):
|
|
if self.defineOnly:
|
|
return ''
|
|
decl = self.child.declare()
|
|
if self.reindent:
|
|
# We don't use lineStartDetector because we don't want to
|
|
# insert whitespace at the beginning of our _first_ line.
|
|
decl = stripTrailingWhitespace(
|
|
decl.replace("\n", "\n" + (" " * len(self.declarePre))))
|
|
return self.declarePre + decl + self.declarePost
|
|
def define(self):
|
|
if self.declareOnly:
|
|
return ''
|
|
defn = self.child.define()
|
|
if self.reindent:
|
|
# We don't use lineStartDetector because we don't want to
|
|
# insert whitespace at the beginning of our _first_ line.
|
|
defn = stripTrailingWhitespace(
|
|
defn.replace("\n", "\n" + (" " * len(self.definePre))))
|
|
return self.definePre + defn + self.definePost
|
|
|
|
class CGNamespace(CGWrapper):
|
|
def __init__(self, namespace, child, declareOnly=False):
|
|
pre = "namespace %s {\n" % namespace
|
|
post = "} // namespace %s\n" % namespace
|
|
CGWrapper.__init__(self, child, pre=pre, post=post,
|
|
declareOnly=declareOnly)
|
|
@staticmethod
|
|
def build(namespaces, child, declareOnly=False):
|
|
"""
|
|
Static helper method to build multiple wrapped namespaces.
|
|
"""
|
|
if not namespaces:
|
|
return CGWrapper(child, declareOnly=declareOnly)
|
|
inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly)
|
|
return CGNamespace(namespaces[0], inner, declareOnly=declareOnly)
|
|
|
|
class CGIncludeGuard(CGWrapper):
|
|
"""
|
|
Generates include guards for a header.
|
|
"""
|
|
def __init__(self, prefix, child):
|
|
"""|prefix| is the filename without the extension."""
|
|
define = 'mozilla_dom_%s_h__' % prefix
|
|
CGWrapper.__init__(self, child,
|
|
declarePre='#ifndef %s\n#define %s\n\n' % (define, define),
|
|
declarePost='\n#endif // %s\n' % define)
|
|
|
|
class CGHeaders(CGWrapper):
|
|
"""
|
|
Generates the appropriate include statements.
|
|
"""
|
|
def __init__(self, descriptors, dictionaries, declareIncludes,
|
|
defineIncludes, child):
|
|
"""
|
|
Builds a set of includes to cover |descriptors|.
|
|
|
|
Also includes the files in |declareIncludes| in the header
|
|
file and the files in |defineIncludes| in the .cpp.
|
|
"""
|
|
|
|
# Determine the filenames for which we need headers.
|
|
interfaceDeps = [d.interface for d in descriptors]
|
|
ancestors = []
|
|
for iface in interfaceDeps:
|
|
while iface.parent:
|
|
ancestors.append(iface.parent)
|
|
iface = iface.parent
|
|
interfaceDeps.extend(ancestors)
|
|
bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
|
|
|
|
# Grab all the implementation declaration files we need.
|
|
implementationIncludes = set(d.headerFile for d in descriptors)
|
|
|
|
# Now find all the things we'll need as arguments because we
|
|
# need to wrap or unwrap them.
|
|
bindingHeaders = set()
|
|
for d in descriptors:
|
|
members = [m for m in d.interface.members]
|
|
signatures = [s for m in members if m.isMethod() for s in m.signatures()]
|
|
types = []
|
|
for s in signatures:
|
|
assert len(s) == 2
|
|
(returnType, arguments) = s
|
|
types.append(returnType)
|
|
types.extend([a.type for a in arguments])
|
|
|
|
attrs = [a for a in members if a.isAttr()]
|
|
types.extend([a.type for a in attrs])
|
|
|
|
for dictionary in dictionaries:
|
|
curDict = dictionary
|
|
while curDict:
|
|
types.extend([m.type for m in curDict.members])
|
|
curDict = curDict.parent
|
|
|
|
for t in types:
|
|
if t.unroll().isInterface():
|
|
if t.unroll().isSpiderMonkeyInterface():
|
|
bindingHeaders.add("jsfriendapi.h")
|
|
bindingHeaders.add("mozilla/dom/TypedArray.h")
|
|
else:
|
|
typeDesc = d.getDescriptor(t.unroll().inner.identifier.name)
|
|
if typeDesc is not None:
|
|
implementationIncludes.add(typeDesc.headerFile)
|
|
bindingHeaders.add(self.getDeclarationFilename(typeDesc.interface))
|
|
elif t.unroll().isDictionary():
|
|
bindingHeaders.add(self.getDeclarationFilename(t.unroll().inner))
|
|
|
|
declareIncludes = set(declareIncludes)
|
|
for d in dictionaries:
|
|
if d.parent:
|
|
declareIncludes.add(self.getDeclarationFilename(d.parent))
|
|
bindingHeaders.add(self.getDeclarationFilename(d))
|
|
|
|
# Let the machinery do its thing.
|
|
def _includeString(includes):
|
|
return ''.join(['#include "%s"\n' % i for i in includes]) + '\n'
|
|
CGWrapper.__init__(self, child,
|
|
declarePre=_includeString(sorted(declareIncludes)),
|
|
definePre=_includeString(sorted(set(defineIncludes) |
|
|
bindingIncludes |
|
|
bindingHeaders |
|
|
implementationIncludes)))
|
|
@staticmethod
|
|
def getDeclarationFilename(decl):
|
|
# Use our local version of the header, not the exported one, so that
|
|
# test bindings, which don't export, will work correctly.
|
|
basename = os.path.basename(decl.filename())
|
|
return basename.replace('.webidl', 'Binding.h')
|
|
|
|
class Argument():
|
|
"""
|
|
A class for outputting the type and name of an argument
|
|
"""
|
|
def __init__(self, argType, name):
|
|
self.argType = argType
|
|
self.name = name
|
|
def __str__(self):
|
|
return self.argType + ' ' + self.name
|
|
|
|
class CGAbstractMethod(CGThing):
|
|
"""
|
|
An abstract class for generating code for a method. Subclasses
|
|
should override definition_body to create the actual code.
|
|
|
|
descriptor is the descriptor for the interface the method is associated with
|
|
|
|
name is the name of the method as a string
|
|
|
|
returnType is the IDLType of the return value
|
|
|
|
args is a list of Argument objects
|
|
|
|
inline should be True to generate an inline method, whose body is
|
|
part of the declaration.
|
|
|
|
static should be True to generate a static method, which only has
|
|
a definition.
|
|
"""
|
|
def __init__(self, descriptor, name, returnType, args, inline=False, static=False):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
self.name = name
|
|
self.returnType = returnType
|
|
self.args = args
|
|
self.inline = inline
|
|
self.static = static
|
|
def _argstring(self):
|
|
return ', '.join([str(a) for a in self.args])
|
|
def _decorators(self):
|
|
decorators = []
|
|
if self.inline:
|
|
decorators.append('inline')
|
|
if self.static:
|
|
decorators.append('static')
|
|
decorators.append(self.returnType)
|
|
return ' '.join(decorators)
|
|
def declare(self):
|
|
if self.inline:
|
|
return self._define()
|
|
return "\n %s %s(%s);\n" % (self._decorators(), self.name, self._argstring())
|
|
def _define(self):
|
|
return self.definition_prologue() + "\n" + self.definition_body() + self.definition_epilogue()
|
|
def define(self):
|
|
return "" if self.inline else self._define()
|
|
def definition_prologue(self):
|
|
maybeNewline = " " if self.inline else "\n"
|
|
return "\n%s%s%s(%s)\n{" % (self._decorators(), maybeNewline,
|
|
self.name, self._argstring())
|
|
def definition_epilogue(self):
|
|
return "\n}\n"
|
|
def definition_body(self):
|
|
assert(False) # Override me!
|
|
|
|
class CGAbstractStaticMethod(CGAbstractMethod):
|
|
"""
|
|
Abstract base class for codegen of implementation-only (no
|
|
declaration) static methods.
|
|
"""
|
|
def __init__(self, descriptor, name, returnType, args):
|
|
CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
|
|
inline=False, static=True)
|
|
def declare(self):
|
|
# We only have implementation
|
|
return ""
|
|
|
|
class CGAbstractClassHook(CGAbstractStaticMethod):
|
|
"""
|
|
Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
|
|
'this' unwrapping as it assumes that the unwrapped type is always known.
|
|
"""
|
|
def __init__(self, descriptor, name, returnType, args):
|
|
CGAbstractStaticMethod.__init__(self, descriptor, name, returnType,
|
|
args)
|
|
|
|
def definition_body_prologue(self):
|
|
return """
|
|
%s* self = UnwrapDOMObject<%s>(obj);
|
|
""" % (self.descriptor.nativeType, self.descriptor.nativeType)
|
|
|
|
def definition_body(self):
|
|
return self.definition_body_prologue() + self.generate_code()
|
|
|
|
def generate_code(self):
|
|
# Override me
|
|
assert(False)
|
|
|
|
class CGAddPropertyHook(CGAbstractClassHook):
|
|
"""
|
|
A hook for addProperty, used to preserve our wrapper from GC.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
|
|
Argument('JSHandleId', 'id'), Argument('jsval*', 'vp')]
|
|
CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME,
|
|
'JSBool', args)
|
|
|
|
def generate_code(self):
|
|
return """
|
|
JSCompartment* compartment = js::GetObjectCompartment(obj);
|
|
xpc::CompartmentPrivate* priv =
|
|
static_cast<xpc::CompartmentPrivate*>(JS_GetCompartmentPrivate(compartment));
|
|
if (!priv->RegisterDOMExpandoObject(obj)) {
|
|
return false;
|
|
}
|
|
self->SetPreservingWrapper(true);
|
|
return true;"""
|
|
|
|
class CGClassFinalizeHook(CGAbstractClassHook):
|
|
"""
|
|
A hook for finalize, used to release our native object.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'obj')]
|
|
CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
|
|
'void', args)
|
|
|
|
def generate_code(self):
|
|
if self.descriptor.customFinalize:
|
|
return """ if (self) {
|
|
self->%s(%s);
|
|
}""" % (self.name, self.args[0].name)
|
|
clearWrapper = "self->ClearWrapper();\n " if self.descriptor.wrapperCache else ""
|
|
if self.descriptor.workers:
|
|
release = "self->Release();"
|
|
else:
|
|
assert self.descriptor.nativeIsISupports
|
|
release = """XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
|
|
if (rt) {
|
|
rt->DeferredRelease(reinterpret_cast<nsISupports*>(self));
|
|
} else {
|
|
NS_RELEASE(self);
|
|
}"""
|
|
return """
|
|
%s%s""" % (clearWrapper, release)
|
|
|
|
class CGClassTraceHook(CGAbstractClassHook):
|
|
"""
|
|
A hook to trace through our native object; used for GC and CC
|
|
"""
|
|
def __init__(self, descriptor):
|
|
args = [Argument('JSTracer*', 'trc'), Argument('JSObject*', 'obj')]
|
|
CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void',
|
|
args)
|
|
|
|
def generate_code(self):
|
|
return """ if (self) {
|
|
self->%s(%s);
|
|
}""" % (self.name, self.args[0].name)
|
|
|
|
class CGClassConstructHook(CGAbstractStaticMethod):
|
|
"""
|
|
JS-visible constructor for our objects
|
|
"""
|
|
def __init__(self, descriptor):
|
|
args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'), Argument('JS::Value*', 'vp')]
|
|
CGAbstractStaticMethod.__init__(self, descriptor, CONSTRUCT_HOOK_NAME,
|
|
'JSBool', args)
|
|
self._ctor = self.descriptor.interface.ctor()
|
|
|
|
def define(self):
|
|
if not self._ctor:
|
|
return ""
|
|
return CGAbstractStaticMethod.define(self)
|
|
|
|
def definition_body(self):
|
|
return self.generate_code()
|
|
|
|
def generate_code(self):
|
|
preamble = """
|
|
JSObject* obj = JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));
|
|
"""
|
|
if self.descriptor.workers:
|
|
preArgs = ["cx", "obj"]
|
|
else:
|
|
preamble += """
|
|
nsISupports* global;
|
|
xpc_qsSelfRef globalRef;
|
|
{
|
|
nsresult rv;
|
|
JS::Value val = OBJECT_TO_JSVAL(obj);
|
|
rv = xpc_qsUnwrapArg<nsISupports>(cx, val, &global, &globalRef.ptr, &val);
|
|
if (NS_FAILED(rv)) {
|
|
return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
|
|
}
|
|
}
|
|
"""
|
|
preArgs = ["global"]
|
|
|
|
name = self._ctor.identifier.name
|
|
nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
|
|
callGenerator = CGMethodCall(preArgs, nativeName, True,
|
|
self.descriptor, self._ctor)
|
|
return preamble + callGenerator.define();
|
|
|
|
class CGClassHasInstanceHook(CGAbstractStaticMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
|
|
Argument('const jsval*', 'v'), Argument('JSBool*', 'bp')]
|
|
CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME,
|
|
'JSBool', args)
|
|
|
|
def define(self):
|
|
if not self.descriptor.hasInstanceInterface:
|
|
return ""
|
|
return CGAbstractStaticMethod.define(self)
|
|
|
|
def definition_body(self):
|
|
return self.generate_code()
|
|
|
|
def generate_code(self):
|
|
return """ if (!v->isObject()) {
|
|
*bp = false;
|
|
return true;
|
|
}
|
|
|
|
jsval protov;
|
|
if (!JS_GetProperty(cx, obj, "prototype", &protov))
|
|
return false;
|
|
if (!protov.isObject()) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PROTOTYPE,
|
|
"%s");
|
|
return false;
|
|
}
|
|
JSObject *objProto = &protov.toObject();
|
|
|
|
JSObject* instance = &v->toObject();
|
|
JSObject* proto = JS_GetPrototype(instance);
|
|
while (proto) {
|
|
if (proto == objProto) {
|
|
*bp = true;
|
|
return true;
|
|
}
|
|
proto = JS_GetPrototype(proto);
|
|
}
|
|
|
|
nsISupports* native =
|
|
nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, instance);
|
|
nsCOMPtr<%s> qiResult = do_QueryInterface(native);
|
|
*bp = !!qiResult;
|
|
return true;
|
|
""" % (self.descriptor.name, self.descriptor.hasInstanceInterface)
|
|
|
|
def isChromeOnly(m):
|
|
return m.getExtendedAttribute("ChromeOnly")
|
|
|
|
class PropertyDefiner:
|
|
"""
|
|
A common superclass for defining things on prototype objects.
|
|
|
|
Subclasses should implement generateArray to generate the actual arrays of
|
|
things we're defining. They should also set self.chrome to the list of
|
|
things exposed to chrome and self.regular to the list of things exposed to
|
|
web pages. self.chrome must be a superset of self.regular but also include
|
|
all the ChromeOnly stuff.
|
|
"""
|
|
def __init__(self, descriptor, name):
|
|
self.descriptor = descriptor
|
|
self.name = name
|
|
# self.prefCacheData will store an array of (prefname, bool*)
|
|
# pairs for our bool var caches. generateArray will fill it
|
|
# in as needed.
|
|
self.prefCacheData = []
|
|
def hasChromeOnly(self):
|
|
return len(self.chrome) > len(self.regular)
|
|
def hasNonChromeOnly(self):
|
|
return len(self.regular) > 0
|
|
def variableName(self, chrome):
|
|
if chrome and self.hasChromeOnly():
|
|
return "sChrome" + self.name
|
|
if self.hasNonChromeOnly():
|
|
return "s" + self.name
|
|
return "NULL"
|
|
def usedForXrays(self, chrome):
|
|
# We only need Xrays for methods and attributes. And we only need them
|
|
# for the non-chrome ones if we have no chromeonly things. Otherwise
|
|
# (we have chromeonly attributes) we need Xrays for the chrome
|
|
# methods/attributes. Finally, in workers there are no Xrays.
|
|
return ((self.name is "Methods" or self.name is "Attributes") and
|
|
chrome == self.hasChromeOnly() and
|
|
not self.descriptor.workers)
|
|
|
|
def __str__(self):
|
|
# We only need to generate id arrays for things that will end
|
|
# up used via ResolveProperty or EnumerateProperties.
|
|
str = self.generateArray(self.regular, self.variableName(False),
|
|
self.usedForXrays(False))
|
|
if self.hasChromeOnly():
|
|
str += self.generateArray(self.chrome, self.variableName(True),
|
|
self.usedForXrays(True))
|
|
return str
|
|
|
|
@staticmethod
|
|
def getControllingPref(interfaceMember):
|
|
prefName = interfaceMember.getExtendedAttribute("Pref")
|
|
if prefName is None:
|
|
return None
|
|
# It's a list of strings
|
|
assert(len(prefName) is 1)
|
|
assert(prefName[0] is not None)
|
|
return prefName[0]
|
|
|
|
def generatePrefableArray(self, array, name, specTemplate, specTerminator,
|
|
specType, getPref, getDataTuple, doIdArrays):
|
|
"""
|
|
This method generates our various arrays.
|
|
|
|
array is an array of interface members as passed to generateArray
|
|
|
|
name is the name as passed to generateArray
|
|
|
|
specTemplate is a template for each entry of the spec array
|
|
|
|
specTerminator is a terminator for the spec array (inserted every time
|
|
our controlling pref changes and at the end of the array)
|
|
|
|
specType is the actual typename of our spec
|
|
|
|
getPref is a callback function that takes an array entry and returns
|
|
the corresponding pref value.
|
|
|
|
getDataTuple is a callback function that takes an array entry and
|
|
returns a tuple suitable for substitution into specTemplate.
|
|
"""
|
|
|
|
# We want to generate a single list of specs, but with specTerminator
|
|
# inserted at every point where the pref name controlling the member
|
|
# changes. That will make sure the order of the properties as exposed
|
|
# on the interface and interface prototype objects does not change when
|
|
# pref control is added to members while still allowing us to define all
|
|
# the members in the smallest number of JSAPI calls.
|
|
assert(len(array) is not 0)
|
|
lastPref = getPref(array[0]) # So we won't put a specTerminator
|
|
# at the very front of the list.
|
|
specs = []
|
|
prefableSpecs = []
|
|
if doIdArrays:
|
|
prefableIds = []
|
|
|
|
prefableTemplate = ' { true, &%s[%d] }'
|
|
prefCacheTemplate = '&%s[%d].enabled'
|
|
def switchToPref(props, pref):
|
|
# Remember the info about where our pref-controlled
|
|
# booleans live.
|
|
if pref is not None:
|
|
props.prefCacheData.append(
|
|
(pref, prefCacheTemplate % (name, len(prefableSpecs)))
|
|
)
|
|
# Set up pointers to the new sets of specs and ids
|
|
# inside prefableSpecs and prefableIds
|
|
prefableSpecs.append(prefableTemplate %
|
|
(name + "_specs", len(specs)))
|
|
|
|
switchToPref(self, lastPref)
|
|
|
|
for member in array:
|
|
curPref = getPref(member)
|
|
if lastPref != curPref:
|
|
# Terminate previous list
|
|
specs.append(specTerminator)
|
|
# And switch to our new pref
|
|
switchToPref(self, curPref)
|
|
lastPref = curPref
|
|
# And the actual spec
|
|
specs.append(specTemplate % getDataTuple(member))
|
|
specs.append(specTerminator)
|
|
prefableSpecs.append(" { false, NULL }");
|
|
|
|
arrays = (("static %s %s_specs[] = {\n" +
|
|
',\n'.join(specs) + "\n" +
|
|
"};\n\n" +
|
|
"static Prefable<%s> %s[] = {\n" +
|
|
',\n'.join(prefableSpecs) + "\n" +
|
|
"};\n\n") % (specType, name, specType, name))
|
|
if doIdArrays:
|
|
arrays += ("static jsid %s_ids[%i] = { JSID_VOID };\n\n" %
|
|
(name, len(specs)))
|
|
return arrays
|
|
|
|
|
|
# The length of a method is the maximum of the lengths of the
|
|
# argument lists of all its overloads.
|
|
def methodLength(method):
|
|
signatures = method.signatures()
|
|
return max([len(arguments) for (retType, arguments) in signatures])
|
|
|
|
class MethodDefiner(PropertyDefiner):
|
|
"""
|
|
A class for defining methods on a prototype object.
|
|
"""
|
|
def __init__(self, descriptor, name, static):
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
|
|
methods = [m for m in descriptor.interface.members if
|
|
m.isMethod() and m.isStatic() == static]
|
|
self.chrome = [{"name": m.identifier.name,
|
|
"length": methodLength(m),
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"pref": PropertyDefiner.getControllingPref(m) }
|
|
for m in methods]
|
|
self.regular = [{"name": m.identifier.name,
|
|
"length": methodLength(m),
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"pref": PropertyDefiner.getControllingPref(m) }
|
|
for m in methods if not isChromeOnly(m)]
|
|
if not descriptor.interface.parent and not static and not descriptor.workers:
|
|
self.chrome.append({"name": 'QueryInterface',
|
|
"length": 1,
|
|
"flags": "0",
|
|
"pref": None })
|
|
self.regular.append({"name": 'QueryInterface',
|
|
"length": 1,
|
|
"flags": "0",
|
|
"pref": None })
|
|
|
|
if static:
|
|
if not descriptor.interface.hasInterfaceObject():
|
|
# static methods go on the interface object
|
|
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
|
|
else:
|
|
if not descriptor.interface.hasInterfacePrototypeObject():
|
|
# non-static methods go on the interface prototype object
|
|
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
|
|
|
|
def generateArray(self, array, name, doIdArrays):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def pref(m):
|
|
return m["pref"]
|
|
|
|
def specData(m):
|
|
return (m["name"], m["name"], m["length"], m["flags"])
|
|
|
|
return self.generatePrefableArray(
|
|
array, name,
|
|
' JS_FN("%s", %s, %s, %s)',
|
|
' JS_FS_END',
|
|
'JSFunctionSpec',
|
|
pref, specData, doIdArrays)
|
|
|
|
class AttrDefiner(PropertyDefiner):
|
|
def __init__(self, descriptor, name):
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
self.name = name
|
|
self.chrome = [m for m in descriptor.interface.members if m.isAttr()]
|
|
self.regular = [m for m in self.chrome if not isChromeOnly(m)]
|
|
|
|
def generateArray(self, array, name, doIdArrays):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def flags(attr):
|
|
flags = "JSPROP_SHARED | JSPROP_ENUMERATE"
|
|
if generateNativeAccessors:
|
|
flags = "JSPROP_NATIVE_ACCESSORS | " + flags
|
|
elif attr.readonly:
|
|
return "JSPROP_READONLY | " + flags
|
|
return flags
|
|
|
|
def getter(attr):
|
|
return "get_" + attr.identifier.name
|
|
|
|
def setter(attr):
|
|
if attr.readonly:
|
|
return "NULL"
|
|
return "set_" + attr.identifier.name
|
|
|
|
def specData(attr):
|
|
return (attr.identifier.name, flags(attr), getter(attr),
|
|
setter(attr))
|
|
|
|
return self.generatePrefableArray(
|
|
array, name,
|
|
' { "%s", 0, %s, (JSPropertyOp)%s, (JSStrictPropertyOp)%s }',
|
|
' { 0, 0, 0, 0, 0 }',
|
|
'JSPropertySpec',
|
|
PropertyDefiner.getControllingPref, specData, doIdArrays)
|
|
|
|
class ConstDefiner(PropertyDefiner):
|
|
"""
|
|
A class for definining constants on the interface object
|
|
"""
|
|
def __init__(self, descriptor, name):
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
self.name = name
|
|
self.chrome = [m for m in descriptor.interface.members if m.isConst()]
|
|
self.regular = [m for m in self.chrome if not isChromeOnly(m)]
|
|
|
|
def generateArray(self, array, name, doIdArrays):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def specData(const):
|
|
return (const.identifier.name,
|
|
convertConstIDLValueToJSVal(const.value))
|
|
|
|
return self.generatePrefableArray(
|
|
array, name,
|
|
' { "%s", %s }',
|
|
' { 0, JSVAL_VOID }',
|
|
'ConstantSpec',
|
|
PropertyDefiner.getControllingPref, specData, doIdArrays)
|
|
|
|
class PropertyArrays():
|
|
def __init__(self, descriptor):
|
|
self.staticMethods = MethodDefiner(descriptor, "StaticMethods", True)
|
|
self.methods = MethodDefiner(descriptor, "Methods", False)
|
|
self.attrs = AttrDefiner(descriptor, "Attributes")
|
|
self.consts = ConstDefiner(descriptor, "Constants")
|
|
|
|
@staticmethod
|
|
def arrayNames():
|
|
return [ "staticMethods", "methods", "attrs", "consts" ]
|
|
|
|
@staticmethod
|
|
def xrayRelevantArrayNames():
|
|
return [ "methods", "attrs" ]
|
|
|
|
def hasChromeOnly(self):
|
|
return reduce(lambda b, a: b or getattr(self, a).hasChromeOnly(),
|
|
self.arrayNames(), False)
|
|
def variableNames(self, chrome):
|
|
names = {}
|
|
for array in self.arrayNames():
|
|
names[array] = getattr(self, array).variableName(chrome)
|
|
return names
|
|
def __str__(self):
|
|
define = ""
|
|
for array in self.arrayNames():
|
|
define += str(getattr(self, array))
|
|
return define
|
|
|
|
class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
|
"""
|
|
Generate the CreateInterfaceObjects method for an interface descriptor.
|
|
|
|
properties should be a PropertyArrays instance.
|
|
"""
|
|
def __init__(self, descriptor, properties):
|
|
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aGlobal'),
|
|
Argument('JSObject*', 'aReceiver')]
|
|
CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'JSObject*', args)
|
|
self.properties = properties
|
|
def definition_body(self):
|
|
protoChain = self.descriptor.prototypeChain
|
|
if len(protoChain) == 1:
|
|
getParentProto = "JS_GetObjectPrototype(aCx, aGlobal)"
|
|
else:
|
|
parentProtoName = self.descriptor.prototypeChain[-2]
|
|
getParentProto = ("%s::GetProtoObject(aCx, aGlobal, aReceiver)" %
|
|
toBindingNamespace(parentProtoName))
|
|
|
|
needInterfaceObject = self.descriptor.interface.hasInterfaceObject()
|
|
needInterfacePrototypeObject = self.descriptor.interface.hasInterfacePrototypeObject()
|
|
|
|
# if we don't need to create anything, why are we generating this?
|
|
assert needInterfaceObject or needInterfacePrototypeObject
|
|
|
|
idsToInit = []
|
|
# There is no need to init any IDs in workers, because worker bindings
|
|
# don't have Xrays.
|
|
if not self.descriptor.workers:
|
|
for var in self.properties.xrayRelevantArrayNames():
|
|
props = getattr(self.properties, var)
|
|
# We only have non-chrome ids to init if we have no chrome ids.
|
|
if props.hasChromeOnly():
|
|
idsToInit.append(props.variableName(True))
|
|
elif props.hasNonChromeOnly():
|
|
idsToInit.append(props.variableName(False))
|
|
if len(idsToInit) > 0:
|
|
initIds = CGList(
|
|
[CGGeneric("!InitIds(aCx, %s, %s_ids)" % (varname, varname)) for
|
|
varname in idsToInit], ' ||\n')
|
|
if len(idsToInit) > 1:
|
|
initIds = CGWrapper(initIds, pre="(", post=")", reindent=True)
|
|
initIds = CGList(
|
|
[CGGeneric("%s_ids[0] == JSID_VOID &&" % idsToInit[0]), initIds],
|
|
"\n")
|
|
initIds = CGWrapper(initIds, pre="if (", post=") {", reindent=True)
|
|
initIds = CGList(
|
|
[initIds,
|
|
CGGeneric((" %s_ids[0] = JSID_VOID;\n"
|
|
" return NULL;") % idsToInit[0]),
|
|
CGGeneric("}")],
|
|
"\n")
|
|
else:
|
|
initIds = None
|
|
|
|
prefCacheData = []
|
|
for var in self.properties.arrayNames():
|
|
props = getattr(self.properties, var)
|
|
prefCacheData.extend(props.prefCacheData)
|
|
if len(prefCacheData) is not 0:
|
|
prefCacheData = [
|
|
CGGeneric('Preferences::AddBoolVarCache(%s, "%s");' % (ptr, pref)) for
|
|
(pref, ptr) in prefCacheData]
|
|
prefCache = CGWrapper(CGIndenter(CGList(prefCacheData, "\n")),
|
|
pre=("static bool sPrefCachesInited = false;\n"
|
|
"if (!sPrefCachesInited) {\n"
|
|
" sPrefCachesInited = true;\n"),
|
|
post="\n}")
|
|
else:
|
|
prefCache = None
|
|
|
|
getParentProto = ("JSObject* parentProto = %s;\n"
|
|
"if (!parentProto) {\n"
|
|
" return NULL;\n"
|
|
"}") % getParentProto
|
|
|
|
needInterfaceObjectClass = (needInterfaceObject and
|
|
self.descriptor.hasInstanceInterface)
|
|
needConstructor = (needInterfaceObject and
|
|
not self.descriptor.hasInstanceInterface)
|
|
if self.descriptor.interface.ctor():
|
|
constructHook = CONSTRUCT_HOOK_NAME
|
|
constructArgs = methodLength(self.descriptor.interface.ctor())
|
|
else:
|
|
constructHook = "ThrowingConstructorWorkers" if self.descriptor.workers else "ThrowingConstructor"
|
|
constructArgs = 0
|
|
|
|
call = CGGeneric(("return dom::CreateInterfaceObjects(aCx, aGlobal, aReceiver, parentProto,\n"
|
|
" %s, %s, %s, %d,\n"
|
|
" %%(methods)s, %%(attrs)s, %%(consts)s, %%(staticMethods)s,\n"
|
|
" %s);") % (
|
|
"&PrototypeClass" if needInterfacePrototypeObject else "NULL",
|
|
"&InterfaceObjectClass" if needInterfaceObjectClass else "NULL",
|
|
constructHook if needConstructor else "NULL",
|
|
constructArgs,
|
|
'"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "NULL"))
|
|
|
|
if self.properties.hasChromeOnly():
|
|
if self.descriptor.workers:
|
|
accessCheck = "mozilla::dom::workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker()"
|
|
else:
|
|
accessCheck = "xpc::AccessCheck::isChrome(js::GetObjectCompartment(aGlobal))"
|
|
accessCheck = "if (" + accessCheck + ") {\n"
|
|
chrome = CGWrapper(CGGeneric((CGIndenter(call).define() % self.properties.variableNames(True))),
|
|
pre=accessCheck, post="\n}")
|
|
else:
|
|
chrome = None
|
|
|
|
functionBody = CGList(
|
|
[CGGeneric(getParentProto), initIds, prefCache, chrome,
|
|
CGGeneric(call.define() % self.properties.variableNames(False))],
|
|
"\n\n")
|
|
return CGIndenter(functionBody).define()
|
|
|
|
class CGGetPerInterfaceObject(CGAbstractMethod):
|
|
"""
|
|
A method for getting a per-interface object (a prototype object or interface
|
|
constructor object).
|
|
"""
|
|
def __init__(self, descriptor, name, idPrefix=""):
|
|
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aGlobal'),
|
|
Argument('JSObject*', 'aReceiver')]
|
|
CGAbstractMethod.__init__(self, descriptor, name,
|
|
'JSObject*', args, inline=True)
|
|
self.id = idPrefix + "id::" + self.descriptor.name
|
|
def definition_body(self):
|
|
return """
|
|
|
|
/* aGlobal and aReceiver are usually the same, but they can be different
|
|
too. For example a sandbox often has an xray wrapper for a window as the
|
|
prototype of the sandbox's global. In that case aReceiver is the xray
|
|
wrapper and aGlobal is the sandbox's global.
|
|
*/
|
|
|
|
/* Make sure our global is sane. Hopefully we can remove this sometime */
|
|
if (!(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL)) {
|
|
return NULL;
|
|
}
|
|
/* Check to see whether the interface objects are already installed */
|
|
JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(aGlobal);
|
|
JSObject* cachedObject = protoOrIfaceArray[%s];
|
|
if (!cachedObject) {
|
|
protoOrIfaceArray[%s] = cachedObject = CreateInterfaceObjects(aCx, aGlobal, aReceiver);
|
|
}
|
|
|
|
/* cachedObject might _still_ be null, but that's OK */
|
|
return cachedObject;""" % (self.id, self.id)
|
|
|
|
class CGGetProtoObjectMethod(CGGetPerInterfaceObject):
|
|
"""
|
|
A method for getting the interface prototype object.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject",
|
|
"prototypes::")
|
|
def definition_body(self):
|
|
return """
|
|
/* Get the interface prototype object for this class. This will create the
|
|
object as needed. */""" + CGGetPerInterfaceObject.definition_body(self)
|
|
|
|
class CGGetConstructorObjectMethod(CGGetPerInterfaceObject):
|
|
"""
|
|
A method for getting the interface constructor object.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject",
|
|
"constructors::")
|
|
def definition_body(self):
|
|
return """
|
|
/* Get the interface object for this class. This will create the object as
|
|
needed. */""" + CGGetPerInterfaceObject.definition_body(self)
|
|
|
|
def CheckPref(descriptor, globalName, varName, retval, wrapperCache = None):
|
|
"""
|
|
Check whether bindings should be enabled for this descriptor. If not, set
|
|
varName to false and return retval.
|
|
"""
|
|
if not descriptor.prefable:
|
|
return ""
|
|
if wrapperCache:
|
|
wrapperCache = " %s->ClearIsDOMBinding();\n" % (wrapperCache)
|
|
else:
|
|
wrapperCache = ""
|
|
return """
|
|
{
|
|
XPCWrappedNativeScope* scope =
|
|
XPCWrappedNativeScope::FindInJSObjectScope(aCx, %s);
|
|
if (!scope) {
|
|
return %s;
|
|
}
|
|
|
|
if (!scope->ExperimentalBindingsEnabled()) {
|
|
%s %s = false;
|
|
return %s;
|
|
}
|
|
}
|
|
""" % (globalName, retval, wrapperCache, varName, retval)
|
|
|
|
class CGDefineDOMInterfaceMethod(CGAbstractMethod):
|
|
"""
|
|
A method for resolve hooks to try to lazily define the interface object for
|
|
a given interface.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aReceiver'),
|
|
Argument('bool*', 'aEnabled')]
|
|
CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'bool', args)
|
|
|
|
def declare(self):
|
|
if self.descriptor.workers:
|
|
return ''
|
|
return CGAbstractMethod.declare(self)
|
|
|
|
def define(self):
|
|
if self.descriptor.workers:
|
|
return ''
|
|
return CGAbstractMethod.define(self)
|
|
|
|
def definition_body(self):
|
|
if self.descriptor.interface.hasInterfacePrototypeObject():
|
|
# We depend on GetProtoObject defining an interface constructor
|
|
# object as needed.
|
|
getter = "GetProtoObject"
|
|
else:
|
|
getter = "GetConstructorObject"
|
|
|
|
return (" JSObject* global = JS_GetGlobalForObject(aCx, aReceiver);\n" +
|
|
CheckPref(self.descriptor, "global", "*aEnabled", "false") +
|
|
"""
|
|
*aEnabled = true;
|
|
return !!%s(aCx, global, aReceiver);""" % (getter))
|
|
|
|
class CGWrapMethod(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
# XXX can we wrap if we don't have an interface prototype object?
|
|
assert descriptor.interface.hasInterfacePrototypeObject()
|
|
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
|
|
Argument(descriptor.nativeType + '*', 'aObject'),
|
|
Argument('bool*', 'aTriedToWrap')]
|
|
CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
|
|
|
|
def definition_body(self):
|
|
if self.descriptor.workers:
|
|
return """
|
|
*aTriedToWrap = true;
|
|
return aObject->GetJSObject();"""
|
|
|
|
return """
|
|
*aTriedToWrap = true;
|
|
|
|
JSObject* parent = WrapNativeParent(aCx, aScope, aObject->GetParentObject());
|
|
if (!parent) {
|
|
return NULL;
|
|
}
|
|
|
|
JSAutoEnterCompartment ac;
|
|
if (js::GetGlobalForObjectCrossCompartment(parent) != aScope) {
|
|
if (!ac.enter(aCx, parent)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
JSObject* global = JS_GetGlobalForObject(aCx, parent);
|
|
%s
|
|
JSObject* proto = GetProtoObject(aCx, global, global);
|
|
if (!proto) {
|
|
return NULL;
|
|
}
|
|
|
|
JSObject* obj = JS_NewObject(aCx, &Class.mBase, proto, parent);
|
|
if (!obj) {
|
|
return NULL;
|
|
}
|
|
|
|
js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject));
|
|
NS_ADDREF(aObject);
|
|
|
|
aObject->SetWrapper(obj);
|
|
|
|
return obj;""" % (CheckPref(self.descriptor, "global", "*aTriedToWrap", "NULL", "aObject"))
|
|
|
|
class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
# XXX can we wrap if we don't have an interface prototype object?
|
|
assert descriptor.interface.hasInterfacePrototypeObject()
|
|
args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
|
|
Argument(descriptor.nativeType + '*', 'aObject')]
|
|
CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
|
|
|
|
def definition_body(self):
|
|
return """
|
|
JSObject* global = JS_GetGlobalForObject(aCx, aScope);
|
|
JSObject* proto = GetProtoObject(aCx, global, global);
|
|
if (!proto) {
|
|
return NULL;
|
|
}
|
|
|
|
JSObject* obj = JS_NewObject(aCx, &Class.mBase, proto, global);
|
|
if (!obj) {
|
|
return NULL;
|
|
}
|
|
|
|
js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject));
|
|
NS_ADDREF(aObject);
|
|
|
|
return obj;"""
|
|
|
|
builtinNames = {
|
|
IDLType.Tags.bool: 'bool',
|
|
IDLType.Tags.int8: 'int8_t',
|
|
IDLType.Tags.int16: 'int16_t',
|
|
IDLType.Tags.int32: 'int32_t',
|
|
IDLType.Tags.int64: 'int64_t',
|
|
IDLType.Tags.uint8: 'uint8_t',
|
|
IDLType.Tags.uint16: 'uint16_t',
|
|
IDLType.Tags.uint32: 'uint32_t',
|
|
IDLType.Tags.uint64: 'uint64_t',
|
|
IDLType.Tags.float: 'float',
|
|
IDLType.Tags.double: 'double'
|
|
}
|
|
|
|
class CastableObjectUnwrapper():
|
|
"""
|
|
A class for unwrapping an object named by the "source" argument
|
|
based on the passed-in descriptor and storing it in a variable
|
|
called by the name in the "target" argument.
|
|
|
|
codeOnFailure is the code to run if unwrapping fails.
|
|
"""
|
|
def __init__(self, descriptor, source, target, codeOnFailure):
|
|
assert descriptor.castable
|
|
self.substitution = { "type" : descriptor.nativeType,
|
|
"protoID" : "prototypes::id::" + descriptor.name,
|
|
"source" : source,
|
|
"target" : target,
|
|
"codeOnFailure" : codeOnFailure }
|
|
|
|
def __str__(self):
|
|
return string.Template(
|
|
"""{
|
|
nsresult rv = UnwrapObject<${protoID}, ${type}>(cx, ${source}, ${target});
|
|
if (NS_FAILED(rv)) {
|
|
${codeOnFailure}
|
|
}
|
|
}""").substitute(self.substitution)
|
|
|
|
class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
|
|
"""
|
|
As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
|
|
"""
|
|
def __init__(self, descriptor, source, target):
|
|
CastableObjectUnwrapper.__init__(self, descriptor, source, target,
|
|
"return Throw<%s>(cx, rv);" %
|
|
toStringBool(not descriptor.workers))
|
|
|
|
class CallbackObjectUnwrapper:
|
|
"""
|
|
A class for unwrapping objects implemented in JS.
|
|
|
|
|source| is the JSObject we want to use in native code.
|
|
|target| is an nsCOMPtr of the appropriate type in which we store the result.
|
|
"""
|
|
def __init__(self, descriptor, source, target, codeOnFailure=None):
|
|
if codeOnFailure is None:
|
|
codeOnFailure = ("return Throw<%s>(cx, rv);" %
|
|
toStringBool(not descriptor.workers))
|
|
self.descriptor = descriptor
|
|
self.substitution = { "nativeType" : descriptor.nativeType,
|
|
"source" : source,
|
|
"target" : target,
|
|
"codeOnFailure" : codeOnFailure }
|
|
|
|
def __str__(self):
|
|
if self.descriptor.workers:
|
|
return string.Template(
|
|
"${target} = ${source};"
|
|
).substitute(self.substitution)
|
|
|
|
return string.Template(
|
|
"""nsresult rv;
|
|
XPCCallContext ccx(JS_CALLER, cx);
|
|
if (!ccx.IsValid()) {
|
|
rv = NS_ERROR_XPC_BAD_CONVERT_JS;
|
|
${codeOnFailure}
|
|
}
|
|
|
|
const nsIID& iid = NS_GET_IID(${nativeType});
|
|
nsRefPtr<nsXPCWrappedJS> wrappedJS;
|
|
rv = nsXPCWrappedJS::GetNewOrUsed(ccx, ${source}, iid,
|
|
NULL, getter_AddRefs(wrappedJS));
|
|
if (NS_FAILED(rv) || !wrappedJS) {
|
|
${codeOnFailure}
|
|
}
|
|
|
|
// Use a temp nsCOMPtr for the null-check, because ${target} might be
|
|
// OwningNonNull, not an nsCOMPtr.
|
|
nsCOMPtr<${nativeType}> tmp = do_QueryObject(wrappedJS.get());
|
|
if (!tmp) {
|
|
${codeOnFailure}
|
|
}
|
|
${target} = tmp.forget();""").substitute(self.substitution)
|
|
|
|
def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
|
|
isDefinitelyObject=False,
|
|
isMember=False,
|
|
isOptional=False,
|
|
invalidEnumValueFatal=True):
|
|
"""
|
|
Get a template for converting a JS value to a native object based on the
|
|
given type and descriptor. If failureCode is given, then we're actually
|
|
testing whether we can convert the argument to the desired type. That
|
|
means that failures to convert due to the JS value being the wrong type of
|
|
value need to use failureCode instead of throwing exceptions. Failures to
|
|
convert that are due to JS exceptions (from toString or valueOf methods) or
|
|
out of memory conditions need to throw exceptions no matter what
|
|
failureCode is.
|
|
|
|
If isDefinitelyObject is True, that means we know the value
|
|
isObject() and we have no need to recheck that.
|
|
|
|
if isMember is True, we're being converted from a property of some
|
|
JS object, not from an actual method argument, so we can't rely on
|
|
our jsval being rooted or outliving us in any way.
|
|
|
|
If isOptional is true, then we are doing conversion of an optional
|
|
argument with no default value.
|
|
|
|
The return value from this function is a tuple consisting of four things:
|
|
|
|
1) A string representing the conversion code. This will have template
|
|
substitution performed on it as follows:
|
|
|
|
${val} replaced by an expression for the JS::Value in question
|
|
${valPtr} is a pointer to the JS::Value in question
|
|
${holderName} replaced by the holder's name, if any
|
|
${declName} replaced by the declaration's name
|
|
|
|
2) A CGThing representing the native C++ type we're converting to
|
|
(declType). This is allowed to be None if the conversion code is
|
|
supposed to be used as-is.
|
|
3) A CGThing representing the type of a "holder" (holderType) which will
|
|
hold a possible reference to the C++ thing whose type we returned in #1,
|
|
or None if no such holder is needed.
|
|
4) A boolean indicating whether the caller has to do optional-argument handling.
|
|
This will only be true if isOptional is true and if the returned template
|
|
expects both declType and holderType to be wrapped in Optional<>, with
|
|
${declName} and ${holderName} adjusted to point to the Value() of the
|
|
Optional, and Construct() calls to be made on the Optional<>s as needed.
|
|
|
|
${declName} must be in scope before the generated code is entered.
|
|
|
|
If holderType is not None then ${holderName} must be in scope
|
|
before the generated code is entered.
|
|
"""
|
|
|
|
# A helper function for dealing with failures due to the JS value being the
|
|
# wrong type of value
|
|
def onFailure(failureCode, isWorker):
|
|
return CGWrapper(CGGeneric(
|
|
failureCode or
|
|
"return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);"
|
|
% toStringBool(isWorker)), post="\n")
|
|
|
|
# A helper function for wrapping up the template body for
|
|
# possibly-nullable objecty stuff
|
|
def wrapObjectTemplate(templateBody, isDefinitelyObject, type,
|
|
codeToSetNull, isWorker, failureCode=None):
|
|
if not isDefinitelyObject:
|
|
# Handle the non-object cases by wrapping up the whole
|
|
# thing in an if cascade.
|
|
templateBody = (
|
|
"if (${val}.isObject()) {\n" +
|
|
CGIndenter(CGGeneric(templateBody)).define() + "\n")
|
|
if type.nullable():
|
|
templateBody += (
|
|
"} else if (${val}.isNullOrUndefined()) {\n"
|
|
" %s;\n" % codeToSetNull)
|
|
templateBody += (
|
|
"} else {\n" +
|
|
CGIndenter(onFailure(failureCode, isWorker)).define() +
|
|
"}")
|
|
return templateBody
|
|
|
|
if type.isArray():
|
|
raise TypeError("Can't handle array arguments yet")
|
|
|
|
if type.isSequence():
|
|
if isMember:
|
|
# XXXbz we probably _could_ handle this; we just have to be careful
|
|
# with reallocation behavior for arrays. In particular, if we have
|
|
# a return value that's a sequence of dictionaries of sequences,
|
|
# that will cause us to have an nsTArray containing objects with
|
|
# nsAutoTArray members, which is a recipe for badness as the
|
|
# outermost array is resized.
|
|
raise TypeError("Can't handle unrooted sequences")
|
|
if failureCode is not None:
|
|
raise TypeError("Can't handle sequences when failureCode is not None")
|
|
nullable = type.nullable();
|
|
# Be very careful not to change "type": we need it later
|
|
if nullable:
|
|
elementType = type.inner.inner
|
|
else:
|
|
elementType = type.inner
|
|
# We don't know anything about the object-ness of the things
|
|
# we wrap, so don't pass through isDefinitelyObject
|
|
(elementTemplate, elementDeclType,
|
|
elementHolderType, dealWithOptional) = getJSToNativeConversionTemplate(
|
|
elementType, descriptorProvider, isMember=True)
|
|
if dealWithOptional:
|
|
raise TypeError("Shouldn't have optional things in sequences")
|
|
if elementHolderType is not None:
|
|
raise TypeError("Shouldn't need holders for sequences")
|
|
|
|
typeName = CGWrapper(elementDeclType, pre="Sequence< ", post=" >")
|
|
if nullable:
|
|
typeName = CGWrapper(typeName, pre="Nullable< ", post=" >")
|
|
arrayRef = "${declName}.Value()"
|
|
else:
|
|
arrayRef = "${declName}"
|
|
# If we're optional, the const will come from the Optional
|
|
mutableTypeName = typeName
|
|
if not isOptional:
|
|
typeName = CGWrapper(typeName, pre="const ")
|
|
|
|
templateBody = ("""JSObject* seq = &${val}.toObject();\n
|
|
if (!IsArrayLike(cx, seq)) {
|
|
return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
|
|
}
|
|
uint32_t length;
|
|
// JS_GetArrayLength actually works on all objects
|
|
if (!JS_GetArrayLength(cx, seq, &length)) {
|
|
return false;
|
|
}
|
|
Sequence< %s > &arr = const_cast< Sequence< %s >& >(%s);
|
|
if (!arr.SetCapacity(length)) {
|
|
return Throw<%s>(cx, NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
jsval temp;
|
|
if (!JS_GetElement(cx, seq, i, &temp)) {
|
|
return false;
|
|
}
|
|
""" % (toStringBool(descriptorProvider.workers),
|
|
elementDeclType.define(),
|
|
elementDeclType.define(),
|
|
arrayRef,
|
|
toStringBool(descriptorProvider.workers)))
|
|
|
|
templateBody += CGIndenter(CGGeneric(
|
|
string.Template(elementTemplate).substitute(
|
|
{
|
|
"val" : "temp",
|
|
"valPtr": "&temp",
|
|
"declName" : "(*arr.AppendElement())"
|
|
}
|
|
))).define()
|
|
|
|
templateBody += "\n}"
|
|
templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject,
|
|
type,
|
|
"const_cast< %s & >(${declName}).SetNull()" % mutableTypeName.define(),
|
|
descriptorProvider.workers)
|
|
return (templateBody, typeName, None, isOptional)
|
|
|
|
if type.isGeckoInterface():
|
|
descriptor = descriptorProvider.getDescriptor(
|
|
type.unroll().inner.identifier.name)
|
|
# This is an interface that we implement as a concrete class
|
|
# or an XPCOM interface.
|
|
|
|
# Allow null pointers for nullable types and old-binding classes
|
|
argIsPointer = type.nullable() or type.unroll().inner.isExternal()
|
|
|
|
# Sequences and non-worker callbacks have to hold a strong ref to the
|
|
# thing being passed down.
|
|
forceOwningType = (descriptor.interface.isCallback() and
|
|
not descriptor.workers) or isMember
|
|
|
|
typeName = descriptor.nativeType
|
|
typePtr = typeName + "*"
|
|
|
|
# Compute a few things:
|
|
# - declType is the type we want to return as the first element of our
|
|
# tuple.
|
|
# - holderType is the type we want to return as the third element
|
|
# of our tuple.
|
|
|
|
# Set up some sensible defaults for these things insofar as we can.
|
|
holderType = None
|
|
if argIsPointer:
|
|
if forceOwningType:
|
|
declType = "nsRefPtr<" + typeName + ">"
|
|
else:
|
|
declType = typePtr
|
|
else:
|
|
if forceOwningType:
|
|
declType = "OwningNonNull<" + typeName + ">"
|
|
else:
|
|
declType = "NonNull<" + typeName + ">"
|
|
|
|
templateBody = ""
|
|
if descriptor.castable:
|
|
if descriptor.prefable:
|
|
raise TypeError("We don't support prefable castable object "
|
|
"arguments (like %s), because we don't know "
|
|
"how to handle them being preffed off" %
|
|
descriptor.interface.identifier.name)
|
|
if failureCode is not None:
|
|
templateBody += str(CastableObjectUnwrapper(
|
|
descriptor,
|
|
"&${val}.toObject()",
|
|
"${declName}",
|
|
failureCode))
|
|
else:
|
|
templateBody += str(FailureFatalCastableObjectUnwrapper(
|
|
descriptor,
|
|
"&${val}.toObject()",
|
|
"${declName}"))
|
|
elif descriptor.interface.isCallback():
|
|
templateBody += str(CallbackObjectUnwrapper(
|
|
descriptor,
|
|
"&${val}.toObject()",
|
|
"${declName}",
|
|
codeOnFailure=failureCode))
|
|
elif descriptor.workers:
|
|
templateBody += "${declName} = &${val}.toObject();"
|
|
else:
|
|
# External interface. We always have a holder for these,
|
|
# because we don't actually know whether we have to addref
|
|
# when unwrapping or not. So we just pass an
|
|
# getter_AddRefs(nsRefPtr) to XPConnect and if we'll need
|
|
# a release it'll put a non-null pointer in there.
|
|
if forceOwningType:
|
|
# Don't return a holderType in this case; our declName
|
|
# will just own stuff.
|
|
templateBody += "nsRefPtr<" + typeName + "> ${holderName};\n"
|
|
else:
|
|
holderType = "nsRefPtr<" + typeName + ">"
|
|
templateBody += (
|
|
"jsval tmpVal = ${val};\n" +
|
|
typePtr + " tmp;\n"
|
|
"if (NS_FAILED(xpc_qsUnwrapArg<" + typeName + ">(cx, ${val}, &tmp, static_cast<" + typeName + "**>(getter_AddRefs(${holderName})), &tmpVal))) {\n")
|
|
templateBody += CGIndenter(onFailure(failureCode,
|
|
descriptor.workers)).define()
|
|
templateBody += ("}\n"
|
|
"MOZ_ASSERT(tmp);\n")
|
|
|
|
if not isDefinitelyObject:
|
|
# Our tmpVal will go out of scope, so we can't rely on it
|
|
# for rooting
|
|
templateBody += (
|
|
"if (tmpVal != ${val} && !${holderName}) {\n"
|
|
" // We have to have a strong ref, because we got this off\n"
|
|
" // some random object that might get GCed\n"
|
|
" ${holderName} = tmp;\n"
|
|
"}\n")
|
|
|
|
# And store our tmp, before it goes out of scope.
|
|
templateBody += "${declName} = tmp;"
|
|
|
|
templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject,
|
|
type, "${declName} = NULL",
|
|
descriptor.workers, failureCode)
|
|
|
|
declType = CGGeneric(declType)
|
|
if holderType is not None:
|
|
holderType = CGGeneric(holderType)
|
|
return (templateBody, declType, holderType, isOptional)
|
|
|
|
if type.isSpiderMonkeyInterface():
|
|
if isMember:
|
|
raise TypeError("Can't handle member arraybuffers or "
|
|
"arraybuffer views because making sure all the "
|
|
"objects are properly rooted is hard")
|
|
name = type.name
|
|
if type.isArrayBuffer():
|
|
jsname = "ArrayBufferObject"
|
|
elif type.isArrayBufferView():
|
|
jsname = "ArrayBufferViewObject"
|
|
else:
|
|
jsname = type.name
|
|
|
|
# By default, we use a Maybe<> to hold our typed array. And in the optional
|
|
# non-nullable case we want to pass Optional<TypedArray> to consumers, not
|
|
# Optional<NonNull<TypedArray> >, so jump though some hoops to do that.
|
|
holderType = "Maybe<%s>" % name
|
|
constructLoc = "${holderName}"
|
|
constructMethod = "construct"
|
|
if type.nullable():
|
|
if isOptional:
|
|
declType = "const Optional<" + name + "*>"
|
|
else:
|
|
declType = name + "*"
|
|
else:
|
|
if isOptional:
|
|
declType = "const Optional<" + name + ">"
|
|
# We don't need a holder in this case
|
|
holderType = None
|
|
constructLoc = "(const_cast<Optional<" + name + ">& >(${declName}))"
|
|
constructMethod = "Construct"
|
|
else:
|
|
declType = "NonNull<" + name + ">"
|
|
template = (
|
|
"if (!JS_Is%s(&${val}.toObject(), cx)) {\n"
|
|
" %s" # No newline here because onFailure() handles that
|
|
"}\n"
|
|
"%s.%s(cx, &${val}.toObject());\n" %
|
|
(jsname, onFailure(failureCode, descriptorProvider.workers).define(),
|
|
constructLoc, constructMethod))
|
|
nullableTarget = ""
|
|
if type.nullable():
|
|
if isOptional:
|
|
mutableDecl = "(const_cast<Optional<" + name + "*>& >(${declName}))"
|
|
template += "%s.Construct();\n" % mutableDecl
|
|
nullableTarget = "%s.Value()" % mutableDecl
|
|
else:
|
|
nullableTarget = "${declName}"
|
|
template += "%s = ${holderName}.addr();" % nullableTarget
|
|
elif not isOptional:
|
|
template += "${declName} = ${holderName}.addr();"
|
|
template = wrapObjectTemplate(template, isDefinitelyObject, type,
|
|
"%s = NULL" % nullableTarget,
|
|
descriptorProvider.workers,
|
|
failureCode)
|
|
|
|
if holderType is not None:
|
|
holderType = CGGeneric(holderType)
|
|
# We handle all the optional stuff ourselves; no need for caller to do it.
|
|
return (template, CGGeneric(declType), holderType, False)
|
|
|
|
if type.isString():
|
|
# XXXbz Need to figure out string behavior based on extended args? Also, how to
|
|
# detect them?
|
|
|
|
# For nullable strings that are not otherwise annotated, null
|
|
# and undefined become null strings.
|
|
if type.nullable():
|
|
nullBehavior = "eNull"
|
|
undefinedBehavior = "eNull"
|
|
else:
|
|
nullBehavior = "eStringify"
|
|
undefinedBehavior = "eStringify"
|
|
|
|
if isMember:
|
|
# We have to make a copy, because our jsval may well not
|
|
# live as long as our string needs to.
|
|
declType = CGGeneric("nsString")
|
|
return (
|
|
"{\n"
|
|
" nsDependentString str;\n"
|
|
" if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, str)) {\n"
|
|
" return false;\n"
|
|
" }\n"
|
|
" ${declName} = str;\n"
|
|
"}\n" %
|
|
(nullBehavior, undefinedBehavior),
|
|
declType, None,
|
|
isOptional)
|
|
|
|
if isOptional:
|
|
declType = "Optional<nsAString>"
|
|
else:
|
|
declType = "NonNull<nsAString>"
|
|
return (
|
|
"if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, ${holderName})) {\n"
|
|
" return false;\n"
|
|
"}\n"
|
|
"const_cast<%s&>(${declName}) = &${holderName};" %
|
|
(nullBehavior, undefinedBehavior, declType),
|
|
CGGeneric("const " + declType), CGGeneric("nsDependentString"),
|
|
# No need to deal with Optional here; we have handled it already
|
|
False)
|
|
|
|
if type.isEnum():
|
|
if type.nullable():
|
|
raise TypeError("We don't support nullable enumerated arguments "
|
|
"yet")
|
|
enum = type.inner.identifier.name
|
|
return (
|
|
"{\n"
|
|
" bool ok;\n"
|
|
" int index = FindEnumStringIndex(cx, ${val}, %(values)s, &ok);\n"
|
|
" if (!ok) {\n"
|
|
" return false;\n"
|
|
" }\n"
|
|
" if (index < 0) {\n"
|
|
" return %(failureCode)s;\n"
|
|
" }\n"
|
|
" ${declName} = static_cast<%(enumtype)s>(index);\n"
|
|
"}" % { "enumtype" : enum,
|
|
"values" : enum + "Values::strings",
|
|
"failureCode" : "Throw<false>(cx, NS_ERROR_XPC_BAD_CONVERT_JS)" if invalidEnumValueFatal else "true" },
|
|
CGGeneric(enum), None, isOptional)
|
|
|
|
if type.isCallback():
|
|
if isMember:
|
|
raise TypeError("Can't handle member callbacks; need to sort out "
|
|
"rooting issues")
|
|
# XXXbz we're going to assume that callback types are always
|
|
# nullable and always have [TreatNonCallableAsNull] for now.
|
|
return (
|
|
"if (${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())) {\n"
|
|
" ${declName} = &${val}.toObject();\n"
|
|
"} else {\n"
|
|
" ${declName} = NULL;\n"
|
|
"}", CGGeneric("JSObject*"), None, isOptional)
|
|
|
|
if type.isAny():
|
|
if isMember:
|
|
raise TypeError("Can't handle member 'any'; need to sort out "
|
|
"rooting issues")
|
|
return ("${declName} = ${val};", CGGeneric("JS::Value"), None, isOptional)
|
|
|
|
if type.isObject():
|
|
if isMember:
|
|
raise TypeError("Can't handle member 'object'; need to sort out "
|
|
"rooting issues")
|
|
template = wrapObjectTemplate("${declName} = &${val}.toObject();",
|
|
isDefinitelyObject, type,
|
|
"${declName} = NULL",
|
|
descriptorProvider.workers, failureCode)
|
|
if type.nullable():
|
|
declType = CGGeneric("JSObject*")
|
|
else:
|
|
declType = CGGeneric("NonNull<JSObject>")
|
|
return (template, declType, None, isOptional)
|
|
|
|
if type.isDictionary():
|
|
if failureCode is not None:
|
|
raise TypeError("Can't handle dictionaries when failureCode is not None")
|
|
|
|
if type.nullable():
|
|
typeName = CGDictionary.makeDictionaryName(type.inner.inner,
|
|
descriptorProvider.workers)
|
|
actualTypeName = "Nullable<%s>" % typeName
|
|
selfRef = "const_cast<%s&>(${declName}).SetValue()" % actualTypeName
|
|
else:
|
|
typeName = CGDictionary.makeDictionaryName(type.inner,
|
|
descriptorProvider.workers)
|
|
actualTypeName = typeName
|
|
selfRef = "${declName}"
|
|
|
|
declType = CGGeneric(actualTypeName)
|
|
|
|
# If we're optional or a member of something else, the const
|
|
# will come from the Optional or our container.
|
|
if not isOptional and not isMember:
|
|
declType = CGWrapper(declType, pre="const ")
|
|
selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef)
|
|
|
|
template = wrapObjectTemplate("if (!%s.Init(cx, &${val}.toObject())) {\n"
|
|
" return false;\n"
|
|
"}" % selfRef,
|
|
isDefinitelyObject, type,
|
|
("const_cast<%s&>(${declName}).SetNull()" %
|
|
actualTypeName),
|
|
descriptorProvider.workers, None)
|
|
|
|
return (template, declType, None, isOptional)
|
|
|
|
if not type.isPrimitive():
|
|
raise TypeError("Need conversion for argument type '%s'" % type)
|
|
|
|
# XXXbz need to add support for [EnforceRange] and [Clamp]
|
|
typeName = builtinNames[type.tag()]
|
|
if type.nullable():
|
|
return ("if (${val}.isNullOrUndefined()) {\n"
|
|
" ${declName}.SetNull();\n"
|
|
"} else if (!ValueToPrimitive<" + typeName + ">(cx, ${val}, &${declName}.SetValue())) {\n"
|
|
" return false;\n"
|
|
"}", CGGeneric("Nullable<" + typeName + ">"), None, isOptional)
|
|
else:
|
|
return ("if (!ValueToPrimitive<" + typeName + ">(cx, ${val}, &${declName})) {\n"
|
|
" return false;\n"
|
|
"}", CGGeneric(typeName), None, isOptional)
|
|
|
|
def instantiateJSToNativeConversionTemplate(templateTuple, replacements,
|
|
argcAndIndex=None):
|
|
"""
|
|
Take a tuple as returned by getJSToNativeConversionTemplate and a set of
|
|
replacements as required by the strings in such a tuple, and generate code
|
|
to convert into stack C++ types.
|
|
|
|
If argcAndIndex is not None it must be a dict that can be used to
|
|
replace ${argc} and ${index}, where ${index} is the index of this
|
|
argument (0-based) and ${argc} is the total number of arguments.
|
|
"""
|
|
(templateBody, declType, holderType, dealWithOptional) = templateTuple
|
|
|
|
if dealWithOptional and argcAndIndex is None:
|
|
raise TypeError("Have to deal with optional things, but don't know how")
|
|
if argcAndIndex is not None and declType is None:
|
|
raise TypeError("Need to predeclare optional things, so they will be "
|
|
"outside the check for big enough arg count!");
|
|
|
|
result = CGList([], "\n")
|
|
# Make a copy of "replacements" since we may be about to start modifying it
|
|
replacements = dict(replacements)
|
|
originalHolderName = replacements["holderName"]
|
|
if holderType is not None:
|
|
if dealWithOptional:
|
|
replacements["holderName"] = (
|
|
"const_cast< %s & >(%s.Value())" %
|
|
(holderType.define(), originalHolderName))
|
|
mutableHolderType = CGWrapper(holderType, pre="Optional< ", post=" >")
|
|
holderType = CGWrapper(mutableHolderType, pre="const ")
|
|
result.append(
|
|
CGList([holderType, CGGeneric(" "),
|
|
CGGeneric(originalHolderName),
|
|
CGGeneric(";")]))
|
|
|
|
originalDeclName = replacements["declName"]
|
|
if declType is not None:
|
|
if dealWithOptional:
|
|
replacements["declName"] = (
|
|
"const_cast< %s & >(%s.Value())" %
|
|
(declType.define(), originalDeclName))
|
|
mutableDeclType = CGWrapper(declType, pre="Optional< ", post=" >")
|
|
declType = CGWrapper(mutableDeclType, pre="const ")
|
|
result.append(
|
|
CGList([declType, CGGeneric(" "),
|
|
CGGeneric(originalDeclName),
|
|
CGGeneric(";")]))
|
|
|
|
conversion = CGGeneric(
|
|
string.Template(templateBody).substitute(replacements)
|
|
)
|
|
|
|
if argcAndIndex is not None:
|
|
if dealWithOptional:
|
|
declConstruct = CGIndenter(
|
|
CGGeneric("const_cast< %s &>(%s).Construct();" %
|
|
(mutableDeclType.define(), originalDeclName)))
|
|
if holderType is not None:
|
|
holderConstruct = CGIndenter(
|
|
CGGeneric("const_cast< %s &>(%s).Construct();" %
|
|
(mutableHolderType.define(), originalHolderName)))
|
|
else:
|
|
holderConstruct = None
|
|
else:
|
|
declConstruct = None
|
|
holderConstruct = None
|
|
|
|
conversion = CGList(
|
|
[CGGeneric(
|
|
string.Template("if (${index} < ${argc}) {").substitute(
|
|
argcAndIndex
|
|
)),
|
|
declConstruct,
|
|
holderConstruct,
|
|
CGIndenter(conversion),
|
|
CGGeneric("}")],
|
|
"\n")
|
|
|
|
result.append(conversion)
|
|
# Add an empty CGGeneric to get an extra newline after the argument
|
|
# conversion.
|
|
result.append(CGGeneric(""))
|
|
return result;
|
|
|
|
def convertConstIDLValueToJSVal(value):
|
|
if isinstance(value, IDLNullValue):
|
|
return "JSVAL_NULL"
|
|
tag = value.type.tag()
|
|
if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
|
|
IDLType.Tags.uint16, IDLType.Tags.int32]:
|
|
return "INT_TO_JSVAL(%s)" % (value.value)
|
|
if tag == IDLType.Tags.uint32:
|
|
return "UINT_TO_JSVAL(%s)" % (value.value)
|
|
if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]:
|
|
return "DOUBLE_TO_JSVAL(%s)" % (value.value)
|
|
if tag == IDLType.Tags.bool:
|
|
return "JSVAL_TRUE" if value.value else "JSVAL_FALSE"
|
|
if tag in [IDLType.Tags.float, IDLType.Tags.double]:
|
|
return "DOUBLE_TO_JSVAL(%s)" % (value.value)
|
|
raise TypeError("Const value of unhandled type: " + value.type)
|
|
|
|
def convertIDLDefaultValueToJSVal(value):
|
|
if value.type:
|
|
tag = value.type.tag()
|
|
if (tag == IDLType.Tags.domstring and
|
|
(not value.type.nullable() or not isinstance(value, IDLNullValue))):
|
|
assert False # Not implemented!
|
|
return convertConstIDLValueToJSVal(value)
|
|
|
|
class CGArgumentConverter(CGThing):
|
|
"""
|
|
A class that takes an IDL argument object, its index in the
|
|
argument list, and the argv and argc strings and generates code to
|
|
unwrap the argument to the right native type.
|
|
"""
|
|
def __init__(self, argument, index, argv, argc, descriptorProvider,
|
|
invalidEnumValueFatal=True):
|
|
CGThing.__init__(self)
|
|
self.argument = argument
|
|
# XXXbz should optional jsval args get JSVAL_VOID? What about
|
|
# others?
|
|
replacer = {
|
|
"index" : index,
|
|
"argc" : argc,
|
|
"argv" : argv
|
|
}
|
|
self.replacementVariables = {
|
|
"declName" : "arg%d" % index,
|
|
"holderName" : ("arg%d" % index) + "_holder"
|
|
}
|
|
if argument.optional and argument.defaultValue:
|
|
replacer["defaultValue"] = convertIDLDefaultValueToJSVal(argument.defaultValue)
|
|
self.replacementVariables["val"] = string.Template(
|
|
"(${index} < ${argc} ? ${argv}[${index}] : ${defaultValue})"
|
|
).substitute(replacer)
|
|
self.replacementVariables["valPtr"] = string.Template(
|
|
"(${index} < ${argc} ? &${argv}[${index}] : NULL)"
|
|
).substitute(replacer)
|
|
else:
|
|
self.replacementVariables["val"] = string.Template(
|
|
"${argv}[${index}]"
|
|
).substitute(replacer)
|
|
self.replacementVariables["valPtr"] = (
|
|
"&" + self.replacementVariables["val"])
|
|
self.descriptorProvider = descriptorProvider
|
|
if self.argument.optional and not self.argument.defaultValue:
|
|
self.argcAndIndex = replacer
|
|
else:
|
|
self.argcAndIndex = None
|
|
self.invalidEnumValueFatal = invalidEnumValueFatal
|
|
|
|
def define(self):
|
|
return instantiateJSToNativeConversionTemplate(
|
|
getJSToNativeConversionTemplate(self.argument.type,
|
|
self.descriptorProvider,
|
|
isOptional=(self.argcAndIndex is not None),
|
|
invalidEnumValueFatal=self.invalidEnumValueFatal),
|
|
self.replacementVariables,
|
|
self.argcAndIndex).define()
|
|
|
|
def getWrapTemplateForType(type, descriptorProvider, result, successCode,
|
|
isCreator):
|
|
"""
|
|
Reflect a C++ value stored in "result", of IDL type "type" into JS. The
|
|
"successCode" is the code to run once we have successfully done the
|
|
conversion. The resulting string should be used with string.Template, it
|
|
needs the following keys when substituting: jsvalPtr/jsvalRef/obj.
|
|
"""
|
|
haveSuccessCode = successCode is not None
|
|
if not haveSuccessCode:
|
|
successCode = "return true;"
|
|
|
|
def setValue(value, callWrapValue=False):
|
|
"""
|
|
Returns the code to set the jsval to value. If "callWrapValue" is true
|
|
JS_WrapValue will be called on the jsval.
|
|
"""
|
|
if not callWrapValue:
|
|
tail = successCode
|
|
elif haveSuccessCode:
|
|
tail = ("if (!JS_WrapValue(cx, ${jsvalPtr})) {\n" +
|
|
" return false;\n" +
|
|
"}\n" +
|
|
successCode)
|
|
else:
|
|
tail = "return JS_WrapValue(cx, ${jsvalPtr});"
|
|
return ("${jsvalRef} = %s;\n" +
|
|
tail) % (value)
|
|
|
|
def wrapAndSetPtr(wrapCall, failureCode=None):
|
|
"""
|
|
Returns the code to set the jsval by calling "wrapCall". "failureCode"
|
|
is the code to run if calling "wrapCall" fails
|
|
"""
|
|
if failureCode is None:
|
|
if not haveSuccessCode:
|
|
return "return " + wrapCall + ";"
|
|
failureCode = "return false;"
|
|
str = ("if (!%s) {\n" +
|
|
CGIndenter(CGGeneric(failureCode)).define() + "\n" +
|
|
"}\n" +
|
|
successCode) % (wrapCall)
|
|
return str
|
|
|
|
if type is None or type.isVoid():
|
|
return setValue("JSVAL_VOID")
|
|
|
|
if type.isArray():
|
|
raise TypeError("Can't handle array return values yet")
|
|
|
|
if type.isSequence():
|
|
if type.nullable():
|
|
# Nullable sequences are Nullable< nsTArray<T> >
|
|
return """
|
|
if (%s.IsNull()) {
|
|
%s
|
|
}
|
|
%s""" % (result, CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define(),
|
|
getWrapTemplateForType(type.inner, descriptorProvider,
|
|
"%s.Value()" % result, successCode,
|
|
isCreator))
|
|
|
|
# Now do non-nullable sequences. We use setting the element
|
|
# in the array as our succcess code because when we succeed in
|
|
# wrapping that's what we should do.
|
|
innerTemplate = wrapForType(
|
|
type.inner, descriptorProvider,
|
|
{
|
|
'result' : "%s[i]" % result,
|
|
'successCode': ("if (!JS_SetElement(cx, returnArray, i, &tmp)) {\n"
|
|
" return false;\n"
|
|
"}"),
|
|
'jsvalRef': "tmp",
|
|
'jsvalPtr': "&tmp",
|
|
'isCreator': isCreator
|
|
}
|
|
)
|
|
innerTemplate = CGIndenter(CGGeneric(innerTemplate)).define()
|
|
return ("""
|
|
uint32_t length = %s.Length();
|
|
JSObject *returnArray = JS_NewArrayObject(cx, length, NULL);
|
|
if (!returnArray) {
|
|
return false;
|
|
}
|
|
jsval tmp;
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
%s
|
|
}\n""" % (result, innerTemplate)) + setValue("JS::ObjectValue(*returnArray)")
|
|
|
|
if type.isGeckoInterface():
|
|
descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name)
|
|
if type.nullable():
|
|
wrappingCode = ("if (!%s) {\n" % (result) +
|
|
CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" +
|
|
"}\n")
|
|
else:
|
|
wrappingCode = ""
|
|
if descriptor.castable and not type.unroll().inner.isExternal():
|
|
if descriptor.wrapperCache:
|
|
wrapMethod = "WrapNewBindingObject"
|
|
else:
|
|
if not isCreator:
|
|
raise MethodNotCreatorError(descriptor.interface.identifier.name)
|
|
wrapMethod = "WrapNewBindingNonWrapperCachedObject"
|
|
wrap = "%s(cx, ${obj}, %s, ${jsvalPtr})" % (wrapMethod, result)
|
|
# We don't support prefable stuff in workers.
|
|
assert(not descriptor.prefable or not descriptor.workers)
|
|
if not descriptor.prefable:
|
|
# Non-prefable bindings can only fail to wrap as a new-binding object
|
|
# if they already threw an exception. Same thing for
|
|
# non-prefable bindings.
|
|
failed = ("MOZ_ASSERT(JS_IsExceptionPending(cx));\n" +
|
|
"return false;")
|
|
else:
|
|
# Try old-style wrapping for bindings which might be preffed off.
|
|
failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalPtr})" % result)
|
|
wrappingCode += wrapAndSetPtr(wrap, failed)
|
|
else:
|
|
if descriptor.notflattened:
|
|
getIID = "&NS_GET_IID(%s), " % descriptor.nativeType
|
|
else:
|
|
getIID = ""
|
|
wrap = "WrapObject(cx, ${obj}, %s, %s${jsvalPtr})" % (result, getIID)
|
|
wrappingCode += wrapAndSetPtr(wrap)
|
|
return wrappingCode
|
|
|
|
if type.isString():
|
|
if type.nullable():
|
|
return wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalPtr})" % result)
|
|
else:
|
|
return wrapAndSetPtr("xpc::NonVoidStringToJsval(cx, %s, ${jsvalPtr})" % result)
|
|
|
|
if type.isEnum():
|
|
if type.nullable():
|
|
raise TypeError("We don't support nullable enumerated return types "
|
|
"yet")
|
|
return """MOZ_ASSERT(uint32_t(%(result)s) < ArrayLength(%(strings)s));
|
|
JSString* %(resultStr)s = JS_NewStringCopyN(cx, %(strings)s[uint32_t(%(result)s)].value, %(strings)s[uint32_t(%(result)s)].length);
|
|
if (!%(resultStr)s) {
|
|
return false;
|
|
}
|
|
""" % { "result" : result,
|
|
"resultStr" : result + "_str",
|
|
"strings" : type.inner.identifier.name + "Values::strings" } + setValue("JS::StringValue(%s_str)" % result)
|
|
|
|
if type.isCallback() and not type.isInterface():
|
|
# XXXbz we're going to assume that callback types are always
|
|
# nullable and always have [TreatNonCallableAsNull] for now.
|
|
# See comments in WrapNewBindingObject explaining why we need
|
|
# to wrap here.
|
|
return setValue("JS::ObjectOrNullValue(%s)" % result, True)
|
|
|
|
if type.tag() == IDLType.Tags.any:
|
|
# See comments in WrapNewBindingObject explaining why we need
|
|
# to wrap here.
|
|
return setValue(result, True)
|
|
|
|
if type.isObject():
|
|
# See comments in WrapNewBindingObject explaining why we need
|
|
# to wrap here.
|
|
if type.nullable():
|
|
toValue = "JS::ObjectOrNullValue(%s)"
|
|
else:
|
|
toValue = "JS::ObjectValue(*%s)"
|
|
return setValue(toValue % result, True)
|
|
|
|
if not type.isPrimitive():
|
|
raise TypeError("Need to learn to wrap %s" % type)
|
|
|
|
if type.nullable():
|
|
return ("if (%s.IsNull()) {\n" % result +
|
|
CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" +
|
|
"}\n" +
|
|
getWrapTemplateForType(type.inner, descriptorProvider,
|
|
"%s.Value()" % result, successCode,
|
|
isCreator))
|
|
|
|
tag = type.tag()
|
|
|
|
if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
|
|
IDLType.Tags.uint16, IDLType.Tags.int32]:
|
|
return setValue("INT_TO_JSVAL(int32_t(%s))" % result)
|
|
|
|
elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, IDLType.Tags.float,
|
|
IDLType.Tags.double]:
|
|
# XXXbz will cast to double do the "even significand" thing that webidl
|
|
# calls for for 64-bit ints? Do we care?
|
|
return wrapAndSetPtr("JS_NewNumberValue(cx, double(%s), ${jsvalPtr})" % result)
|
|
|
|
elif tag == IDLType.Tags.uint32:
|
|
return setValue("UINT_TO_JSVAL(%s)" % result)
|
|
|
|
elif tag == IDLType.Tags.bool:
|
|
return setValue("BOOLEAN_TO_JSVAL(%s)" % result)
|
|
|
|
else:
|
|
raise TypeError("Need to learn to wrap primitive: %s" % type)
|
|
|
|
def wrapForType(type, descriptorProvider, templateValues):
|
|
"""
|
|
Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict
|
|
that should contain:
|
|
|
|
* 'jsvalRef': a C++ reference to the jsval in which to store the result of
|
|
the conversion
|
|
* 'jsvalPtr': a C++ pointer to the jsval in which to store the result of
|
|
the conversion
|
|
* 'obj' (optional): the name of the variable that contains the JSObject to
|
|
use as a scope when wrapping, if not supplied 'obj'
|
|
will be used as the name
|
|
* 'result' (optional): the name of the variable in which the C++ value is
|
|
stored, if not supplied 'result' will be used as
|
|
the name
|
|
* 'successCode' (optional): the code to run once we have successfully done
|
|
the conversion, if not supplied 'return true;'
|
|
will be used as the code
|
|
* 'isCreator' (optional): If true, we're wrapping for the return value of
|
|
a [Creator] method. Assumed false if not set.
|
|
"""
|
|
wrap = getWrapTemplateForType(type, descriptorProvider,
|
|
templateValues.get('result', 'result'),
|
|
templateValues.get('successCode', None),
|
|
templateValues.get('isCreator', False))
|
|
|
|
defaultValues = {'obj': 'obj'}
|
|
return string.Template(wrap).substitute(defaultValues, **templateValues)
|
|
|
|
def typeNeedsCx(type):
|
|
return (type is not None and
|
|
(type.isCallback() or type.isAny() or type.isObject()))
|
|
|
|
# Returns a CGThing containing the type of the return value, or None
|
|
# if there is no need for a return value.
|
|
def getRetvalDeclarationForType(returnType, descriptorProvider,
|
|
resultAlreadyAddRefed):
|
|
if returnType is None or returnType.isVoid():
|
|
# Nothing to declare
|
|
return None
|
|
if returnType.isPrimitive() and returnType.tag() in builtinNames:
|
|
result = CGGeneric(builtinNames[returnType.tag()])
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Nullable<", post=">")
|
|
return result
|
|
if returnType.isString():
|
|
return CGGeneric("nsString")
|
|
if returnType.isEnum():
|
|
if returnType.nullable():
|
|
raise TypeError("We don't support nullable enum return values")
|
|
return CGGeneric(returnType.inner.identifier.name)
|
|
if returnType.isGeckoInterface():
|
|
result = CGGeneric(descriptorProvider.getDescriptor(
|
|
returnType.unroll().inner.identifier.name).nativeType)
|
|
if resultAlreadyAddRefed:
|
|
result = CGWrapper(result, pre="nsRefPtr<", post=">")
|
|
else:
|
|
result = CGWrapper(result, post="*")
|
|
return result
|
|
if returnType.isCallback():
|
|
# XXXbz we're going to assume that callback types are always
|
|
# nullable for now.
|
|
return CGGeneric("JSObject*")
|
|
if returnType.isAny():
|
|
return CGGeneric("JS::Value")
|
|
if returnType.isObject():
|
|
return CGGeneric("JSObject*")
|
|
if returnType.isSequence():
|
|
nullable = returnType.nullable()
|
|
if nullable:
|
|
returnType = returnType.inner
|
|
# If our result is already addrefed, use the right type in the
|
|
# sequence argument here.
|
|
result = getRetvalDeclarationForType(returnType.inner,
|
|
descriptorProvider,
|
|
resultAlreadyAddRefed)
|
|
result = CGWrapper(result, pre="nsTArray< ", post=" >")
|
|
if nullable:
|
|
result = CGWrapper(result, pre="Nullable< ", post=" >")
|
|
return result
|
|
raise TypeError("Don't know how to declare return value for %s" %
|
|
returnType)
|
|
|
|
def isResultAlreadyAddRefed(descriptor, extendedAttributes):
|
|
# Default to already_AddRefed on the main thread, raw pointer in workers
|
|
return not descriptor.workers and not 'resultNotAddRefed' in extendedAttributes
|
|
|
|
class CGCallGenerator(CGThing):
|
|
"""
|
|
A class to generate an actual call to a C++ object. Assumes that the C++
|
|
object is stored in a variable named "self".
|
|
"""
|
|
def __init__(self, errorReport, arguments, argsPre, returnType,
|
|
extendedAttributes, descriptorProvider, nativeMethodName, static):
|
|
CGThing.__init__(self)
|
|
|
|
isFallible = errorReport is not None
|
|
|
|
args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
|
|
for (i, a) in enumerate(arguments):
|
|
arg = "arg" + str(i)
|
|
# This is a workaround for a bug in Apple's clang.
|
|
if a.type.isObject() and not a.type.nullable() and not a.optional:
|
|
arg = "(JSObject&)" + arg
|
|
args.append(CGGeneric(arg))
|
|
resultOutParam = (returnType is not None and
|
|
(returnType.isString() or returnType.isSequence()))
|
|
# Return values that go in outparams go here
|
|
if resultOutParam:
|
|
args.append(CGGeneric("result"))
|
|
if isFallible:
|
|
args.append(CGGeneric("rv"))
|
|
|
|
resultAlreadyAddRefed = isResultAlreadyAddRefed(descriptorProvider,
|
|
extendedAttributes)
|
|
result = getRetvalDeclarationForType(returnType,
|
|
descriptorProvider,
|
|
resultAlreadyAddRefed)
|
|
needsCx = (typeNeedsCx(returnType) or
|
|
any(typeNeedsCx(a.type) for a in arguments) or
|
|
'implicitJSContext' in extendedAttributes)
|
|
|
|
if not "cx" in argsPre and needsCx:
|
|
args.prepend(CGGeneric("cx"))
|
|
|
|
# Build up our actual call
|
|
self.cgRoot = CGList([], "\n")
|
|
|
|
call = CGGeneric(nativeMethodName)
|
|
if static:
|
|
call = CGWrapper(call, pre="%s::" % (descriptorProvider.getDescriptor(
|
|
returnType.unroll().inner.identifier.name).nativeType))
|
|
else:
|
|
call = CGWrapper(call, pre="self->")
|
|
call = CGList([call, CGWrapper(args, pre="(", post=");")])
|
|
if result is not None:
|
|
result = CGWrapper(result, post=" result;")
|
|
self.cgRoot.prepend(result)
|
|
if not resultOutParam:
|
|
call = CGWrapper(call, pre="result = ")
|
|
|
|
call = CGWrapper(call)
|
|
self.cgRoot.append(call)
|
|
|
|
if isFallible:
|
|
self.cgRoot.prepend(CGGeneric("ErrorResult rv;"))
|
|
self.cgRoot.append(CGGeneric("if (rv.Failed()) {"))
|
|
self.cgRoot.append(CGIndenter(CGGeneric(errorReport)))
|
|
self.cgRoot.append(CGGeneric("}"))
|
|
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
class MethodNotCreatorError(Exception):
|
|
def __init__(self, typename):
|
|
self.typename = typename
|
|
|
|
class CGPerSignatureCall(CGThing):
|
|
"""
|
|
This class handles the guts of generating code for a particular
|
|
call signature. A call signature consists of four things:
|
|
|
|
1) A return type, which can be None to indicate that there is no
|
|
actual return value (e.g. this is an attribute setter) or an
|
|
IDLType if there's an IDL type involved (including |void|).
|
|
2) An argument list, which is allowed to be empty.
|
|
3) A name of a native method to call.
|
|
4) Whether or not this method is static.
|
|
|
|
We also need to know whether this is a method or a getter/setter
|
|
to do error reporting correctly.
|
|
|
|
The idlNode parameter can be either a method or an attr. We can query
|
|
|idlNode.identifier| in both cases, so we can be agnostic between the two.
|
|
"""
|
|
# XXXbz For now each entry in the argument list is either an
|
|
# IDLArgument or a FakeArgument, but longer-term we may want to
|
|
# have ways of flagging things like JSContext* or optional_argc in
|
|
# there.
|
|
|
|
def __init__(self, returnType, argsPre, arguments, nativeMethodName, static,
|
|
descriptor, idlNode, argConversionStartsAt=0,
|
|
getter=False, setter=False):
|
|
CGThing.__init__(self)
|
|
self.returnType = returnType
|
|
self.descriptor = descriptor
|
|
self.idlNode = idlNode
|
|
self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
|
|
getter=getter,
|
|
setter=setter)
|
|
self.argsPre = argsPre
|
|
self.arguments = arguments
|
|
self.argCount = len(arguments)
|
|
if self.argCount > argConversionStartsAt:
|
|
# Insert our argv in there
|
|
cgThings = [CGGeneric(self.getArgvDecl())]
|
|
else:
|
|
cgThings = []
|
|
cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(),
|
|
self.getArgc(), self.descriptor,
|
|
invalidEnumValueFatal=not setter) for
|
|
i in range(argConversionStartsAt, self.argCount)])
|
|
|
|
cgThings.append(CGCallGenerator(
|
|
self.getErrorReport() if self.isFallible() else None,
|
|
self.arguments, self.argsPre, returnType,
|
|
self.extendedAttributes, descriptor, nativeMethodName,
|
|
static))
|
|
self.cgRoot = CGList(cgThings, "\n")
|
|
|
|
def getArgv(self):
|
|
return "argv" if self.argCount > 0 else ""
|
|
def getArgvDecl(self):
|
|
return "\nJS::Value* argv = JS_ARGV(cx, vp);\n"
|
|
def getArgc(self):
|
|
return "argc"
|
|
|
|
def isFallible(self):
|
|
return not 'infallible' in self.extendedAttributes
|
|
|
|
def wrap_return_value(self):
|
|
isCreator = self.idlNode.getExtendedAttribute("Creator") is not None
|
|
if isCreator:
|
|
# We better be returning addrefed things!
|
|
assert isResultAlreadyAddRefed(self.descriptor,
|
|
self.extendedAttributes)
|
|
|
|
resultTemplateValues = { 'jsvalRef': '*vp', 'jsvalPtr': 'vp',
|
|
'isCreator': isCreator}
|
|
try:
|
|
return wrapForType(self.returnType, self.descriptor,
|
|
resultTemplateValues)
|
|
except MethodNotCreatorError, err:
|
|
assert not isCreator
|
|
raise TypeError("%s being returned from non-creator method or property %s.%s" %
|
|
(err.typename,
|
|
self.descriptor.interface.identifier.name,
|
|
self.idlNode.identifier.name))
|
|
|
|
def getErrorReport(self):
|
|
return 'return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");'\
|
|
% (toStringBool(not self.descriptor.workers),
|
|
self.descriptor.interface.identifier.name,
|
|
self.idlNode.identifier.name)
|
|
|
|
def define(self):
|
|
return (self.cgRoot.define() + "\n" + self.wrap_return_value())
|
|
|
|
class CGSwitch(CGList):
|
|
"""
|
|
A class to generate code for a switch statement.
|
|
|
|
Takes three constructor arguments: an expression, a list of cases,
|
|
and an optional default.
|
|
|
|
Each case is a CGCase. The default is a CGThing for the body of
|
|
the default case, if any.
|
|
"""
|
|
def __init__(self, expression, cases, default=None):
|
|
CGList.__init__(self, [CGIndenter(c) for c in cases], "\n")
|
|
self.prepend(CGWrapper(CGGeneric(expression),
|
|
pre="switch (", post=") {"));
|
|
if default is not None:
|
|
self.append(
|
|
CGIndenter(
|
|
CGWrapper(
|
|
CGIndenter(default),
|
|
pre="default: {\n",
|
|
post="\n break;\n}"
|
|
)
|
|
)
|
|
)
|
|
|
|
self.append(CGGeneric("}"))
|
|
|
|
class CGCase(CGList):
|
|
"""
|
|
A class to generate code for a case statement.
|
|
|
|
Takes three constructor arguments: an expression, a CGThing for
|
|
the body (allowed to be None if there is no body), and an optional
|
|
argument (defaulting to False) for whether to fall through.
|
|
"""
|
|
def __init__(self, expression, body, fallThrough=False):
|
|
CGList.__init__(self, [], "\n")
|
|
self.append(CGWrapper(CGGeneric(expression), pre="case ", post=": {"))
|
|
bodyList = CGList([body], "\n")
|
|
if fallThrough:
|
|
bodyList.append(CGGeneric("/* Fall through */"))
|
|
else:
|
|
bodyList.append(CGGeneric("break;"))
|
|
self.append(CGIndenter(bodyList));
|
|
self.append(CGGeneric("}"))
|
|
|
|
class CGMethodCall(CGThing):
|
|
"""
|
|
A class to generate selection of a method signature from a set of
|
|
signatures and generation of a call to that signature.
|
|
"""
|
|
def __init__(self, argsPre, nativeMethodName, static, descriptor, method):
|
|
CGThing.__init__(self)
|
|
|
|
def requiredArgCount(signature):
|
|
arguments = signature[1]
|
|
if len(arguments) == 0:
|
|
return 0
|
|
requiredArgs = len(arguments)
|
|
while requiredArgs and arguments[requiredArgs-1].optional:
|
|
requiredArgs -= 1
|
|
return requiredArgs
|
|
|
|
def maxSigLength(signatures):
|
|
return max([len(s[1]) for s in signatures])
|
|
|
|
def signaturesForArgCount(i, signatures):
|
|
return filter(
|
|
lambda s: len(s[1]) == i or (len(s[1]) > i and
|
|
s[1][i].optional),
|
|
signatures)
|
|
|
|
def findDistinguishingIndex(argCount, signatures):
|
|
def isValidDistinguishingIndex(idx, signatures):
|
|
for firstSigIndex in range(0, len(signatures)):
|
|
for secondSigIndex in range(0, firstSigIndex):
|
|
firstType = signatures[firstSigIndex][1][idx].type
|
|
secondType = signatures[secondSigIndex][1][idx].type
|
|
if not firstType.isDistinguishableFrom(secondType):
|
|
return False
|
|
return True
|
|
for idx in range(0, argCount):
|
|
if isValidDistinguishingIndex(idx, signatures):
|
|
return idx
|
|
return -1
|
|
|
|
def getPerSignatureCall(signature, argConversionStartsAt=0):
|
|
return CGPerSignatureCall(signature[0], argsPre, signature[1],
|
|
nativeMethodName, static, descriptor,
|
|
method, argConversionStartsAt)
|
|
|
|
|
|
signatures = method.signatures()
|
|
if len(signatures) == 1:
|
|
# Special case: we can just do a per-signature method call
|
|
# here for our one signature and not worry about switching
|
|
# on anything.
|
|
signature = signatures[0]
|
|
self.cgRoot = CGList([ CGIndenter(getPerSignatureCall(signature)) ])
|
|
requiredArgs = requiredArgCount(signature)
|
|
if requiredArgs > 0:
|
|
self.cgRoot.prepend(
|
|
CGWrapper(
|
|
CGIndenter(
|
|
CGGeneric(
|
|
"if (argc < %d) {\n"
|
|
" return Throw<%s>(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);\n"
|
|
"}" % (requiredArgs,
|
|
toStringBool(not descriptor.workers)))
|
|
),
|
|
pre="\n", post="\n")
|
|
)
|
|
return
|
|
|
|
# Need to find the right overload
|
|
maxSigArgs = maxSigLength(signatures)
|
|
allowedArgCounts = [ i for i in range(0, maxSigArgs+1)
|
|
if len(signaturesForArgCount(i, signatures)) != 0 ]
|
|
|
|
argCountCases = []
|
|
for argCount in allowedArgCounts:
|
|
possibleSignatures = signaturesForArgCount(argCount, signatures)
|
|
if len(possibleSignatures) == 1:
|
|
# easy case!
|
|
signature = possibleSignatures[0]
|
|
|
|
# (possibly) important optimization: if signature[1] has >
|
|
# argCount arguments and signature[1][argCount] is optional and
|
|
# there is only one signature for argCount+1, then the
|
|
# signature for argCount+1 is just ourselves and we can fall
|
|
# through.
|
|
if (len(signature[1]) > argCount and
|
|
signature[1][argCount].optional and
|
|
(argCount+1) in allowedArgCounts and
|
|
len(signaturesForArgCount(argCount+1, signatures)) == 1):
|
|
argCountCases.append(
|
|
CGCase(str(argCount), None, True))
|
|
else:
|
|
argCountCases.append(
|
|
CGCase(str(argCount), getPerSignatureCall(signature)))
|
|
continue
|
|
|
|
distinguishingIndex = findDistinguishingIndex(argCount,
|
|
possibleSignatures)
|
|
if distinguishingIndex == -1:
|
|
raise TypeError(("Signatures with %s arguments for " +
|
|
descriptor.interface.identifier.name + "." +
|
|
method.identifier.name +
|
|
" are not distinguishable") % argCount)
|
|
|
|
for idx in range(0, distinguishingIndex):
|
|
firstSigType = possibleSignatures[0][1][idx].type
|
|
for sigIdx in range(1, len(possibleSignatures)):
|
|
if possibleSignatures[sigIdx][1][idx].type != firstSigType:
|
|
raise TypeError(("Signatures with %d arguments for " +
|
|
descriptor.interface.identifier.name +
|
|
"." + method.identifier.name +
|
|
" have different types at index %d" +
|
|
" which is before distinguishing" +
|
|
" index %d. Types are %s and %s") %
|
|
(argCount, idx,
|
|
distinguishingIndex,
|
|
str(possibleSignatures[sigIdx][1][idx].type),
|
|
str(firstSigType)))
|
|
|
|
# Convert all our arguments up to the distinguishing index.
|
|
# Doesn't matter which of the possible signatures we use, since
|
|
# they all have the same types up to that point; just use
|
|
# possibleSignatures[0]
|
|
caseBody = [CGGeneric("JS::Value* argv_start = JS_ARGV(cx, vp);")]
|
|
caseBody.extend([ CGArgumentConverter(possibleSignatures[0][1][i],
|
|
i, "argv_start", "argc",
|
|
descriptor) for i in
|
|
range(0, distinguishingIndex) ])
|
|
|
|
# Select the right overload from our set.
|
|
distinguishingArg = "argv_start[%d]" % distinguishingIndex
|
|
|
|
def pickFirstSignature(condition, filterLambda):
|
|
sigs = filter(filterLambda, possibleSignatures)
|
|
assert len(sigs) < 2
|
|
if len(sigs) > 0:
|
|
if condition is None:
|
|
caseBody.append(
|
|
getPerSignatureCall(sigs[0], distinguishingIndex))
|
|
else:
|
|
caseBody.append(CGGeneric("if (" + condition + ") {"))
|
|
caseBody.append(CGIndenter(
|
|
getPerSignatureCall(sigs[0], distinguishingIndex)))
|
|
caseBody.append(CGGeneric("}"))
|
|
return True
|
|
return False
|
|
|
|
# First check for null or undefined
|
|
pickFirstSignature("%s.isNullOrUndefined()" % distinguishingArg,
|
|
lambda s: s[1][distinguishingIndex].type.nullable())
|
|
|
|
# Now check for distinguishingArg being an object that implements a
|
|
# non-callback interface. That includes typed arrays and
|
|
# arraybuffers.
|
|
interfacesSigs = [
|
|
s for s in possibleSignatures
|
|
if (s[1][distinguishingIndex].type.isObject() or
|
|
(s[1][distinguishingIndex].type.isInterface() and
|
|
not s[1][distinguishingIndex].type.isCallback())) ]
|
|
# There might be more than one of these; we need to check
|
|
# which ones we unwrap to.
|
|
|
|
if len(interfacesSigs) > 0:
|
|
# The spec says that we should check for "platform objects
|
|
# implementing an interface", but it's enough to guard on these
|
|
# being an object. The code for unwrapping non-callback
|
|
# interfaces and typed arrays will just bail out and move on to
|
|
# the next overload if the object fails to unwrap correctly. We
|
|
# could even not do the isObject() check up front here, but in
|
|
# cases where we have multiple object overloads it makes sense
|
|
# to do it only once instead of for each overload. That will
|
|
# also allow the unwrapping test to skip having to do codegen
|
|
# for the null-or-undefined case, which we already handled
|
|
# above.
|
|
caseBody.append(CGGeneric("if (%s.isObject()) {" %
|
|
(distinguishingArg)))
|
|
for sig in interfacesSigs:
|
|
caseBody.append(CGIndenter(CGGeneric("do {")));
|
|
type = sig[1][distinguishingIndex].type
|
|
|
|
# The argument at index distinguishingIndex can't possibly
|
|
# be unset here, because we've already checked that argc is
|
|
# large enough that we can examine this argument.
|
|
testCode = instantiateJSToNativeConversionTemplate(
|
|
getJSToNativeConversionTemplate(type, descriptor,
|
|
failureCode="break;",
|
|
isDefinitelyObject=True),
|
|
{
|
|
"declName" : "arg%d" % distinguishingIndex,
|
|
"holderName" : ("arg%d" % distinguishingIndex) + "_holder",
|
|
"val" : distinguishingArg
|
|
})
|
|
|
|
# Indent by 4, since we need to indent further than our "do" statement
|
|
caseBody.append(CGIndenter(testCode, 4));
|
|
# If we got this far, we know we unwrapped to the right
|
|
# interface, so just do the call. Start conversion with
|
|
# distinguishingIndex + 1, since we already converted
|
|
# distinguishingIndex.
|
|
caseBody.append(CGIndenter(
|
|
getPerSignatureCall(sig, distinguishingIndex + 1), 4))
|
|
caseBody.append(CGIndenter(CGGeneric("} while (0);")))
|
|
|
|
caseBody.append(CGGeneric("}"))
|
|
|
|
# XXXbz Now we're supposed to check for distinguishingArg being
|
|
# an array or a platform object that supports indexed
|
|
# properties... skip that last for now. It's a bit of a pain.
|
|
pickFirstSignature("%s.isObject() && IsArrayLike(cx, &%s.toObject())" %
|
|
(distinguishingArg, distinguishingArg),
|
|
lambda s:
|
|
(s[1][distinguishingIndex].type.isArray() or
|
|
s[1][distinguishingIndex].type.isSequence() or
|
|
s[1][distinguishingIndex].type.isObject()))
|
|
|
|
# Check for Date objects
|
|
# XXXbz Do we need to worry about security wrappers around the Date?
|
|
pickFirstSignature("%s.isObject() && JS_ObjectIsDate(cx, &%s.toObject())" %
|
|
(distinguishingArg, distinguishingArg),
|
|
lambda s: (s[1][distinguishingIndex].type.isDate() or
|
|
s[1][distinguishingIndex].type.isObject()))
|
|
|
|
# Check for vanilla JS objects
|
|
# XXXbz Do we need to worry about security wrappers?
|
|
pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" %
|
|
(distinguishingArg, distinguishingArg),
|
|
lambda s: (s[1][distinguishingIndex].type.isCallback() or
|
|
s[1][distinguishingIndex].type.isDictionary() or
|
|
s[1][distinguishingIndex].type.isObject()))
|
|
|
|
# The remaining cases are mutually exclusive. The
|
|
# pickFirstSignature calls are what change caseBody
|
|
# Check for strings or enums
|
|
if pickFirstSignature(None,
|
|
lambda s: (s[1][distinguishingIndex].type.isString() or
|
|
s[1][distinguishingIndex].type.isEnum())):
|
|
pass
|
|
# Check for primitives
|
|
elif pickFirstSignature(None,
|
|
lambda s: s[1][distinguishingIndex].type.isPrimitive()):
|
|
pass
|
|
# Check for "any"
|
|
elif pickFirstSignature(None,
|
|
lambda s: s[1][distinguishingIndex].type.isAny()):
|
|
pass
|
|
else:
|
|
# Just throw; we have no idea what we're supposed to
|
|
# do with this.
|
|
caseBody.append(CGGeneric("return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);" %
|
|
toStringBool(not descriptor.workers)))
|
|
|
|
argCountCases.append(CGCase(str(argCount),
|
|
CGList(caseBody, "\n")))
|
|
|
|
overloadCGThings = []
|
|
overloadCGThings.append(
|
|
CGGeneric("unsigned argcount = NS_MIN(argc, %du);" %
|
|
maxSigArgs))
|
|
overloadCGThings.append(
|
|
CGSwitch("argcount",
|
|
argCountCases,
|
|
CGGeneric("return Throw<%s>(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);" %
|
|
toStringBool(not descriptor.workers))))
|
|
overloadCGThings.append(
|
|
CGGeneric('MOZ_NOT_REACHED("We have an always-returning default case");\n'
|
|
'return false;'))
|
|
self.cgRoot = CGWrapper(CGIndenter(CGList(overloadCGThings, "\n")),
|
|
pre="\n")
|
|
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
class CGGetterSetterCall(CGPerSignatureCall):
|
|
"""
|
|
A class to generate a native object getter or setter call for a
|
|
particular IDL getter or setter.
|
|
"""
|
|
def __init__(self, returnType, arguments, nativeMethodName, descriptor,
|
|
attr, getter=False, setter=False):
|
|
assert bool(getter) != bool(setter)
|
|
CGPerSignatureCall.__init__(self, returnType, [], arguments,
|
|
nativeMethodName, False, descriptor, attr,
|
|
getter=getter, setter=setter)
|
|
def getArgv(self):
|
|
if generateNativeAccessors:
|
|
return CGPerSignatureCall.getArgv(self)
|
|
return "vp"
|
|
|
|
class CGGetterCall(CGGetterSetterCall):
|
|
"""
|
|
A class to generate a native object getter call for a particular IDL
|
|
getter.
|
|
"""
|
|
def __init__(self, returnType, nativeMethodName, descriptor, attr):
|
|
CGGetterSetterCall.__init__(self, returnType, [], nativeMethodName,
|
|
descriptor, attr, getter=True)
|
|
def getArgc(self):
|
|
if generateNativeAccessors:
|
|
return CGGetterSetterCall.getArgc()
|
|
return "0"
|
|
def getArgvDecl(self):
|
|
if generateNativeAccessors:
|
|
return CGPerSignatureCall.getArgvDecl(self)
|
|
# We just get our stuff from vp
|
|
return ""
|
|
|
|
class FakeArgument():
|
|
def __init__(self, type):
|
|
self.type = type
|
|
self.optional = False
|
|
|
|
class CGSetterCall(CGGetterSetterCall):
|
|
"""
|
|
A class to generate a native object setter call for a particular IDL
|
|
setter.
|
|
"""
|
|
def __init__(self, argType, nativeMethodName, descriptor, attr):
|
|
CGGetterSetterCall.__init__(self, None, [FakeArgument(argType)],
|
|
nativeMethodName, descriptor, attr,
|
|
setter=True)
|
|
def wrap_return_value(self):
|
|
if generateNativeAccessors:
|
|
return CGGetterSetterCall.wrap_return_value(self)
|
|
# We have no return value
|
|
return "\nreturn true;"
|
|
def getArgc(self):
|
|
if generateNativeAccessors:
|
|
return CGGetterSetterCall.getArgc(self)
|
|
return "1"
|
|
def getArgvDecl(self):
|
|
if generateNativeAccessors:
|
|
return (CGPerSignatureCall.getArgvDecl(self) +
|
|
"jsval undef = JS::UndefinedValue();\n"
|
|
"if (argc == 0) {\n"
|
|
" argv = &undef;\n"
|
|
" argc = 1;\n"
|
|
"}")
|
|
# We just get our stuff from vp
|
|
return ""
|
|
|
|
class CGAbstractBindingMethod(CGAbstractStaticMethod):
|
|
"""
|
|
Common class to generate the JSNatives for all our methods, getters, and
|
|
setters. This will generate the function declaration and unwrap the
|
|
|this| object. Subclasses are expected to override the generate_code
|
|
function to do the rest of the work. This function should return a
|
|
CGThing which is already properly indented.
|
|
"""
|
|
def __init__(self, descriptor, name, args):
|
|
CGAbstractStaticMethod.__init__(self, descriptor, name, "JSBool", args)
|
|
|
|
def definition_body(self):
|
|
unwrapThis = CGIndenter(CGGeneric(
|
|
str(FailureFatalCastableObjectUnwrapper(self.descriptor,
|
|
"obj", "self"))))
|
|
return CGList([ self.getThis(), unwrapThis,
|
|
self.generate_code() ], "\n").define()
|
|
|
|
def getThis(self):
|
|
return CGIndenter(
|
|
CGGeneric("JSObject* obj = JS_THIS_OBJECT(cx, vp);\n"
|
|
"if (!obj) {\n"
|
|
" return false;\n"
|
|
"}\n"
|
|
"\n"
|
|
"%s* self;" % self.descriptor.nativeType))
|
|
|
|
def generate_code(self):
|
|
assert(False) # Override me
|
|
|
|
def MakeNativeName(name):
|
|
return name[0].upper() + name[1:]
|
|
|
|
class CGNativeMethod(CGAbstractBindingMethod):
|
|
"""
|
|
A class for generating the C++ code for an IDL method..
|
|
"""
|
|
def __init__(self, descriptor, method):
|
|
self.method = method
|
|
baseName = method.identifier.name
|
|
args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
|
|
Argument('JS::Value*', 'vp')]
|
|
CGAbstractBindingMethod.__init__(self, descriptor, baseName, args)
|
|
def generate_code(self):
|
|
name = self.method.identifier.name
|
|
nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
|
|
return CGMethodCall([], nativeName, self.method.isStatic(),
|
|
self.descriptor, self.method)
|
|
|
|
class CGNativeGetter(CGAbstractBindingMethod):
|
|
"""
|
|
A class for generating the C++ code for an IDL attribute getter.
|
|
"""
|
|
def __init__(self, descriptor, attr):
|
|
self.attr = attr
|
|
name = 'get_' + attr.identifier.name
|
|
if generateNativeAccessors:
|
|
args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
|
|
Argument('JS::Value*', 'vp')]
|
|
else:
|
|
args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
|
|
Argument('JSHandleId', 'id'), Argument('JS::Value*', 'vp')]
|
|
CGAbstractBindingMethod.__init__(self, descriptor, name, args)
|
|
|
|
def getThis(self):
|
|
if generateNativeAccessors:
|
|
return CGAbstractBindingMethod.getThis(self)
|
|
return CGIndenter(
|
|
CGGeneric("%s* self;" % self.descriptor.nativeType))
|
|
|
|
def generate_code(self):
|
|
name = self.attr.identifier.name
|
|
nativeName = "Get" + MakeNativeName(self.descriptor.binaryNames.get(name, name))
|
|
return CGIndenter(CGGetterCall(self.attr.type, nativeName, self.descriptor,
|
|
self.attr))
|
|
|
|
class CGNativeSetter(CGAbstractBindingMethod):
|
|
"""
|
|
A class for generating the C++ code for an IDL attribute setter.
|
|
"""
|
|
def __init__(self, descriptor, attr):
|
|
self.attr = attr
|
|
baseName = attr.identifier.name
|
|
name = 'set_' + attr.identifier.name
|
|
if generateNativeAccessors:
|
|
args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
|
|
Argument('JS::Value*', 'vp')]
|
|
else:
|
|
args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
|
|
Argument('JSHandleId', 'id'), Argument('JSBool', 'strict'),
|
|
Argument('JS::Value*', 'vp')]
|
|
CGAbstractBindingMethod.__init__(self, descriptor, name, args)
|
|
|
|
def getThis(self):
|
|
if generateNativeAccessors:
|
|
return CGAbstractBindingMethod.getThis(self)
|
|
return CGIndenter(
|
|
CGGeneric("%s* self;" % self.descriptor.nativeType))
|
|
|
|
def generate_code(self):
|
|
name = self.attr.identifier.name
|
|
nativeName = "Set" + MakeNativeName(self.descriptor.binaryNames.get(name, name))
|
|
return CGIndenter(CGSetterCall(self.attr.type, nativeName, self.descriptor,
|
|
self.attr))
|
|
|
|
def getEnumValueName(value):
|
|
# Some enum values can be empty strings. Others might have weird
|
|
# characters in them. Deal with the former by returning "_empty",
|
|
# deal with possible name collisions from that by throwing if the
|
|
# enum value is actually "_empty", and throw on any value
|
|
# containing chars other than [a-z] or '-' for now. Replace '-' with '_'.
|
|
value = value.replace('-', '_')
|
|
if value == "_empty":
|
|
raise SyntaxError('"_empty" is not an IDL enum value we support yet')
|
|
if value == "":
|
|
return "_empty"
|
|
if not re.match("^[a-z_]+$", value):
|
|
raise SyntaxError('Enum value "' + value + '" contains characters '
|
|
'outside [a-z_]')
|
|
return MakeNativeName(value)
|
|
|
|
class CGEnum(CGThing):
|
|
def __init__(self, enum):
|
|
CGThing.__init__(self)
|
|
self.enum = enum
|
|
|
|
def declare(self):
|
|
return """
|
|
enum valuelist {
|
|
%s
|
|
};
|
|
|
|
extern const EnumEntry strings[%d];
|
|
""" % (",\n ".join(map(getEnumValueName, self.enum.values())),
|
|
len(self.enum.values()) + 1)
|
|
|
|
def define(self):
|
|
return """
|
|
const EnumEntry strings[%d] = {
|
|
%s,
|
|
{ NULL, 0 }
|
|
};
|
|
""" % (len(self.enum.values()) + 1,
|
|
",\n ".join(['{"' + val + '", ' + str(len(val)) + '}' for val in self.enum.values()]))
|
|
|
|
class ClassItem:
|
|
""" Use with CGClass """
|
|
def __init__(self, name, visibility):
|
|
self.name = name
|
|
self.visibility = visibility
|
|
def declare(self, cgClass):
|
|
assert False
|
|
def define(self, cgClass):
|
|
assert False
|
|
|
|
class ClassBase(ClassItem):
|
|
def __init__(self, name, visibility='public'):
|
|
ClassItem.__init__(self, name, visibility)
|
|
def declare(self, cgClass):
|
|
return '%s %s' % (self.visibility, self.name)
|
|
def define(self, cgClass):
|
|
# Only in the header
|
|
return ''
|
|
|
|
class ClassMethod(ClassItem):
|
|
def __init__(self, name, returnType, args, inline=False, static=False,
|
|
virtual=False, const=False, bodyInHeader=False,
|
|
templateArgs=None, visibility='public', body=None):
|
|
self.returnType = returnType
|
|
self.args = args
|
|
self.inline = inline or bodyInHeader
|
|
self.static = static
|
|
self.virtual = virtual
|
|
self.const = const
|
|
self.bodyInHeader = bodyInHeader
|
|
self.templateArgs = templateArgs
|
|
self.body = body
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def getDecorators(self, declaring):
|
|
decorators = []
|
|
if self.inline:
|
|
decorators.append('inline')
|
|
if declaring:
|
|
if self.static:
|
|
decorators.append('static')
|
|
if self.virtual:
|
|
decorators.append('virtual')
|
|
if decorators:
|
|
return ' '.join(decorators) + ' '
|
|
return ''
|
|
|
|
def getBody(self):
|
|
# Override me or pass a string to constructor
|
|
assert self.body is not None
|
|
return self.body
|
|
|
|
def declare(self, cgClass):
|
|
templateClause = 'template <%s>\n' % ', '.join(self.templateArgs) \
|
|
if self.bodyInHeader and self.templateArgs else ''
|
|
args = ', '.join([str(a) for a in self.args])
|
|
if self.bodyInHeader:
|
|
body = CGIndenter(CGGeneric(self.getBody())).define()
|
|
body = '\n{\n' + body + '\n}'
|
|
else:
|
|
body = ';'
|
|
|
|
return string.Template("""${templateClause}${decorators}${returnType}
|
|
${name}(${args})${const}${body}
|
|
""").substitute({ 'templateClause': templateClause,
|
|
'decorators': self.getDecorators(True),
|
|
'returnType': self.returnType,
|
|
'name': self.name,
|
|
'const': ' const' if self.const else '',
|
|
'args': args,
|
|
'body': body })
|
|
|
|
def define(self, cgClass):
|
|
if self.bodyInHeader:
|
|
return ''
|
|
|
|
templateArgs = cgClass.templateArgs
|
|
if templateArgs:
|
|
if cgClass.templateSpecialization:
|
|
templateArgs = \
|
|
templateArgs[len(cgClass.templateSpecialization):]
|
|
|
|
if templateArgs:
|
|
templateClause = \
|
|
'template <%s>\n' % ', '.join([str(a) for a in templateArgs])
|
|
else:
|
|
templateClause = ''
|
|
|
|
args = ', '.join([str(a) for a in self.args])
|
|
|
|
body = CGIndenter(CGGeneric(self.getBody())).define()
|
|
|
|
return string.Template("""${templateClause}${decorators}${returnType}
|
|
${className}::${name}(${args})${const}
|
|
{
|
|
${body}
|
|
}\n
|
|
""").substitute({ 'templateClause': templateClause,
|
|
'decorators': self.getDecorators(False),
|
|
'returnType': self.returnType,
|
|
'className': cgClass.getNameString(),
|
|
'name': self.name,
|
|
'args': args,
|
|
'const': ' const' if self.const else '',
|
|
'body': body })
|
|
|
|
class ClassMember(ClassItem):
|
|
def __init__(self, name, type, visibility="private", static=False,
|
|
body=None):
|
|
self.type = type;
|
|
self.static = static
|
|
self.body = body
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def getBody(self):
|
|
assert self.body is not None
|
|
return self.body
|
|
|
|
def declare(self, cgClass):
|
|
return '%s%s %s;\n' % ('static ' if self.static else '', self.type,
|
|
self.name)
|
|
|
|
def define(self, cgClass):
|
|
if not self.static:
|
|
return ''
|
|
return '%s %s::%s = %s;\n' % (self.type, cgClass.getNameString(),
|
|
self.name, self.getBody())
|
|
|
|
class ClassTypedef(ClassItem):
|
|
def __init__(self, name, type, visibility="public"):
|
|
self.type = type
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return 'typedef %s %s;\n' % (self.type, self.name)
|
|
|
|
def define(self, cgClass):
|
|
# Only goes in the header
|
|
return ''
|
|
|
|
class ClassEnum(ClassItem):
|
|
def __init__(self, name, entries, values=None, visibility="public"):
|
|
self.entries = entries
|
|
self.values = values
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
entries = []
|
|
for i in range(0, len(self.entries)):
|
|
if i >= len(self.values):
|
|
entry = '%s' % self.entries[i]
|
|
else:
|
|
entry = '%s = %s' % (self.entries[i], self.values[i])
|
|
entries.append(entry)
|
|
name = '' if not self.name else ' ' + self.name
|
|
return 'enum%s\n{\n %s\n};\n' % (name, ',\n '.join(entries))
|
|
|
|
def define(self, cgClass):
|
|
# Only goes in the header
|
|
return ''
|
|
|
|
class CGClass(CGThing):
|
|
def __init__(self, name, bases=[], members=[], methods=[], typedefs = [],
|
|
enums=[], templateArgs=[], templateSpecialization=[],
|
|
isStruct=False, indent=''):
|
|
CGThing.__init__(self)
|
|
self.name = name
|
|
self.bases = bases
|
|
self.members = members
|
|
self.methods = methods
|
|
self.typedefs = typedefs
|
|
self.enums = enums
|
|
self.templateArgs = templateArgs
|
|
self.templateSpecialization = templateSpecialization
|
|
self.isStruct = isStruct
|
|
self.indent = indent
|
|
self.defaultVisibility ='public' if isStruct else 'private'
|
|
|
|
def getNameString(self):
|
|
className = self.name
|
|
if self.templateSpecialization:
|
|
className = className + \
|
|
'<%s>' % ', '.join([str(a) for a
|
|
in self.templateSpecialization])
|
|
return className
|
|
|
|
def declare(self):
|
|
result = ''
|
|
if self.templateArgs:
|
|
templateArgs = [str(a) for a in self.templateArgs]
|
|
templateArgs = templateArgs[len(self.templateSpecialization):]
|
|
result = result + self.indent + 'template <%s>\n' \
|
|
% ','.join([str(a) for a in templateArgs])
|
|
|
|
type = 'struct' if self.isStruct else 'class'
|
|
|
|
if self.templateSpecialization:
|
|
specialization = \
|
|
'<%s>' % ', '.join([str(a) for a in self.templateSpecialization])
|
|
else:
|
|
specialization = ''
|
|
|
|
result = result + '%s%s %s%s' \
|
|
% (self.indent, type, self.name, specialization)
|
|
|
|
if self.bases:
|
|
result = result + ' : %s' % ', '.join([d.declare(self) for d in self.bases])
|
|
|
|
result = result + '\n%s{\n' % self.indent
|
|
|
|
def declareMembers(cgClass, memberList, defaultVisibility, itemCount,
|
|
separator=''):
|
|
members = { 'private': [], 'protected': [], 'public': [] }
|
|
|
|
for member in memberList:
|
|
members[member.visibility].append(member)
|
|
|
|
|
|
if defaultVisibility == 'public':
|
|
order = [ 'public', 'protected', 'private' ]
|
|
else:
|
|
order = [ 'private', 'protected', 'public' ]
|
|
|
|
result = ''
|
|
|
|
lastVisibility = defaultVisibility
|
|
for visibility in order:
|
|
list = members[visibility]
|
|
if list:
|
|
if visibility != lastVisibility:
|
|
if itemCount:
|
|
result = result + '\n'
|
|
result = result + visibility + ':\n'
|
|
itemCount = 0
|
|
for member in list:
|
|
if itemCount != 0:
|
|
result = result + separator
|
|
declaration = member.declare(cgClass)
|
|
declaration = CGIndenter(CGGeneric(declaration)).define()
|
|
result = result + declaration
|
|
itemCount = itemCount + 1
|
|
lastVisibility = visibility
|
|
return (result, lastVisibility, itemCount)
|
|
|
|
order = [(self.enums, ''), (self.typedefs, ''), (self.members, ''),
|
|
(self.methods, '\n')]
|
|
|
|
lastVisibility = self.defaultVisibility
|
|
itemCount = 0
|
|
for (memberList, separator) in order:
|
|
(memberString, lastVisibility, itemCount) = \
|
|
declareMembers(self, memberList, lastVisibility, itemCount,
|
|
separator)
|
|
if self.indent:
|
|
memberString = CGIndenter(CGGeneric(memberString),
|
|
len(self.indent)).define()
|
|
result = result + memberString
|
|
|
|
result = result + self.indent + '};\n\n'
|
|
return result
|
|
|
|
def define(self):
|
|
def defineMembers(cgClass, memberList, itemCount, separator=''):
|
|
result = ''
|
|
for member in memberList:
|
|
if itemCount != 0:
|
|
result = result + separator
|
|
result = result + member.define(cgClass)
|
|
itemCount = itemCount + 1
|
|
return (result, itemCount)
|
|
|
|
order = [(self.members, '\n'), (self.methods, '\n')]
|
|
|
|
result = ''
|
|
itemCount = 0
|
|
for (memberList, separator) in order:
|
|
(memberString, itemCount) = defineMembers(self, memberList,
|
|
itemCount, separator)
|
|
result = result + memberString
|
|
return result
|
|
|
|
class CGResolveProperty(CGAbstractMethod):
|
|
def __init__(self, descriptor, properties):
|
|
args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'),
|
|
Argument('jsid', 'id'), Argument('bool', 'set'),
|
|
Argument('JSPropertyDescriptor*', 'desc')]
|
|
CGAbstractMethod.__init__(self, descriptor, "ResolveProperty", "bool", args)
|
|
self.properties = properties
|
|
def definition_body(self):
|
|
str = ""
|
|
|
|
varNames = self.properties.variableNames(True)
|
|
|
|
methods = self.properties.methods
|
|
if methods.hasNonChromeOnly() or methods.hasChromeOnly():
|
|
str += """ // %(methods)s has an end-of-list marker at the end that we ignore
|
|
for (size_t prefIdx = 0; prefIdx < ArrayLength(%(methods)s)-1; ++prefIdx) {
|
|
MOZ_ASSERT(%(methods)s[prefIdx].specs);
|
|
if (%(methods)s[prefIdx].enabled) {
|
|
// Set i to be the index into our full list of ids/specs that we're
|
|
// looking at now.
|
|
size_t i = %(methods)s[prefIdx].specs - %(methods)s_specs;
|
|
for ( ; %(methods)s_ids[i] != JSID_VOID; ++i) {
|
|
if (id == %(methods)s_ids[i]) {
|
|
JSFunction *fun = JS_NewFunctionById(cx, %(methods)s_specs[i].call, %(methods)s_specs[i].nargs, 0, wrapper, id);
|
|
if (!fun)
|
|
return false;
|
|
JSObject *funobj = JS_GetFunctionObject(fun);
|
|
desc->value.setObject(*funobj);
|
|
desc->attrs = %(methods)s_specs[i].flags;
|
|
desc->obj = wrapper;
|
|
desc->setter = nsnull;
|
|
desc->getter = nsnull;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
""" % varNames
|
|
|
|
attrs = self.properties.attrs
|
|
if attrs.hasNonChromeOnly() or attrs.hasChromeOnly():
|
|
str += """ // %(attrs)s has an end-of-list marker at the end that we ignore
|
|
for (size_t prefIdx = 0; prefIdx < ArrayLength(%(attrs)s)-1; ++prefIdx) {
|
|
MOZ_ASSERT(%(attrs)s[prefIdx].specs);
|
|
if (%(attrs)s[prefIdx].enabled) {
|
|
// Set i to be the index into our full list of ids/specs that we're
|
|
// looking at now.
|
|
size_t i = %(attrs)s[prefIdx].specs - %(attrs)s_specs;;
|
|
for ( ; %(attrs)s_ids[i] != JSID_VOID; ++i) {
|
|
if (id == %(attrs)s_ids[i]) {
|
|
desc->attrs = %(attrs)s_specs[i].flags;
|
|
desc->obj = wrapper;
|
|
desc->setter = %(attrs)s_specs[i].setter;
|
|
desc->getter = %(attrs)s_specs[i].getter;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
""" % varNames
|
|
|
|
return str + " return true;"
|
|
|
|
class CGEnumerateProperties(CGAbstractMethod):
|
|
def __init__(self, descriptor, properties):
|
|
args = [Argument('JS::AutoIdVector&', 'props')]
|
|
CGAbstractMethod.__init__(self, descriptor, "EnumerateProperties", "bool", args)
|
|
self.properties = properties
|
|
def definition_body(self):
|
|
str = ""
|
|
|
|
varNames = self.properties.variableNames(True)
|
|
|
|
methods = self.properties.methods
|
|
if methods.hasNonChromeOnly() or methods.hasChromeOnly():
|
|
str += """ // %(methods)s has an end-of-list marker at the end that we ignore
|
|
for (size_t prefIdx = 0; prefIdx < ArrayLength(%(methods)s)-1; ++prefIdx) {
|
|
MOZ_ASSERT(%(methods)s[prefIdx].specs);
|
|
if (%(methods)s[prefIdx].enabled) {
|
|
// Set i to be the index into our full list of ids/specs that we're
|
|
// looking at now.
|
|
size_t i = %(methods)s[prefIdx].specs - %(methods)s_specs;
|
|
for ( ; %(methods)s_ids[i] != JSID_VOID; ++i) {
|
|
if ((%(methods)s_specs[i].flags & JSPROP_ENUMERATE) &&
|
|
!props.append(%(methods)s_ids[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
""" % varNames
|
|
|
|
attrs = self.properties.attrs
|
|
if attrs.hasNonChromeOnly() or attrs.hasChromeOnly():
|
|
str += """ // %(attrs)s has an end-of-list marker at the end that we ignore
|
|
for (size_t prefIdx = 0; prefIdx < ArrayLength(%(attrs)s)-1; ++prefIdx) {
|
|
MOZ_ASSERT(%(attrs)s[prefIdx].specs);
|
|
if (%(attrs)s[prefIdx].enabled) {
|
|
// Set i to be the index into our full list of ids/specs that we're
|
|
// looking at now.
|
|
size_t i = %(attrs)s[prefIdx].specs - %(attrs)s_specs;;
|
|
for ( ; %(attrs)s_ids[i] != JSID_VOID; ++i) {
|
|
if ((%(attrs)s_specs[i].flags & JSPROP_ENUMERATE) &&
|
|
!props.append(%(attrs)s_ids[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
""" % varNames
|
|
|
|
return str + " return true;"
|
|
|
|
class CGPrototypeTraitsClass(CGClass):
|
|
def __init__(self, descriptor, indent=''):
|
|
templateArgs = [Argument('prototypes::ID', 'PrototypeID')]
|
|
templateSpecialization = ['prototypes::id::' + descriptor.name]
|
|
enums = [ClassEnum('', ['Depth'],
|
|
[descriptor.interface.inheritanceDepth()])]
|
|
typedefs = [ClassTypedef('NativeType', descriptor.nativeType)]
|
|
CGClass.__init__(self, 'PrototypeTraits', indent=indent,
|
|
templateArgs=templateArgs,
|
|
templateSpecialization=templateSpecialization,
|
|
enums=enums, typedefs=typedefs, isStruct=True)
|
|
|
|
class CGPrototypeIDMapClass(CGClass):
|
|
def __init__(self, descriptor, indent=''):
|
|
templateArgs = [Argument('class', 'ConcreteClass')]
|
|
templateSpecialization = [descriptor.nativeType]
|
|
enums = [ClassEnum('', ['PrototypeID'],
|
|
['prototypes::id::' + descriptor.name])]
|
|
CGClass.__init__(self, 'PrototypeIDMap', indent=indent,
|
|
templateArgs=templateArgs,
|
|
templateSpecialization=templateSpecialization,
|
|
enums=enums, isStruct=True)
|
|
|
|
class CGClassForwardDeclare(CGThing):
|
|
def __init__(self, name, isStruct=False):
|
|
CGThing.__init__(self)
|
|
self.name = name
|
|
self.isStruct = isStruct
|
|
def declare(self):
|
|
type = 'struct' if self.isStruct else 'class'
|
|
return '%s %s;\n' % (type, self.name)
|
|
def define(self):
|
|
# Header only
|
|
return ''
|
|
|
|
def stripTrailingWhitespace(text):
|
|
lines = text.splitlines()
|
|
for i in range(len(lines)):
|
|
lines[i] = lines[i].rstrip()
|
|
return '\n'.join(lines)
|
|
|
|
class CGDescriptor(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
|
|
assert not descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject()
|
|
|
|
cgThings = []
|
|
if descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.extend([CGNativeMethod(descriptor, m) for m in
|
|
descriptor.interface.members if
|
|
m.isMethod() and not m.isStatic()])
|
|
cgThings.extend([CGNativeGetter(descriptor, a) for a in
|
|
descriptor.interface.members if a.isAttr()])
|
|
cgThings.extend([CGNativeSetter(descriptor, a) for a in
|
|
descriptor.interface.members if
|
|
a.isAttr() and not a.readonly])
|
|
|
|
if descriptor.concrete:
|
|
if not descriptor.workers and descriptor.wrapperCache:
|
|
cgThings.append(CGAddPropertyHook(descriptor))
|
|
|
|
# Always have a finalize hook, regardless of whether the class wants a
|
|
# custom hook.
|
|
cgThings.append(CGClassFinalizeHook(descriptor))
|
|
|
|
# Only generate a trace hook if the class wants a custom hook.
|
|
if (descriptor.customTrace):
|
|
cgThings.append(CGClassTraceHook(descriptor))
|
|
|
|
if descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.append(CGNativePropertyHooks(descriptor))
|
|
if descriptor.concrete:
|
|
cgThings.append(CGDOMJSClass(descriptor))
|
|
|
|
if descriptor.interface.hasInterfaceObject():
|
|
cgThings.append(CGClassConstructHook(descriptor))
|
|
cgThings.append(CGClassHasInstanceHook(descriptor))
|
|
cgThings.append(CGInterfaceObjectJSClass(descriptor))
|
|
|
|
if descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.append(CGPrototypeJSClass(descriptor))
|
|
|
|
properties = PropertyArrays(descriptor)
|
|
cgThings.append(CGGeneric(define=str(properties)))
|
|
cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
|
|
if descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.append(CGIndenter(CGGetProtoObjectMethod(descriptor)))
|
|
else:
|
|
cgThings.append(CGIndenter(CGGetConstructorObjectMethod(descriptor)))
|
|
|
|
# Set up our Xray callbacks as needed. Note that we don't need to do
|
|
# it in workers.
|
|
if ((descriptor.concrete or
|
|
descriptor.interface.hasInterfacePrototypeObject()) and
|
|
not descriptor.workers):
|
|
cgThings.append(CGResolveProperty(descriptor, properties))
|
|
cgThings.append(CGEnumerateProperties(descriptor, properties))
|
|
|
|
if descriptor.interface.hasInterfaceObject():
|
|
cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
|
|
|
|
if descriptor.concrete:
|
|
if descriptor.wrapperCache:
|
|
cgThings.append(CGWrapMethod(descriptor))
|
|
else:
|
|
cgThings.append(CGWrapNonWrapperCacheMethod(descriptor))
|
|
|
|
cgThings = CGList(cgThings)
|
|
cgThings = CGWrapper(cgThings, post='\n')
|
|
self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
|
|
cgThings),
|
|
post='\n')
|
|
|
|
def declare(self):
|
|
return self.cgRoot.declare()
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
class CGNamespacedEnum(CGThing):
|
|
def __init__(self, namespace, enumName, names, values, comment=""):
|
|
|
|
if not values:
|
|
values = []
|
|
|
|
# Account for explicit enum values.
|
|
entries = []
|
|
for i in range(0, len(names)):
|
|
if len(values) > i and values[i] is not None:
|
|
entry = "%s = %s" % (names[i], values[i])
|
|
else:
|
|
entry = names[i]
|
|
entries.append(entry)
|
|
|
|
# Append a Count.
|
|
entries.append('_' + enumName + '_Count')
|
|
|
|
# Indent.
|
|
entries = [' ' + e for e in entries]
|
|
|
|
# Build the enum body.
|
|
enumstr = comment + 'enum %s\n{\n%s\n};\n' % (enumName, ',\n'.join(entries))
|
|
curr = CGGeneric(declare=enumstr)
|
|
|
|
# Add some whitespace padding.
|
|
curr = CGWrapper(curr, pre='\n',post='\n')
|
|
|
|
# Add the namespace.
|
|
curr = CGNamespace(namespace, curr)
|
|
|
|
# Add the typedef
|
|
typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName)
|
|
curr = CGList([curr, CGGeneric(declare=typedef)])
|
|
|
|
# Save the result.
|
|
self.node = curr
|
|
|
|
def declare(self):
|
|
return self.node.declare()
|
|
def define(self):
|
|
assert False # Only for headers.
|
|
|
|
class CGDictionary(CGThing):
|
|
def __init__(self, dictionary, descriptorProvider):
|
|
self.dictionary = dictionary;
|
|
self.workers = descriptorProvider.workers
|
|
if dictionary.parent:
|
|
parentCGThing = CGDictionary(dictionary.parent, descriptorProvider)
|
|
self.generatable = parentCGThing.generatable
|
|
if not self.generatable:
|
|
# Nothing else to do here
|
|
return
|
|
else:
|
|
self.generatable = True
|
|
# Getting a conversion template for interface types can fail
|
|
# if we don't have a relevant descriptor when self.workers is True.
|
|
# If that happens, just mark ourselves as not being
|
|
# generatable and move on.
|
|
try:
|
|
self.memberInfo = [
|
|
(member,
|
|
getJSToNativeConversionTemplate(member.type,
|
|
descriptorProvider,
|
|
isMember=True,
|
|
isOptional=(not member.defaultValue)))
|
|
for member in dictionary.members ]
|
|
except NoSuchDescriptorError, err:
|
|
if not self.workers:
|
|
raise err
|
|
self.generatable = False
|
|
|
|
def declare(self):
|
|
if not self.generatable:
|
|
return ""
|
|
d = self.dictionary
|
|
if d.parent:
|
|
inheritance = ": public %s " % self.makeClassName(d.parent)
|
|
else:
|
|
inheritance = ""
|
|
memberDecls = [" %s %s;" %
|
|
(self.getMemberType(m), m[0].identifier.name)
|
|
for m in self.memberInfo]
|
|
|
|
return (string.Template(
|
|
"struct ${selfName} ${inheritance}{\n"
|
|
" ${selfName}() {}\n"
|
|
" bool Init(JSContext* cx, JSObject* obj);\n"
|
|
"\n" +
|
|
"\n".join(memberDecls) + "\n"
|
|
"private:\n"
|
|
" // Disallow copy-construction\n"
|
|
" ${selfName}(const ${selfName}&) MOZ_DELETE;\n"
|
|
" static bool InitIds(JSContext* cx);\n"
|
|
" static bool initedIds;\n" +
|
|
"\n".join(" static jsid " +
|
|
self.makeIdName(m.identifier.name) + ";" for
|
|
m in d.members) + "\n"
|
|
"};").substitute( { "selfName": self.makeClassName(d),
|
|
"inheritance": inheritance }))
|
|
|
|
def define(self):
|
|
if not self.generatable:
|
|
return ""
|
|
d = self.dictionary
|
|
if d.parent:
|
|
initParent = ("// Per spec, we init the parent's members first\n"
|
|
"if (!%s::Init(cx, obj)) {\n"
|
|
" return false;\n"
|
|
"}\n" % self.makeClassName(d.parent))
|
|
else:
|
|
initParent = ""
|
|
|
|
memberInits = [CGIndenter(self.getMemberConversion(m)).define()
|
|
for m in self.memberInfo]
|
|
idinit = [CGGeneric('!InternJSString(cx, %s, "%s")' %
|
|
(m.identifier.name + "_id", m.identifier.name))
|
|
for m in d.members]
|
|
idinit = CGList(idinit, " ||\n")
|
|
idinit = CGWrapper(idinit, pre="if (",
|
|
post=(") {\n"
|
|
" return false;\n"
|
|
"}"),
|
|
reindent=True)
|
|
|
|
return string.Template(
|
|
"bool ${selfName}::initedIds = false;\n" +
|
|
"\n".join("jsid ${selfName}::%s = JSID_VOID;" %
|
|
self.makeIdName(m.identifier.name)
|
|
for m in d.members) + "\n"
|
|
"\n"
|
|
"bool\n"
|
|
"${selfName}::InitIds(JSContext* cx)\n"
|
|
"{\n"
|
|
" MOZ_ASSERT(!initedIds);\n"
|
|
"${idInit}\n"
|
|
" initedIds = true;\n"
|
|
" return true;\n"
|
|
"}\n"
|
|
"\n"
|
|
"bool\n"
|
|
"${selfName}::Init(JSContext* cx, JSObject* obj)\n"
|
|
"{\n"
|
|
" if (!initedIds && !InitIds(cx)) {\n"
|
|
" return false;\n"
|
|
" }\n"
|
|
"${initParent}"
|
|
" JSBool found;\n"
|
|
" JS::Value temp;\n"
|
|
"\n"
|
|
"${initMembers}\n"
|
|
" return true;\n"
|
|
"}").substitute({
|
|
"selfName": self.makeClassName(d),
|
|
"initParent": CGIndenter(CGGeneric(initParent)).define(),
|
|
"initMembers": "\n\n".join(memberInits),
|
|
"idInit": CGIndenter(idinit).define()
|
|
})
|
|
|
|
@staticmethod
|
|
def makeDictionaryName(dictionary, workers):
|
|
suffix = "Workers" if workers else ""
|
|
return dictionary.identifier.name + suffix
|
|
|
|
def makeClassName(self, dictionary):
|
|
return self.makeDictionaryName(dictionary, self.workers)
|
|
|
|
def getMemberType(self, memberInfo):
|
|
(member, (templateBody, declType,
|
|
holderType, dealWithOptional)) = memberInfo
|
|
# We can't handle having a holderType here
|
|
assert holderType is None
|
|
if dealWithOptional:
|
|
declType = CGWrapper(declType, pre="Optional< ", post=" >")
|
|
return declType.define()
|
|
|
|
def getMemberConversion(self, memberInfo):
|
|
(member, (templateBody, declType,
|
|
holderType, dealWithOptional)) = memberInfo
|
|
replacements = { "val": "temp",
|
|
"valPtr": "&temp",
|
|
# Use this->%s to refer to members, because we don't
|
|
# control the member names and want to make sure we're
|
|
# talking about the member, not some local that
|
|
# shadows the member. Another option would be to move
|
|
# the guts of init to a static method which is passed
|
|
# an explicit reference to our dictionary object, so
|
|
# we couldn't screw this up even if we wanted to....
|
|
"declName": ("(this->%s)" % member.identifier.name),
|
|
# We need a holder name for external interfaces, but
|
|
# it's scoped down to the conversion so we can just use
|
|
# anything we want.
|
|
"holderName": "holder"}
|
|
# We can't handle having a holderType here
|
|
assert holderType is None
|
|
if dealWithOptional:
|
|
replacements["declName"] = "(" + replacements["declName"] + ".Value())"
|
|
|
|
conversionReplacements = {
|
|
"propId" : self.makeIdName(member.identifier.name),
|
|
"prop": "(this->%s)" % member.identifier.name,
|
|
"convert": string.Template(templateBody).substitute(replacements)
|
|
}
|
|
conversion = ("if (!JS_HasPropertyById(cx, obj, ${propId}, &found)) {\n"
|
|
" return false;\n"
|
|
"}\n")
|
|
if member.defaultValue:
|
|
conversion += (
|
|
"if (found) {\n"
|
|
" if (!JS_GetPropertyById(cx, obj, ${propId}, &temp)) {\n"
|
|
" return false;\n"
|
|
" }\n"
|
|
"} else {\n"
|
|
" temp = ${defaultVal};\n"
|
|
"}\n"
|
|
"${convert}")
|
|
conversionReplacements["defaultVal"] = (
|
|
convertIDLDefaultValueToJSVal(member.defaultValue))
|
|
else:
|
|
conversion += (
|
|
"if (found) {\n"
|
|
" ${prop}.Construct();\n"
|
|
" if (!JS_GetPropertyById(cx, obj, ${propId}, &temp)) {\n"
|
|
" return false;\n"
|
|
" }\n"
|
|
"${convert}\n"
|
|
"}")
|
|
conversionReplacements["convert"] = CGIndenter(
|
|
CGGeneric(conversionReplacements["convert"])).define()
|
|
|
|
return CGGeneric(
|
|
string.Template(conversion).substitute(conversionReplacements)
|
|
)
|
|
|
|
@staticmethod
|
|
def makeIdName(name):
|
|
return name + "_id"
|
|
|
|
class CGRegisterProtos(CGAbstractMethod):
|
|
def __init__(self, config):
|
|
CGAbstractMethod.__init__(self, None, 'Register', 'void',
|
|
[Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')])
|
|
self.config = config
|
|
|
|
def _defineMacro(self):
|
|
return """
|
|
#define REGISTER_PROTO(_dom_class) \\
|
|
aNameSpaceManager->RegisterDefineDOMInterface(NS_LITERAL_STRING(#_dom_class), _dom_class##Binding::DefineDOMInterface);\n\n"""
|
|
def _undefineMacro(self):
|
|
return "\n#undef REGISTER_PROTO"
|
|
def _registerProtos(self):
|
|
lines = ["REGISTER_PROTO(%s);" % desc.name
|
|
for desc in self.config.getDescriptors(hasInterfaceObject=True,
|
|
isExternal=False,
|
|
workers=False,
|
|
register=True)]
|
|
return '\n'.join(lines) + '\n'
|
|
def definition_body(self):
|
|
return self._defineMacro() + self._registerProtos() + self._undefineMacro()
|
|
|
|
class CGBindingRoot(CGThing):
|
|
"""
|
|
Root codegen class for binding generation. Instantiate the class, and call
|
|
declare or define to generate header or cpp code (respectively).
|
|
"""
|
|
def __init__(self, config, prefix, webIDLFile):
|
|
descriptors = config.getDescriptors(webIDLFile=webIDLFile,
|
|
hasInterfaceOrInterfacePrototypeObject=True)
|
|
dictionaries = config.getDictionaries(webIDLFile)
|
|
|
|
forwardDeclares = [CGClassForwardDeclare('XPCWrappedNativeScope')]
|
|
|
|
descriptorsForForwardDeclaration = list(descriptors)
|
|
for dictionary in dictionaries:
|
|
curDict = dictionary
|
|
ifacemembers = []
|
|
while curDict:
|
|
ifacemembers.extend([m.type.unroll().inner for m
|
|
in curDict.members
|
|
if m.type.unroll().isInterface()])
|
|
curDict = curDict.parent
|
|
# Put in all the non-worker descriptors
|
|
descriptorsForForwardDeclaration.extend(
|
|
[config.getDescriptor(iface.identifier.name, False) for
|
|
iface in ifacemembers])
|
|
# And now the worker ones. But these may not exist, so we
|
|
# have to be more careful.
|
|
for iface in ifacemembers:
|
|
try:
|
|
descriptorsForForwardDeclaration.append(
|
|
config.getDescriptor(iface.identifier.name, True))
|
|
except NoSuchDescriptorError:
|
|
# just move along
|
|
pass
|
|
|
|
for x in descriptorsForForwardDeclaration:
|
|
nativeType = x.nativeType
|
|
components = x.nativeType.split('::')
|
|
className = components[-1]
|
|
# JSObject is a struct, not a class
|
|
declare = CGClassForwardDeclare(className, className is "JSObject")
|
|
if len(components) > 1:
|
|
declare = CGNamespace.build(components[:-1],
|
|
CGWrapper(declare, declarePre='\n',
|
|
declarePost='\n'),
|
|
declareOnly=True)
|
|
forwardDeclares.append(CGWrapper(declare, declarePost='\n'))
|
|
|
|
forwardDeclares = CGList(forwardDeclares)
|
|
|
|
descriptorsWithPrototype = filter(lambda d: d.interface.hasInterfacePrototypeObject(),
|
|
descriptors)
|
|
traitsClasses = [CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype]
|
|
|
|
# We must have a 1:1 mapping here, skip for prototypes that have more
|
|
# than one concrete class implementation.
|
|
traitsClasses.extend([CGPrototypeIDMapClass(d) for d in descriptorsWithPrototype
|
|
if d.uniqueImplementation])
|
|
|
|
# Wrap all of that in our namespaces.
|
|
if len(traitsClasses) > 0:
|
|
traitsClasses = CGNamespace.build(['mozilla', 'dom'],
|
|
CGWrapper(CGList(traitsClasses),
|
|
declarePre='\n'),
|
|
declareOnly=True)
|
|
traitsClasses = CGWrapper(traitsClasses, declarePost='\n')
|
|
else:
|
|
traitsClasses = None
|
|
|
|
# Do codegen for all the enums
|
|
def makeEnum(e):
|
|
return CGNamespace.build([e.identifier.name + "Values"],
|
|
CGEnum(e))
|
|
def makeEnumTypedef(e):
|
|
return CGGeneric(declare=("typedef %sValues::valuelist %s;\n" %
|
|
(e.identifier.name, e.identifier.name)))
|
|
cgthings = [ fun(e) for e in config.getEnums(webIDLFile)
|
|
for fun in [makeEnum, makeEnumTypedef] ]
|
|
|
|
# Do codegen for all the dictionaries. We have to be a bit careful
|
|
# here, because we have to generate these in order from least derived to
|
|
# most derived so that class inheritance works out.
|
|
#
|
|
# XXXbz this will fail if we have two webidl files A and B such that A
|
|
# declares a dictionary which inherits from a dictionary in B and B
|
|
# declares a dictionary (possibly a different one!) that inherits from a
|
|
# dictionary in A. The good news is that I expect this to never happen.
|
|
reSortedDictionaries = []
|
|
while len(dictionaries) != 0:
|
|
toMove = [d for d in dictionaries if d.parent not in dictionaries]
|
|
dictionaries = [d for d in dictionaries if d.parent in dictionaries]
|
|
reSortedDictionaries.extend(toMove)
|
|
|
|
dictionaries = reSortedDictionaries
|
|
cgthings.extend([CGDictionary(d, config.getDescriptorProvider(True))
|
|
for d in dictionaries])
|
|
cgthings.extend([CGDictionary(d, config.getDescriptorProvider(False))
|
|
for d in dictionaries])
|
|
|
|
# Do codegen for all the descriptors
|
|
cgthings.extend([CGDescriptor(x) for x in descriptors])
|
|
|
|
# And make sure we have the right number of newlines at the end
|
|
curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(['mozilla', 'dom'],
|
|
CGWrapper(curr, pre="\n"))
|
|
|
|
curr = CGList([forwardDeclares,
|
|
CGWrapper(CGGeneric("using namespace mozilla::dom;"),
|
|
defineOnly=True),
|
|
traitsClasses, curr],
|
|
"\n")
|
|
|
|
# Add header includes.
|
|
curr = CGHeaders(descriptors,
|
|
dictionaries,
|
|
['mozilla/dom/BindingUtils.h',
|
|
'mozilla/dom/DOMJSClass.h'],
|
|
['mozilla/dom/Nullable.h',
|
|
'PrimitiveConversions.h',
|
|
'XPCQuickStubs.h',
|
|
'nsDOMQS.h',
|
|
'AccessCheck.h',
|
|
'WorkerPrivate.h',
|
|
'nsContentUtils.h',
|
|
'mozilla/Preferences.h',
|
|
# Have to include nsDOMQS.h to get fast arg unwrapping
|
|
# for old-binding things with castability.
|
|
'nsDOMQS.h'
|
|
],
|
|
curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard(prefix, curr)
|
|
|
|
# Add the auto-generated comment.
|
|
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
|
|
|
|
# Store the final result.
|
|
self.root = curr
|
|
|
|
def declare(self):
|
|
return stripTrailingWhitespace(self.root.declare())
|
|
def define(self):
|
|
return stripTrailingWhitespace(self.root.define())
|
|
|
|
|
|
class GlobalGenRoots():
|
|
"""
|
|
Roots for global codegen.
|
|
|
|
To generate code, call the method associated with the target, and then
|
|
call the appropriate define/declare method.
|
|
"""
|
|
|
|
@staticmethod
|
|
def PrototypeList(config):
|
|
|
|
# Prototype ID enum.
|
|
protos = [d.name for d in config.getDescriptors(hasInterfacePrototypeObject=True)]
|
|
idEnum = CGNamespacedEnum('id', 'ID', protos, [0])
|
|
idEnum = CGList([idEnum])
|
|
idEnum.append(CGGeneric(declare="const unsigned MaxProtoChainLength = " +
|
|
str(config.maxProtoChainLength) + ";\n\n"))
|
|
|
|
# Wrap all of that in our namespaces.
|
|
idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'],
|
|
CGWrapper(idEnum, pre='\n'))
|
|
idEnum = CGWrapper(idEnum, post='\n')
|
|
|
|
curr = CGList([idEnum])
|
|
|
|
# Constructor ID enum.
|
|
constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True,
|
|
hasInterfacePrototypeObject=False)]
|
|
idEnum = CGNamespacedEnum('id', 'ID', constructors, [0])
|
|
|
|
# Wrap all of that in our namespaces.
|
|
idEnum = CGNamespace.build(['mozilla', 'dom', 'constructors'],
|
|
CGWrapper(idEnum, pre='\n'))
|
|
idEnum = CGWrapper(idEnum, post='\n')
|
|
|
|
curr.append(idEnum)
|
|
|
|
traitsDecl = CGGeneric(declare="""
|
|
template <prototypes::ID PrototypeID>
|
|
struct PrototypeTraits;
|
|
|
|
template <class ConcreteClass>
|
|
struct PrototypeIDMap;
|
|
""")
|
|
|
|
traitsDecl = CGNamespace.build(['mozilla', 'dom'],
|
|
CGWrapper(traitsDecl, post='\n'))
|
|
|
|
curr.append(traitsDecl)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard('PrototypeList', curr)
|
|
|
|
# Add the auto-generated comment.
|
|
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def RegisterBindings(config):
|
|
|
|
# TODO - Generate the methods we want
|
|
curr = CGRegisterProtos(config)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(['mozilla', 'dom'],
|
|
CGWrapper(curr, post='\n'))
|
|
curr = CGWrapper(curr, post='\n')
|
|
|
|
# Add the includes
|
|
defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
|
|
for desc in config.getDescriptors(hasInterfaceObject=True,
|
|
workers=False,
|
|
register=True)]
|
|
defineIncludes.append('nsScriptNameSpaceManager.h')
|
|
curr = CGHeaders([], [], [], defineIncludes, curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard('RegisterBindings', curr)
|
|
|
|
# Done.
|
|
return curr
|