diff options
Diffstat (limited to '')
81 files changed, 21091 insertions, 0 deletions
diff --git a/dom/bindings/parser/README b/dom/bindings/parser/README new file mode 100644 index 0000000000..94b64b8845 --- /dev/null +++ b/dom/bindings/parser/README @@ -0,0 +1 @@ +A WebIDL parser written in Python to be used in Mozilla.
\ No newline at end of file diff --git a/dom/bindings/parser/UPSTREAM b/dom/bindings/parser/UPSTREAM new file mode 100644 index 0000000000..7ac5899379 --- /dev/null +++ b/dom/bindings/parser/UPSTREAM @@ -0,0 +1 @@ +http://dev.w3.org/cvsweb/~checkout~/2006/webapi/WebIDL/Overview.html?rev=1.409;content-type=text%2Fhtml%3b+charset=utf-8
\ No newline at end of file diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py new file mode 100644 index 0000000000..f1fafb56c0 --- /dev/null +++ b/dom/bindings/parser/WebIDL.py @@ -0,0 +1,9139 @@ +# 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/. + +""" A WebIDL parser. """ + +import copy +import math +import os +import re +import string +import traceback +from collections import OrderedDict, defaultdict +from itertools import chain + +from ply import lex, yacc + +# Machinery + + +def parseInt(literal): + string = literal + sign = 0 + base = 0 + + if string[0] == "-": + sign = -1 + string = string[1:] + else: + sign = 1 + + if string[0] == "0" and len(string) > 1: + if string[1] == "x" or string[1] == "X": + base = 16 + string = string[2:] + else: + base = 8 + string = string[1:] + else: + base = 10 + + value = int(string, base) + return value * sign + + +def enum(*names, **kw): + class Foo(object): + attrs = OrderedDict() + + def __init__(self, names): + for v, k in enumerate(names): + self.attrs[k] = v + + def __getattr__(self, attr): + if attr in self.attrs: + return self.attrs[attr] + raise AttributeError + + def __setattr__(self, name, value): # this makes it read-only + raise NotImplementedError + + if "base" not in kw: + return Foo(names) + return Foo(chain(kw["base"].attrs.keys(), names)) + + +class WebIDLError(Exception): + def __init__(self, message, locations, warning=False): + self.message = message + self.locations = [str(loc) for loc in locations] + self.warning = warning + + def __str__(self): + return "%s: %s%s%s" % ( + self.warning and "warning" or "error", + self.message, + ", " if len(self.locations) != 0 else "", + "\n".join(self.locations), + ) + + +class Location(object): + def __init__(self, lexer, lineno, lexpos, filename): + self._line = None + self._lineno = lineno + self._lexpos = lexpos + self._lexdata = lexer.lexdata + self._file = filename if filename else "<unknown>" + + def __eq__(self, other): + return self._lexpos == other._lexpos and self._file == other._file + + def filename(self): + return self._file + + def resolve(self): + if self._line: + return + + startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1 + endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80) + if endofline != -1: + self._line = self._lexdata[startofline:endofline] + else: + self._line = self._lexdata[startofline:] + self._colno = self._lexpos - startofline + + # Our line number seems to point to the start of self._lexdata + self._lineno += self._lexdata.count("\n", 0, startofline) + + def get(self): + self.resolve() + return "%s line %s:%s" % (self._file, self._lineno, self._colno) + + def _pointerline(self): + return " " * self._colno + "^" + + def __str__(self): + self.resolve() + return "%s line %s:%s\n%s\n%s" % ( + self._file, + self._lineno, + self._colno, + self._line, + self._pointerline(), + ) + + +class BuiltinLocation(object): + def __init__(self, text): + self.msg = text + "\n" + + def __eq__(self, other): + return isinstance(other, BuiltinLocation) and self.msg == other.msg + + def filename(self): + return "<builtin>" + + def resolve(self): + pass + + def get(self): + return self.msg + + def __str__(self): + return self.get() + + +# Data Model + + +class IDLObject(object): + def __init__(self, location): + self.location = location + self.userData = dict() + + def filename(self): + return self.location.filename() + + def isInterface(self): + return False + + def isNamespace(self): + return False + + def isInterfaceMixin(self): + return False + + def isEnum(self): + return False + + def isCallback(self): + return False + + def isType(self): + return False + + def isDictionary(self): + return False + + def isUnion(self): + return False + + def isTypedef(self): + return False + + def getUserData(self, key, default): + return self.userData.get(key, default) + + def setUserData(self, key, value): + self.userData[key] = value + + def addExtendedAttributes(self, attrs): + assert False # Override me! + + def handleExtendedAttribute(self, attr): + assert False # Override me! + + def _getDependentObjects(self): + assert False # Override me! + + def getDeps(self, visited=None): + """Return a set of files that this object depends on. If any of + these files are changed the parser needs to be rerun to regenerate + a new IDLObject. + + The visited argument is a set of all the objects already visited. + We must test to see if we are in it, and if so, do nothing. This + prevents infinite recursion.""" + + # NB: We can't use visited=set() above because the default value is + # evaluated when the def statement is evaluated, not when the function + # is executed, so there would be one set for all invocations. + if visited is None: + visited = set() + + if self in visited: + return set() + + visited.add(self) + + deps = set() + if self.filename() != "<builtin>": + deps.add(self.filename()) + + for d in self._getDependentObjects(): + deps.update(d.getDeps(visited)) + + return deps + + +class IDLScope(IDLObject): + def __init__(self, location, parentScope, identifier): + IDLObject.__init__(self, location) + + self.parentScope = parentScope + if identifier: + assert isinstance(identifier, IDLIdentifier) + self._name = identifier + else: + self._name = None + + self._dict = {} + self.globalNames = set() + # A mapping from global name to the set of global interfaces + # that have that global name. + self.globalNameMapping = defaultdict(set) + + def __str__(self): + return self.QName() + + def QName(self): + # It's possible for us to be called before __init__ has been called, for + # the IDLObjectWithScope case. In that case, self._name won't be set yet. + if hasattr(self, "_name"): + name = self._name + else: + name = None + if name: + return name.QName() + "::" + return "::" + + def ensureUnique(self, identifier, object): + """ + Ensure that there is at most one 'identifier' in scope ('self'). + Note that object can be None. This occurs if we end up here for an + interface type we haven't seen yet. + """ + assert isinstance(identifier, IDLUnresolvedIdentifier) + assert not object or isinstance(object, IDLObjectWithIdentifier) + assert not object or object.identifier == identifier + + if identifier.name in self._dict: + if not object: + return + + # ensureUnique twice with the same object is not allowed + assert id(object) != id(self._dict[identifier.name]) + + replacement = self.resolveIdentifierConflict( + self, identifier, self._dict[identifier.name], object + ) + self._dict[identifier.name] = replacement + return + + self.addNewIdentifier(identifier, object) + + def addNewIdentifier(self, identifier, object): + assert object + + self._dict[identifier.name] = object + + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): + if ( + isinstance(originalObject, IDLExternalInterface) + and isinstance(newObject, IDLExternalInterface) + and originalObject.identifier.name == newObject.identifier.name + ): + return originalObject + + if isinstance(originalObject, IDLExternalInterface) or isinstance( + newObject, IDLExternalInterface + ): + raise WebIDLError( + "Name collision between " + "interface declarations for identifier '%s' at '%s' and '%s'" + % (identifier.name, originalObject.location, newObject.location), + [], + ) + + if isinstance(originalObject, IDLDictionary) or isinstance( + newObject, IDLDictionary + ): + raise WebIDLError( + "Name collision between dictionary declarations for " + "identifier '%s'.\n%s\n%s" + % (identifier.name, originalObject.location, newObject.location), + [], + ) + + # We do the merging of overloads here as opposed to in IDLInterface + # because we need to merge overloads of LegacyFactoryFunctions and we need to + # detect conflicts in those across interfaces. See also the comment in + # IDLInterface.addExtendedAttributes for "LegacyFactoryFunction". + if isinstance(originalObject, IDLMethod) and isinstance(newObject, IDLMethod): + return originalObject.addOverload(newObject) + + # Default to throwing, derived classes can override. + raise self.createIdentifierConflictError(identifier, originalObject, newObject) + + def createIdentifierConflictError(self, identifier, originalObject, newObject): + conflictdesc = "\n\t%s at %s\n\t%s at %s" % ( + originalObject, + originalObject.location, + newObject, + newObject.location, + ) + + return WebIDLError( + "Multiple unresolvable definitions of identifier '%s' in scope '%s'%s" + % (identifier.name, str(self), conflictdesc), + [], + ) + + def _lookupIdentifier(self, identifier): + return self._dict[identifier.name] + + def lookupIdentifier(self, identifier): + assert isinstance(identifier, IDLIdentifier) + assert identifier.scope == self + return self._lookupIdentifier(identifier) + + def addIfaceGlobalNames(self, interfaceName, globalNames): + """Record the global names (from |globalNames|) that can be used in + [Exposed] to expose things in a global named |interfaceName|""" + self.globalNames.update(globalNames) + for name in globalNames: + self.globalNameMapping[name].add(interfaceName) + + +class IDLIdentifier(IDLObject): + def __init__(self, location, scope, name): + IDLObject.__init__(self, location) + + self.name = name + assert isinstance(scope, IDLScope) + self.scope = scope + + def __str__(self): + return self.QName() + + def QName(self): + return self.scope.QName() + self.name + + def __hash__(self): + return self.QName().__hash__() + + def __eq__(self, other): + return self.QName() == other.QName() + + def object(self): + return self.scope.lookupIdentifier(self) + + +class IDLUnresolvedIdentifier(IDLObject): + def __init__( + self, location, name, allowDoubleUnderscore=False, allowForbidden=False + ): + IDLObject.__init__(self, location) + + assert len(name) > 0 + + if name == "__noSuchMethod__": + raise WebIDLError("__noSuchMethod__ is deprecated", [location]) + + if name[:2] == "__" and not allowDoubleUnderscore: + raise WebIDLError("Identifiers beginning with __ are reserved", [location]) + if name[0] == "_" and not allowDoubleUnderscore: + name = name[1:] + if name in ["constructor", "toString"] and not allowForbidden: + raise WebIDLError( + "Cannot use reserved identifier '%s'" % (name), [location] + ) + + self.name = name + + def __str__(self): + return self.QName() + + def QName(self): + return "<unresolved scope>::" + self.name + + def resolve(self, scope, object): + assert isinstance(scope, IDLScope) + assert not object or isinstance(object, IDLObjectWithIdentifier) + assert not object or object.identifier == self + + scope.ensureUnique(self, object) + + identifier = IDLIdentifier(self.location, scope, self.name) + if object: + object.identifier = identifier + return identifier + + def finish(self): + assert False # Should replace with a resolved identifier first. + + +class IDLObjectWithIdentifier(IDLObject): + def __init__(self, location, parentScope, identifier): + IDLObject.__init__(self, location) + + assert isinstance(identifier, IDLUnresolvedIdentifier) + + self.identifier = identifier + + if parentScope: + self.resolve(parentScope) + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + assert isinstance(self.identifier, IDLUnresolvedIdentifier) + self.identifier.resolve(parentScope, self) + + +class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope): + def __init__(self, location, parentScope, identifier): + assert isinstance(identifier, IDLUnresolvedIdentifier) + + IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) + IDLScope.__init__(self, location, parentScope, self.identifier) + + +class IDLIdentifierPlaceholder(IDLObjectWithIdentifier): + def __init__(self, location, identifier): + assert isinstance(identifier, IDLUnresolvedIdentifier) + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + + def finish(self, scope): + try: + scope._lookupIdentifier(self.identifier) + except Exception: + raise WebIDLError( + "Unresolved type '%s'." % self.identifier, [self.location] + ) + + obj = self.identifier.resolve(scope, None) + return scope.lookupIdentifier(obj) + + +class IDLExposureMixins: + def __init__(self, location): + # _exposureGlobalNames are the global names listed in our [Exposed] + # extended attribute. exposureSet is the exposure set as defined in the + # Web IDL spec: it contains interface names. + self._exposureGlobalNames = set() + self.exposureSet = set() + self._location = location + self._globalScope = None + + def finish(self, scope): + assert scope.parentScope is None + self._globalScope = scope + + if "*" in self._exposureGlobalNames: + self._exposureGlobalNames = scope.globalNames + else: + # Verify that our [Exposed] value, if any, makes sense. + for globalName in self._exposureGlobalNames: + if globalName not in scope.globalNames: + raise WebIDLError( + "Unknown [Exposed] value %s" % globalName, [self._location] + ) + + # Verify that we are exposed _somwhere_ if we have some place to be + # exposed. We don't want to assert that we're definitely exposed + # because a lot of our parser tests have small-enough IDL snippets that + # they don't include any globals, and we don't really want to go through + # and add global interfaces and [Exposed] annotations to all those + # tests. + if len(scope.globalNames) != 0: + if len(self._exposureGlobalNames) == 0 and not self.isPseudoInterface(): + raise WebIDLError( + ( + "'%s' is not exposed anywhere even though we have " + "globals to be exposed to" + ) + % self, + [self.location], + ) + + globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposureSet) + + def isExposedInWindow(self): + return "Window" in self.exposureSet + + def isExposedInAnyWorker(self): + return len(self.getWorkerExposureSet()) > 0 + + def isExposedInWorkerDebugger(self): + return len(self.getWorkerDebuggerExposureSet()) > 0 + + def isExposedInAnyWorklet(self): + return len(self.getWorkletExposureSet()) > 0 + + def isExposedInSomeButNotAllWorkers(self): + """ + Returns true if the Exposed extended attribute for this interface + exposes it in some worker globals but not others. The return value does + not depend on whether the interface is exposed in Window or System + globals. + """ + if not self.isExposedInAnyWorker(): + return False + workerScopes = self.parentScope.globalNameMapping["Worker"] + return len(workerScopes.difference(self.exposureSet)) > 0 + + def isExposedInShadowRealms(self): + return "ShadowRealmGlobalScope" in self.exposureSet + + def getWorkerExposureSet(self): + workerScopes = self._globalScope.globalNameMapping["Worker"] + return workerScopes.intersection(self.exposureSet) + + def getWorkletExposureSet(self): + workletScopes = self._globalScope.globalNameMapping["Worklet"] + return workletScopes.intersection(self.exposureSet) + + def getWorkerDebuggerExposureSet(self): + workerDebuggerScopes = self._globalScope.globalNameMapping["WorkerDebugger"] + return workerDebuggerScopes.intersection(self.exposureSet) + + +class IDLExternalInterface(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, identifier): + assert isinstance(identifier, IDLUnresolvedIdentifier) + assert isinstance(parentScope, IDLScope) + self.parent = None + IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) + IDLObjectWithIdentifier.resolve(self, parentScope) + + def finish(self, scope): + pass + + def validate(self): + pass + + def isIteratorInterface(self): + return False + + def isAsyncIteratorInterface(self): + return False + + def isExternal(self): + return True + + def isInterface(self): + return True + + def addExtendedAttributes(self, attrs): + if len(attrs) != 0: + raise WebIDLError( + "There are no extended attributes that are " + "allowed on external interfaces", + [attrs[0].location, self.location], + ) + + def resolve(self, parentScope): + pass + + def getJSImplementation(self): + return None + + def isJSImplemented(self): + return False + + def hasProbablyShortLivingWrapper(self): + return False + + def _getDependentObjects(self): + return set() + + +class IDLPartialDictionary(IDLObject): + def __init__(self, location, name, members, nonPartialDictionary): + assert isinstance(name, IDLUnresolvedIdentifier) + + IDLObject.__init__(self, location) + self.identifier = name + self.members = members + self._nonPartialDictionary = nonPartialDictionary + self._finished = False + nonPartialDictionary.addPartialDictionary(self) + + def addExtendedAttributes(self, attrs): + pass + + def finish(self, scope): + if self._finished: + return + self._finished = True + + # Need to make sure our non-partial dictionary gets + # finished so it can report cases when we only have partial + # dictionaries. + self._nonPartialDictionary.finish(scope) + + def validate(self): + pass + + +class IDLPartialInterfaceOrNamespace(IDLObject): + def __init__(self, location, name, members, nonPartialInterfaceOrNamespace): + assert isinstance(name, IDLUnresolvedIdentifier) + + IDLObject.__init__(self, location) + self.identifier = name + self.members = members + # propagatedExtendedAttrs are the ones that should get + # propagated to our non-partial interface. + self.propagatedExtendedAttrs = [] + self._haveSecureContextExtendedAttribute = False + self._nonPartialInterfaceOrNamespace = nonPartialInterfaceOrNamespace + self._finished = False + nonPartialInterfaceOrNamespace.addPartial(self) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier == "LegacyFactoryFunction": + self.propagatedExtendedAttrs.append(attr) + elif identifier == "SecureContext": + self._haveSecureContextExtendedAttribute = True + # This gets propagated to all our members. + for member in self.members: + if member.getExtendedAttribute("SecureContext"): + raise WebIDLError( + "[SecureContext] specified on both a " + "partial interface member and on the " + "partial interface itself", + [member.location, attr.location], + ) + member.addExtendedAttributes([attr]) + elif identifier == "Exposed": + # This just gets propagated to all our members. + for member in self.members: + if len(member._exposureGlobalNames) != 0: + raise WebIDLError( + "[Exposed] specified on both a " + "partial interface member and on the " + "partial interface itself", + [member.location, attr.location], + ) + member.addExtendedAttributes([attr]) + else: + raise WebIDLError( + "Unknown extended attribute %s on partial " + "interface" % identifier, + [attr.location], + ) + + def finish(self, scope): + if self._finished: + return + self._finished = True + if ( + not self._haveSecureContextExtendedAttribute + and self._nonPartialInterfaceOrNamespace.getExtendedAttribute( + "SecureContext" + ) + ): + # This gets propagated to all our members. + for member in self.members: + if member.getExtendedAttribute("SecureContext"): + raise WebIDLError( + "[SecureContext] specified on both a " + "partial interface member and on the " + "non-partial interface", + [ + member.location, + self._nonPartialInterfaceOrNamespace.location, + ], + ) + member.addExtendedAttributes( + [ + IDLExtendedAttribute( + self._nonPartialInterfaceOrNamespace.location, + ("SecureContext",), + ) + ] + ) + # Need to make sure our non-partial interface or namespace gets + # finished so it can report cases when we only have partial + # interfaces/namespaces. + self._nonPartialInterfaceOrNamespace.finish(scope) + + def validate(self): + pass + + +def convertExposedAttrToGlobalNameSet(exposedAttr, targetSet): + assert len(targetSet) == 0 + if exposedAttr.hasValue(): + targetSet.add(exposedAttr.value()) + else: + assert exposedAttr.hasArgs() + targetSet.update(exposedAttr.args()) + + +def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): + for name in nameSet: + exposureSet.update(globalScope.globalNameMapping[name]) + + +# Because WebIDL allows static and regular operations with the same identifier +# we use a special class to be able to store them both in the scope for the +# same identifier. +class IDLOperations: + def __init__(self, static=None, regular=None): + self.static = static + self.regular = regular + + +class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins): + def __init__(self, location, parentScope, name): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + + self._finished = False + self.members = [] + self._partials = [] + self._extendedAttrDict = {} + self._isKnownNonPartial = False + + IDLObjectWithScope.__init__(self, location, parentScope, name) + IDLExposureMixins.__init__(self, location) + + def finish(self, scope): + if not self._isKnownNonPartial: + raise WebIDLError( + "%s does not have a non-partial declaration" % str(self), + [self.location], + ) + + IDLExposureMixins.finish(self, scope) + + # Now go ahead and merge in our partials. + for partial in self._partials: + partial.finish(scope) + self.addExtendedAttributes(partial.propagatedExtendedAttrs) + self.members.extend(partial.members) + + def addNewIdentifier(self, identifier, object): + if isinstance(object, IDLMethod): + if object.isStatic(): + object = IDLOperations(static=object) + else: + object = IDLOperations(regular=object) + + IDLScope.addNewIdentifier(self, identifier, object) + + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): + assert isinstance(scope, IDLScope) + assert isinstance(newObject, IDLInterfaceMember) + + # The identifier of a regular operation or static operation must not be + # the same as the identifier of a constant or attribute. + if isinstance(newObject, IDLMethod) != isinstance( + originalObject, IDLOperations + ): + if isinstance(originalObject, IDLOperations): + if originalObject.regular is not None: + originalObject = originalObject.regular + else: + assert originalObject.static is not None + originalObject = originalObject.static + + raise self.createIdentifierConflictError( + identifier, originalObject, newObject + ) + + if isinstance(newObject, IDLMethod): + originalOperations = originalObject + if newObject.isStatic(): + if originalOperations.static is None: + originalOperations.static = newObject + return originalOperations + + originalObject = originalOperations.static + else: + if originalOperations.regular is None: + originalOperations.regular = newObject + return originalOperations + + originalObject = originalOperations.regular + + assert isinstance(originalObject, IDLMethod) + else: + assert isinstance(originalObject, IDLInterfaceMember) + + retval = IDLScope.resolveIdentifierConflict( + self, scope, identifier, originalObject, newObject + ) + + if isinstance(newObject, IDLMethod): + if newObject.isStatic(): + originalOperations.static = retval + else: + originalOperations.regular = retval + + retval = originalOperations + + # Might be a ctor, which isn't in self.members + if newObject in self.members: + self.members.remove(newObject) + return retval + + def typeName(self): + if self.isInterface(): + return "interface" + if self.isNamespace(): + return "namespace" + assert self.isInterfaceMixin() + return "interface mixin" + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def setNonPartial(self, location, members): + if self._isKnownNonPartial: + raise WebIDLError( + "Two non-partial definitions for the " "same %s" % self.typeName(), + [location, self.location], + ) + self._isKnownNonPartial = True + # Now make it look like we were parsed at this new location, since + # that's the place where the interface is "really" defined + self.location = location + # Put the new members at the beginning + self.members = members + self.members + + def addPartial(self, partial): + assert self.identifier.name == partial.identifier.name + self._partials.append(partial) + + def getPartials(self): + # Don't let people mutate our guts. + return list(self._partials) + + def finishMembers(self, scope): + # Assuming we've merged in our partials, set the _exposureGlobalNames on + # any members that don't have it set yet. Note that any partial + # interfaces that had [Exposed] set have already set up + # _exposureGlobalNames on all the members coming from them, so this is + # just implementing the "members default to interface or interface mixin + # that defined them" and "partial interfaces or interface mixins default + # to interface or interface mixin they're a partial for" rules from the + # spec. + for m in self.members: + # If m, or the partial m came from, had [Exposed] + # specified, it already has a nonempty exposure global names set. + if len(m._exposureGlobalNames) == 0: + m._exposureGlobalNames.update(self._exposureGlobalNames) + if m.isAttr() and m.stringifier: + m.expand(self.members) + + # resolve() will modify self.members, so we need to iterate + # over a copy of the member list here. + for member in list(self.members): + member.resolve(self) + + for member in self.members: + member.finish(scope) + + # Now that we've finished our members, which has updated their exposure + # sets, make sure they aren't exposed in places where we are not. + for member in self.members: + if not member.exposureSet.issubset(self.exposureSet): + raise WebIDLError( + "Interface or interface mixin member has " + "larger exposure set than its container", + [member.location, self.location], + ) + + def isExternal(self): + return False + + +class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): + def __init__(self, location, parentScope, name, members, isKnownNonPartial): + self.actualExposureGlobalNames = set() + + assert isKnownNonPartial or len(members) == 0 + IDLInterfaceOrInterfaceMixinOrNamespace.__init__( + self, location, parentScope, name + ) + + if isKnownNonPartial: + self.setNonPartial(location, members) + + def __str__(self): + return "Interface mixin '%s'" % self.identifier.name + + def isInterfaceMixin(self): + return True + + def finish(self, scope): + if self._finished: + return + self._finished = True + + # Expose to the globals of interfaces that includes this mixin if this + # mixin has no explicit [Exposed] so that its members can be exposed + # based on the base interface exposure set. + # + # Make sure this is done before IDLExposureMixins.finish call, since + # that converts our set of exposure global names to an actual exposure + # set. + hasImplicitExposure = len(self._exposureGlobalNames) == 0 + if hasImplicitExposure: + self._exposureGlobalNames.update(self.actualExposureGlobalNames) + + IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope) + + self.finishMembers(scope) + + def validate(self): + for member in self.members: + + if member.isAttr(): + if member.inherit: + raise WebIDLError( + "Interface mixin member cannot include " + "an inherited attribute", + [member.location, self.location], + ) + if member.isStatic(): + raise WebIDLError( + "Interface mixin member cannot include " "a static member", + [member.location, self.location], + ) + + if member.isMethod(): + if member.isStatic(): + raise WebIDLError( + "Interface mixin member cannot include " "a static operation", + [member.location, self.location], + ) + if ( + member.isGetter() + or member.isSetter() + or member.isDeleter() + or member.isLegacycaller() + ): + raise WebIDLError( + "Interface mixin member cannot include a " "special operation", + [member.location, self.location], + ) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier == "SecureContext": + if not attr.noArguments(): + raise WebIDLError( + "[%s] must take no arguments" % identifier, [attr.location] + ) + # This gets propagated to all our members. + for member in self.members: + if member.getExtendedAttribute("SecureContext"): + raise WebIDLError( + "[SecureContext] specified on both " + "an interface mixin member and on" + "the interface mixin itself", + [member.location, attr.location], + ) + member.addExtendedAttributes([attr]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + else: + raise WebIDLError( + "Unknown extended attribute %s on interface" % identifier, + [attr.location], + ) + + attrlist = attr.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + def _getDependentObjects(self): + return set(self.members) + + +class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): + def __init__(self, location, parentScope, name, parent, members, isKnownNonPartial): + assert isKnownNonPartial or not parent + assert isKnownNonPartial or len(members) == 0 + + self.parent = None + self._callback = False + self.maplikeOrSetlikeOrIterable = None + # namedConstructors needs deterministic ordering because bindings code + # outputs the constructs in the order that namedConstructors enumerates + # them. + self.legacyFactoryFunctions = list() + self.legacyWindowAliases = [] + self.includedMixins = set() + # self.interfacesBasedOnSelf is the set of interfaces that inherit from + # self, including self itself. + # Used for distinguishability checking. + self.interfacesBasedOnSelf = set([self]) + self._hasChildInterfaces = False + self._isOnGlobalProtoChain = False + # Pseudo interfaces aren't exposed anywhere, and so shouldn't issue warnings + self._isPseudo = False + + # Tracking of the number of reserved slots we need for our + # members and those of ancestor interfaces. + self.totalMembersInSlots = 0 + # Tracking of the number of own own members we have in slots + self._ownMembersInSlots = 0 + # If this is an iterator interface, we need to know what iterable + # interface we're iterating for in order to get its nativeType. + self.iterableInterface = None + self.asyncIterableInterface = None + # True if we have cross-origin members. + self.hasCrossOriginMembers = False + # True if some descendant (including ourselves) has cross-origin members + self.hasDescendantWithCrossOriginMembers = False + + IDLInterfaceOrInterfaceMixinOrNamespace.__init__( + self, location, parentScope, name + ) + + if isKnownNonPartial: + self.setNonPartial(location, parent, members) + + def ctor(self): + identifier = IDLUnresolvedIdentifier( + self.location, "constructor", allowForbidden=True + ) + try: + return self._lookupIdentifier(identifier).static + except Exception: + return None + + def isIterable(self): + return ( + self.maplikeOrSetlikeOrIterable + and self.maplikeOrSetlikeOrIterable.isIterable() + ) + + def isAsyncIterable(self): + return ( + self.maplikeOrSetlikeOrIterable + and self.maplikeOrSetlikeOrIterable.isAsyncIterable() + ) + + def isIteratorInterface(self): + return self.iterableInterface is not None + + def isAsyncIteratorInterface(self): + return self.asyncIterableInterface is not None + + def getClassName(self): + return self.identifier.name + + def finish(self, scope): + if self._finished: + return + + self._finished = True + + IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope) + + if len(self.legacyWindowAliases) > 0: + if not self.hasInterfaceObject(): + raise WebIDLError( + "Interface %s unexpectedly has [LegacyWindowAlias] " + "and [LegacyNoInterfaceObject] together" % self.identifier.name, + [self.location], + ) + if not self.isExposedInWindow(): + raise WebIDLError( + "Interface %s has [LegacyWindowAlias] " + "but not exposed in Window" % self.identifier.name, + [self.location], + ) + + # Generate maplike/setlike interface members. Since generated members + # need to be treated like regular interface members, do this before + # things like exposure setting. + for member in self.members: + if member.isMaplikeOrSetlikeOrIterable(): + if self.isJSImplemented(): + raise WebIDLError( + "%s declaration used on " + "interface that is implemented in JS" + % (member.maplikeOrSetlikeOrIterableType), + [member.location], + ) + if member.valueType.isObservableArray() or ( + member.hasKeyType() and member.keyType.isObservableArray() + ): + raise WebIDLError( + "%s declaration uses ObservableArray as value or key type" + % (member.maplikeOrSetlikeOrIterableType), + [member.location], + ) + # Check that we only have one interface declaration (currently + # there can only be one maplike/setlike declaration per + # interface) + if self.maplikeOrSetlikeOrIterable: + raise WebIDLError( + "%s declaration used on " + "interface that already has %s " + "declaration" + % ( + member.maplikeOrSetlikeOrIterableType, + self.maplikeOrSetlikeOrIterable.maplikeOrSetlikeOrIterableType, + ), + [self.maplikeOrSetlikeOrIterable.location, member.location], + ) + self.maplikeOrSetlikeOrIterable = member + # If we've got a maplike or setlike declaration, we'll be building all of + # our required methods in Codegen. Generate members now. + self.maplikeOrSetlikeOrIterable.expand(self.members) + + assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder) + parent = self.parent.finish(scope) if self.parent else None + if parent and isinstance(parent, IDLExternalInterface): + raise WebIDLError( + "%s inherits from %s which does not have " + "a definition" % (self.identifier.name, self.parent.identifier.name), + [self.location], + ) + if parent and not isinstance(parent, IDLInterface): + raise WebIDLError( + "%s inherits from %s which is not an interface " + % (self.identifier.name, self.parent.identifier.name), + [self.location, parent.location], + ) + + self.parent = parent + + assert iter(self.members) + + if self.isNamespace(): + assert not self.parent + for m in self.members: + if m.isAttr() or m.isMethod(): + if m.isStatic(): + raise WebIDLError( + "Don't mark things explicitly static " "in namespaces", + [self.location, m.location], + ) + # Just mark all our methods/attributes as static. The other + # option is to duplicate the relevant InterfaceMembers + # production bits but modified to produce static stuff to + # start with, but that sounds annoying. + m.forceStatic() + + if self.parent: + self.parent.finish(scope) + self.parent._hasChildInterfaces = True + + self.totalMembersInSlots = self.parent.totalMembersInSlots + + # Interfaces with [Global] must not have anything inherit from them + if self.parent.getExtendedAttribute("Global"): + # Note: This is not a self.parent.isOnGlobalProtoChain() check + # because ancestors of a [Global] interface can have other + # descendants. + raise WebIDLError( + "[Global] interface has another interface " "inheriting from it", + [self.location, self.parent.location], + ) + + # Make sure that we're not exposed in places where our parent is not + if not self.exposureSet.issubset(self.parent.exposureSet): + raise WebIDLError( + "Interface %s is exposed in globals where its " + "parent interface %s is not exposed." + % (self.identifier.name, self.parent.identifier.name), + [self.location, self.parent.location], + ) + + # Callbacks must not inherit from non-callbacks. + # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending. + if self.isCallback(): + if not self.parent.isCallback(): + raise WebIDLError( + "Callback interface %s inheriting from " + "non-callback interface %s" + % (self.identifier.name, self.parent.identifier.name), + [self.location, self.parent.location], + ) + elif self.parent.isCallback(): + raise WebIDLError( + "Non-callback interface %s inheriting from " + "callback interface %s" + % (self.identifier.name, self.parent.identifier.name), + [self.location, self.parent.location], + ) + + # Interfaces which have interface objects can't inherit + # from [LegacyNoInterfaceObject] interfaces. + if self.parent.getExtendedAttribute( + "LegacyNoInterfaceObject" + ) and not self.getExtendedAttribute("LegacyNoInterfaceObject"): + raise WebIDLError( + "Interface %s does not have " + "[LegacyNoInterfaceObject] but inherits from " + "interface %s which does" + % (self.identifier.name, self.parent.identifier.name), + [self.location, self.parent.location], + ) + + # Interfaces that are not [SecureContext] can't inherit + # from [SecureContext] interfaces. + if self.parent.getExtendedAttribute( + "SecureContext" + ) and not self.getExtendedAttribute("SecureContext"): + raise WebIDLError( + "Interface %s does not have " + "[SecureContext] but inherits from " + "interface %s which does" + % (self.identifier.name, self.parent.identifier.name), + [self.location, self.parent.location], + ) + + for mixin in self.includedMixins: + mixin.finish(scope) + + cycleInGraph = self.findInterfaceLoopPoint(self) + if cycleInGraph: + raise WebIDLError( + "Interface %s has itself as ancestor" % self.identifier.name, + [self.location, cycleInGraph.location], + ) + + self.finishMembers(scope) + + ctor = self.ctor() + if ctor is not None: + if not self.hasInterfaceObject(): + raise WebIDLError( + "Can't have both a constructor and [LegacyNoInterfaceObject]", + [self.location, ctor.location], + ) + + if self.globalNames: + raise WebIDLError( + "Can't have both a constructor and [Global]", + [self.location, ctor.location], + ) + + assert ctor._exposureGlobalNames == self._exposureGlobalNames + ctor._exposureGlobalNames.update(self._exposureGlobalNames) + # Remove the constructor operation from our member list so + # it doesn't get in the way later. + self.members.remove(ctor) + + for ctor in self.legacyFactoryFunctions: + if self.globalNames: + raise WebIDLError( + "Can't have both a legacy factory function and [Global]", + [self.location, ctor.location], + ) + assert len(ctor._exposureGlobalNames) == 0 + ctor._exposureGlobalNames.update(self._exposureGlobalNames) + ctor.finish(scope) + + # Make a copy of our member list, so things that implement us + # can get those without all the stuff we implement ourselves + # admixed. + self.originalMembers = list(self.members) + + for mixin in sorted(self.includedMixins, key=lambda x: x.identifier.name): + for mixinMember in mixin.members: + for member in self.members: + if mixinMember.identifier.name == member.identifier.name and ( + not mixinMember.isMethod() + or not member.isMethod() + or mixinMember.isStatic() == member.isStatic() + ): + raise WebIDLError( + "Multiple definitions of %s on %s coming from 'includes' statements" + % (member.identifier.name, self), + [mixinMember.location, member.location], + ) + self.members.extend(mixin.members) + + for ancestor in self.getInheritedInterfaces(): + ancestor.interfacesBasedOnSelf.add(self) + if ( + ancestor.maplikeOrSetlikeOrIterable is not None + and self.maplikeOrSetlikeOrIterable is not None + ): + raise WebIDLError( + "Cannot have maplike/setlike on %s that " + "inherits %s, which is already " + "maplike/setlike" + % (self.identifier.name, ancestor.identifier.name), + [ + self.maplikeOrSetlikeOrIterable.location, + ancestor.maplikeOrSetlikeOrIterable.location, + ], + ) + + # Deal with interfaces marked [LegacyUnforgeable], now that we have our full + # member list, except unforgeables pulled in from parents. We want to + # do this before we set "originatingInterface" on our unforgeable + # members. + if self.getExtendedAttribute("LegacyUnforgeable"): + # Check that the interface already has all the things the + # spec would otherwise require us to synthesize and is + # missing the ones we plan to synthesize. + if not any(m.isMethod() and m.isStringifier() for m in self.members): + raise WebIDLError( + "LegacyUnforgeable interface %s does not have a " + "stringifier" % self.identifier.name, + [self.location], + ) + + for m in self.members: + if m.identifier.name == "toJSON": + raise WebIDLError( + "LegacyUnforgeable interface %s has a " + "toJSON so we won't be able to add " + "one ourselves" % self.identifier.name, + [self.location, m.location], + ) + + if m.identifier.name == "valueOf" and not m.isStatic(): + raise WebIDLError( + "LegacyUnforgeable interface %s has a valueOf " + "member so we won't be able to add one " + "ourselves" % self.identifier.name, + [self.location, m.location], + ) + + for member in self.members: + if ( + (member.isAttr() or member.isMethod()) + and member.isLegacyUnforgeable() + and not hasattr(member, "originatingInterface") + ): + member.originatingInterface = self + + for member in self.members: + if ( + member.isMethod() and member.getExtendedAttribute("CrossOriginCallable") + ) or ( + member.isAttr() + and ( + member.getExtendedAttribute("CrossOriginReadable") + or member.getExtendedAttribute("CrossOriginWritable") + ) + ): + self.hasCrossOriginMembers = True + break + + if self.hasCrossOriginMembers: + parent = self + while parent: + parent.hasDescendantWithCrossOriginMembers = True + parent = parent.parent + + # Compute slot indices for our members before we pull in unforgeable + # members from our parent. Also, maplike/setlike declarations get a + # slot to hold their backing object. + for member in self.members: + if ( + member.isAttr() + and ( + member.getExtendedAttribute("StoreInSlot") + or member.getExtendedAttribute("Cached") + or member.type.isObservableArray() + ) + ) or member.isMaplikeOrSetlike(): + if self.isJSImplemented() and not member.isMaplikeOrSetlike(): + raise WebIDLError( + "Interface %s is JS-implemented and we " + "don't support [Cached] or [StoreInSlot] or ObservableArray " + "on JS-implemented interfaces" % self.identifier.name, + [self.location, member.location], + ) + if member.slotIndices is None: + member.slotIndices = dict() + member.slotIndices[self.identifier.name] = self.totalMembersInSlots + self.totalMembersInSlots += 1 + if member.getExtendedAttribute("StoreInSlot"): + self._ownMembersInSlots += 1 + + if self.parent: + # Make sure we don't shadow any of the [LegacyUnforgeable] attributes on our + # ancestor interfaces. We don't have to worry about mixins here, because + # those have already been imported into the relevant .members lists. And + # we don't have to worry about anything other than our parent, because it + # has already imported its ancestors' unforgeable attributes into its + # member list. + for unforgeableMember in ( + member + for member in self.parent.members + if (member.isAttr() or member.isMethod()) + and member.isLegacyUnforgeable() + ): + shadows = [ + m + for m in self.members + if (m.isAttr() or m.isMethod()) + and not m.isStatic() + and m.identifier.name == unforgeableMember.identifier.name + ] + if len(shadows) != 0: + locs = [unforgeableMember.location] + [s.location for s in shadows] + raise WebIDLError( + "Interface %s shadows [LegacyUnforgeable] " + "members of %s" + % (self.identifier.name, ancestor.identifier.name), + locs, + ) + # And now just stick it in our members, since we won't be + # inheriting this down the proto chain. If we really cared we + # could try to do something where we set up the unforgeable + # attributes/methods of ancestor interfaces, with their + # corresponding getters, on our interface, but that gets pretty + # complicated and seems unnecessary. + self.members.append(unforgeableMember) + + # At this point, we have all of our members. If the current interface + # uses maplike/setlike, check for collisions anywhere in the current + # interface or higher in the inheritance chain. + if self.maplikeOrSetlikeOrIterable: + testInterface = self + isAncestor = False + while testInterface: + self.maplikeOrSetlikeOrIterable.checkCollisions( + testInterface.members, isAncestor + ) + isAncestor = True + testInterface = testInterface.parent + + # Ensure that there's at most one of each {named,indexed} + # {getter,setter,deleter}, at most one stringifier, + # and at most one legacycaller. Note that this last is not + # quite per spec, but in practice no one overloads + # legacycallers. Also note that in practice we disallow + # indexed deleters, but it simplifies some other code to + # treat deleter analogously to getter/setter by + # prefixing it with "named". + specialMembersSeen = {} + for member in self.members: + if not member.isMethod(): + continue + + if member.isGetter(): + memberType = "getters" + elif member.isSetter(): + memberType = "setters" + elif member.isDeleter(): + memberType = "deleters" + elif member.isStringifier(): + memberType = "stringifiers" + elif member.isLegacycaller(): + memberType = "legacycallers" + else: + continue + + if memberType != "stringifiers" and memberType != "legacycallers": + if member.isNamed(): + memberType = "named " + memberType + else: + assert member.isIndexed() + memberType = "indexed " + memberType + + if memberType in specialMembersSeen: + raise WebIDLError( + "Multiple " + memberType + " on %s" % (self), + [ + self.location, + specialMembersSeen[memberType].location, + member.location, + ], + ) + + specialMembersSeen[memberType] = member + + if self.getExtendedAttribute("LegacyUnenumerableNamedProperties"): + # Check that we have a named getter. + if "named getters" not in specialMembersSeen: + raise WebIDLError( + "Interface with [LegacyUnenumerableNamedProperties] does " + "not have a named getter", + [self.location], + ) + ancestor = self.parent + while ancestor: + if ancestor.getExtendedAttribute("LegacyUnenumerableNamedProperties"): + raise WebIDLError( + "Interface with [LegacyUnenumerableNamedProperties] " + "inherits from another interface with " + "[LegacyUnenumerableNamedProperties]", + [self.location, ancestor.location], + ) + ancestor = ancestor.parent + + if self._isOnGlobalProtoChain: + # Make sure we have no named setters or deleters + for memberType in ["setter", "deleter"]: + memberId = "named " + memberType + "s" + if memberId in specialMembersSeen: + raise WebIDLError( + "Interface with [Global] has a named %s" % memberType, + [self.location, specialMembersSeen[memberId].location], + ) + # Make sure we're not [LegacyOverrideBuiltIns] + if self.getExtendedAttribute("LegacyOverrideBuiltIns"): + raise WebIDLError( + "Interface with [Global] also has " "[LegacyOverrideBuiltIns]", + [self.location], + ) + # Mark all of our ancestors as being on the global's proto chain too + parent = self.parent + while parent: + # Must not inherit from an interface with [LegacyOverrideBuiltIns] + if parent.getExtendedAttribute("LegacyOverrideBuiltIns"): + raise WebIDLError( + "Interface with [Global] inherits from " + "interface with [LegacyOverrideBuiltIns]", + [self.location, parent.location], + ) + parent._isOnGlobalProtoChain = True + parent = parent.parent + + def validate(self): + def checkDuplicateNames(member, name, attributeName): + for m in self.members: + if m.identifier.name == name: + raise WebIDLError( + "[%s=%s] has same name as interface member" + % (attributeName, name), + [member.location, m.location], + ) + if m.isMethod() and m != member and name in m.aliases: + raise WebIDLError( + "conflicting [%s=%s] definitions" % (attributeName, name), + [member.location, m.location], + ) + if m.isAttr() and m != member and name in m.bindingAliases: + raise WebIDLError( + "conflicting [%s=%s] definitions" % (attributeName, name), + [member.location, m.location], + ) + + # We also don't support inheriting from unforgeable interfaces. + if self.getExtendedAttribute("LegacyUnforgeable") and self.hasChildInterfaces(): + locations = [self.location] + list( + i.location for i in self.interfacesBasedOnSelf if i.parent == self + ) + raise WebIDLError( + "%s is an unforgeable ancestor interface" % self.identifier.name, + locations, + ) + + ctor = self.ctor() + if ctor is not None: + ctor.validate() + for namedCtor in self.legacyFactoryFunctions: + namedCtor.validate() + + indexedGetter = None + hasLengthAttribute = False + for member in self.members: + member.validate() + + if self.isCallback() and member.getExtendedAttribute("Replaceable"): + raise WebIDLError( + "[Replaceable] used on an attribute on " + "interface %s which is a callback interface" % self.identifier.name, + [self.location, member.location], + ) + + # Check that PutForwards refers to another attribute and that no + # cycles exist in forwarded assignments. Also check for a + # integer-typed "length" attribute. + if member.isAttr(): + if member.identifier.name == "length" and member.type.isInteger(): + hasLengthAttribute = True + + iface = self + attr = member + putForwards = attr.getExtendedAttribute("PutForwards") + if putForwards and self.isCallback(): + raise WebIDLError( + "[PutForwards] used on an attribute " + "on interface %s which is a callback " + "interface" % self.identifier.name, + [self.location, member.location], + ) + + while putForwards is not None: + forwardIface = attr.type.unroll().inner + fowardAttr = None + + for forwardedMember in forwardIface.members: + if ( + not forwardedMember.isAttr() + or forwardedMember.identifier.name != putForwards[0] + ): + continue + if forwardedMember == member: + raise WebIDLError( + "Cycle detected in forwarded " + "assignments for attribute %s on " + "%s" % (member.identifier.name, self), + [member.location], + ) + fowardAttr = forwardedMember + break + + if fowardAttr is None: + raise WebIDLError( + "Attribute %s on %s forwards to " + "missing attribute %s" + % (attr.identifier.name, iface, putForwards), + [attr.location], + ) + + iface = forwardIface + attr = fowardAttr + putForwards = attr.getExtendedAttribute("PutForwards") + + # Check that the name of an [Alias] doesn't conflict with an + # interface member and whether we support indexed properties. + if member.isMethod(): + if member.isGetter() and member.isIndexed(): + indexedGetter = member + + for alias in member.aliases: + if self.isOnGlobalProtoChain(): + raise WebIDLError( + "[Alias] must not be used on a " + "[Global] interface operation", + [member.location], + ) + if ( + member.getExtendedAttribute("Exposed") + or member.getExtendedAttribute("ChromeOnly") + or member.getExtendedAttribute("Pref") + or member.getExtendedAttribute("Func") + or member.getExtendedAttribute("Trial") + or member.getExtendedAttribute("SecureContext") + ): + raise WebIDLError( + "[Alias] must not be used on a " + "conditionally exposed operation", + [member.location], + ) + if member.isStatic(): + raise WebIDLError( + "[Alias] must not be used on a " "static operation", + [member.location], + ) + if member.isIdentifierLess(): + raise WebIDLError( + "[Alias] must not be used on an " + "identifierless operation", + [member.location], + ) + if member.isLegacyUnforgeable(): + raise WebIDLError( + "[Alias] must not be used on an " + "[LegacyUnforgeable] operation", + [member.location], + ) + + checkDuplicateNames(member, alias, "Alias") + + # Check that the name of a [BindingAlias] doesn't conflict with an + # interface member. + if member.isAttr(): + for bindingAlias in member.bindingAliases: + checkDuplicateNames(member, bindingAlias, "BindingAlias") + + # Conditional exposure makes no sense for interfaces with no + # interface object. + # And SecureContext makes sense for interfaces with no interface object, + # since it is also propagated to interface members. + if ( + self.isExposedConditionally(exclusions=["SecureContext"]) + and not self.hasInterfaceObject() + ): + raise WebIDLError( + "Interface with no interface object is " "exposed conditionally", + [self.location], + ) + + # Value iterators are only allowed on interfaces with indexed getters, + # and pair iterators are only allowed on interfaces without indexed + # getters. + if self.isIterable(): + iterableDecl = self.maplikeOrSetlikeOrIterable + if iterableDecl.isValueIterator(): + if not indexedGetter: + raise WebIDLError( + "Interface with value iterator does not " + "support indexed properties", + [self.location, iterableDecl.location], + ) + + if iterableDecl.valueType != indexedGetter.signatures()[0][0]: + raise WebIDLError( + "Iterable type does not match indexed " "getter type", + [iterableDecl.location, indexedGetter.location], + ) + + if not hasLengthAttribute: + raise WebIDLError( + "Interface with value iterator does not " + 'have an integer-typed "length" attribute', + [self.location, iterableDecl.location], + ) + else: + assert iterableDecl.isPairIterator() + if indexedGetter: + raise WebIDLError( + "Interface with pair iterator supports " "indexed properties", + [self.location, iterableDecl.location, indexedGetter.location], + ) + + if indexedGetter and not hasLengthAttribute: + raise WebIDLError( + "Interface with an indexed getter does not have " + 'an integer-typed "length" attribute', + [self.location, indexedGetter.location], + ) + + def setCallback(self, value): + self._callback = value + + def isCallback(self): + return self._callback + + def isSingleOperationInterface(self): + assert self.isCallback() or self.isJSImplemented() + return ( + # JS-implemented things should never need the + # this-handling weirdness of single-operation interfaces. + not self.isJSImplemented() + and + # Not inheriting from another interface + not self.parent + and + # No attributes of any kinds + not any(m.isAttr() for m in self.members) + and + # There is at least one regular operation, and all regular + # operations have the same identifier + len( + set( + m.identifier.name + for m in self.members + if m.isMethod() and not m.isStatic() + ) + ) + == 1 + ) + + def inheritanceDepth(self): + depth = 0 + parent = self.parent + while parent: + depth = depth + 1 + parent = parent.parent + return depth + + def hasConstants(self): + return any(m.isConst() for m in self.members) + + def hasInterfaceObject(self): + if self.isCallback(): + return self.hasConstants() + return not hasattr(self, "_noInterfaceObject") and not self.isPseudoInterface() + + def hasInterfacePrototypeObject(self): + return ( + not self.isCallback() + and not self.isNamespace() + and self.getUserData("hasConcreteDescendant", False) + and not self.isPseudoInterface() + ) + + def addIncludedMixin(self, includedMixin): + assert isinstance(includedMixin, IDLInterfaceMixin) + self.includedMixins.add(includedMixin) + + def getInheritedInterfaces(self): + """ + Returns a list of the interfaces this interface inherits from + (not including this interface itself). The list is in order + from most derived to least derived. + """ + assert self._finished + if not self.parent: + return [] + parentInterfaces = self.parent.getInheritedInterfaces() + parentInterfaces.insert(0, self.parent) + return parentInterfaces + + def findInterfaceLoopPoint(self, otherInterface): + """ + Finds an interface amongst our ancestors that inherits from otherInterface. + If there is no such interface, returns None. + """ + if self.parent: + if self.parent == otherInterface: + return self + loopPoint = self.parent.findInterfaceLoopPoint(otherInterface) + if loopPoint: + return loopPoint + return None + + def setNonPartial(self, location, parent, members): + assert not parent or isinstance(parent, IDLIdentifierPlaceholder) + IDLInterfaceOrInterfaceMixinOrNamespace.setNonPartial(self, location, members) + assert not self.parent + self.parent = parent + + def getJSImplementation(self): + classId = self.getExtendedAttribute("JSImplementation") + if not classId: + return classId + assert isinstance(classId, list) + assert len(classId) == 1 + return classId[0] + + def isJSImplemented(self): + return bool(self.getJSImplementation()) + + def hasProbablyShortLivingWrapper(self): + current = self + while current: + if current.getExtendedAttribute("ProbablyShortLivingWrapper"): + return True + current = current.parent + return False + + def hasChildInterfaces(self): + return self._hasChildInterfaces + + def isOnGlobalProtoChain(self): + return self._isOnGlobalProtoChain + + def isPseudoInterface(self): + return self._isPseudo + + def _getDependentObjects(self): + deps = set(self.members) + deps.update(self.includedMixins) + if self.parent: + deps.add(self.parent) + return deps + + def hasMembersInSlots(self): + return self._ownMembersInSlots != 0 + + conditionExtendedAttributes = [ + "Pref", + "ChromeOnly", + "Func", + "Trial", + "SecureContext", + ] + + def isExposedConditionally(self, exclusions=[]): + return any( + ((a not in exclusions) and self.getExtendedAttribute(a)) + for a in self.conditionExtendedAttributes + ) + + +class IDLInterface(IDLInterfaceOrNamespace): + def __init__( + self, + location, + parentScope, + name, + parent, + members, + isKnownNonPartial, + classNameOverride=None, + ): + IDLInterfaceOrNamespace.__init__( + self, location, parentScope, name, parent, members, isKnownNonPartial + ) + self.classNameOverride = classNameOverride + + def __str__(self): + return "Interface '%s'" % self.identifier.name + + def isInterface(self): + return True + + def getClassName(self): + if self.classNameOverride: + return self.classNameOverride + return IDLInterfaceOrNamespace.getClassName(self) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + # Special cased attrs + if identifier == "TreatNonCallableAsNull": + raise WebIDLError( + "TreatNonCallableAsNull cannot be specified on interfaces", + [attr.location, self.location], + ) + if identifier == "LegacyTreatNonObjectAsNull": + raise WebIDLError( + "LegacyTreatNonObjectAsNull cannot be specified on interfaces", + [attr.location, self.location], + ) + elif identifier == "LegacyNoInterfaceObject": + if not attr.noArguments(): + raise WebIDLError( + "[LegacyNoInterfaceObject] must take no arguments", + [attr.location], + ) + + self._noInterfaceObject = True + elif identifier == "LegacyFactoryFunction": + if not attr.hasValue(): + raise WebIDLError( + ( + "LegacyFactoryFunction must either take an " + "identifier or take a named argument list" + ), + [attr.location], + ) + + args = attr.args() if attr.hasArgs() else [] + + method = IDLConstructor(attr.location, args, attr.value()) + method.reallyInit(self) + + # Named constructors are always assumed to be able to + # throw (since there's no way to indicate otherwise). + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("Throws",))] + ) + + # We need to detect conflicts for LegacyFactoryFunctions across + # interfaces. We first call resolve on the parentScope, + # which will merge all LegacyFactoryFunctions with the same + # identifier accross interfaces as overloads. + method.resolve(self.parentScope) + + # Then we look up the identifier on the parentScope. If the + # result is the same as the method we're adding then it + # hasn't been added as an overload and it's the first time + # we've encountered a LegacyFactoryFunction with that identifier. + # If the result is not the same as the method we're adding + # then it has been added as an overload and we need to check + # whether the result is actually one of our existing + # LegacyFactoryFunctions. + newMethod = self.parentScope.lookupIdentifier(method.identifier) + if newMethod == method: + self.legacyFactoryFunctions.append(method) + elif newMethod not in self.legacyFactoryFunctions: + raise WebIDLError( + "LegacyFactoryFunction conflicts with a " + "LegacyFactoryFunction of a different interface", + [method.location, newMethod.location], + ) + elif identifier == "ExceptionClass": + if not attr.noArguments(): + raise WebIDLError( + "[ExceptionClass] must take no arguments", [attr.location] + ) + if self.parent: + raise WebIDLError( + "[ExceptionClass] must not be specified on " + "an interface with inherited interfaces", + [attr.location, self.location], + ) + elif identifier == "Global": + if attr.hasValue(): + self.globalNames = [attr.value()] + elif attr.hasArgs(): + self.globalNames = attr.args() + else: + self.globalNames = [self.identifier.name] + self.parentScope.addIfaceGlobalNames( + self.identifier.name, self.globalNames + ) + self._isOnGlobalProtoChain = True + elif identifier == "LegacyWindowAlias": + if attr.hasValue(): + self.legacyWindowAliases = [attr.value()] + elif attr.hasArgs(): + self.legacyWindowAliases = attr.args() + else: + raise WebIDLError( + "[%s] must either take an identifier " + "or take an identifier list" % identifier, + [attr.location], + ) + for alias in self.legacyWindowAliases: + unresolved = IDLUnresolvedIdentifier(attr.location, alias) + IDLObjectWithIdentifier(attr.location, self.parentScope, unresolved) + elif identifier == "SecureContext": + if not attr.noArguments(): + raise WebIDLError( + "[%s] must take no arguments" % identifier, [attr.location] + ) + # This gets propagated to all our members. + for member in self.members: + if member.getExtendedAttribute("SecureContext"): + raise WebIDLError( + "[SecureContext] specified on both " + "an interface member and on the " + "interface itself", + [member.location, attr.location], + ) + member.addExtendedAttributes([attr]) + elif ( + identifier == "NeedResolve" + or identifier == "LegacyOverrideBuiltIns" + or identifier == "ChromeOnly" + or identifier == "LegacyUnforgeable" + or identifier == "LegacyEventInit" + or identifier == "ProbablyShortLivingWrapper" + or identifier == "LegacyUnenumerableNamedProperties" + or identifier == "RunConstructorInCallerCompartment" + or identifier == "WantsEventListenerHooks" + or identifier == "Serializable" + ): + # Known extended attributes that do not take values + if not attr.noArguments(): + raise WebIDLError( + "[%s] must take no arguments" % identifier, [attr.location] + ) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif ( + identifier == "Pref" + or identifier == "JSImplementation" + or identifier == "HeaderFile" + or identifier == "Func" + or identifier == "Trial" + or identifier == "Deprecated" + ): + # Known extended attributes that take a string value + if not attr.hasValue(): + raise WebIDLError( + "[%s] must have a value" % identifier, [attr.location] + ) + elif identifier == "InstrumentedProps": + # Known extended attributes that take a list + if not attr.hasArgs(): + raise WebIDLError( + "[%s] must have arguments" % identifier, [attr.location] + ) + else: + raise WebIDLError( + "Unknown extended attribute %s on interface" % identifier, + [attr.location], + ) + + attrlist = attr.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + def validate(self): + IDLInterfaceOrNamespace.validate(self) + if self.parent and self.isSerializable() and not self.parent.isSerializable(): + raise WebIDLError( + "Serializable interface inherits from non-serializable " + "interface. Per spec, that means the object should not be " + "serializable, so chances are someone made a mistake here " + "somewhere.", + [self.location, self.parent.location], + ) + + def isSerializable(self): + return self.getExtendedAttribute("Serializable") + + def setNonPartial(self, location, parent, members): + # Before we do anything else, finish initializing any constructors that + # might be in "members", so we don't have partially-initialized objects + # hanging around. We couldn't do it before now because we needed to have + # to have the IDLInterface on hand to properly set the return type. + for member in members: + if isinstance(member, IDLConstructor): + member.reallyInit(self) + + IDLInterfaceOrNamespace.setNonPartial(self, location, parent, members) + + +class IDLNamespace(IDLInterfaceOrNamespace): + def __init__(self, location, parentScope, name, members, isKnownNonPartial): + IDLInterfaceOrNamespace.__init__( + self, location, parentScope, name, None, members, isKnownNonPartial + ) + + def __str__(self): + return "Namespace '%s'" % self.identifier.name + + def isNamespace(self): + return True + + def addExtendedAttributes(self, attrs): + # The set of things namespaces support is small enough it's simpler + # to factor out into a separate method than it is to sprinkle + # isNamespace() checks all through + # IDLInterfaceOrNamespace.addExtendedAttributes. + for attr in attrs: + identifier = attr.identifier() + + if identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif identifier == "ClassString": + # Takes a string value to override the default "Object" if + # desired. + if not attr.hasValue(): + raise WebIDLError( + "[%s] must have a value" % identifier, [attr.location] + ) + elif identifier == "ProtoObjectHack" or identifier == "ChromeOnly": + if not attr.noArguments(): + raise WebIDLError( + "[%s] must not have arguments" % identifier, [attr.location] + ) + elif ( + identifier == "Pref" + or identifier == "HeaderFile" + or identifier == "Func" + or identifier == "Trial" + ): + # Known extended attributes that take a string value + if not attr.hasValue(): + raise WebIDLError( + "[%s] must have a value" % identifier, [attr.location] + ) + else: + raise WebIDLError( + "Unknown extended attribute %s on namespace" % identifier, + [attr.location], + ) + + attrlist = attr.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + def isSerializable(self): + return False + + +class IDLDictionary(IDLObjectWithScope): + def __init__(self, location, parentScope, name, parent, members): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + assert not parent or isinstance(parent, IDLIdentifierPlaceholder) + + self.parent = parent + self._finished = False + self.members = list(members) + self._partialDictionaries = [] + self._extendedAttrDict = {} + self.needsConversionToJS = False + self.needsConversionFromJS = False + + IDLObjectWithScope.__init__(self, location, parentScope, name) + + def __str__(self): + return "Dictionary '%s'" % self.identifier.name + + def isDictionary(self): + return True + + def canBeEmpty(self): + """ + Returns true if this dictionary can be empty (that is, it has no + required members and neither do any of its ancestors). + """ + return all(member.optional for member in self.members) and ( + not self.parent or self.parent.canBeEmpty() + ) + + def finish(self, scope): + if self._finished: + return + + self._finished = True + + if self.parent: + assert isinstance(self.parent, IDLIdentifierPlaceholder) + oldParent = self.parent + self.parent = self.parent.finish(scope) + if not isinstance(self.parent, IDLDictionary): + raise WebIDLError( + "Dictionary %s has parent that is not a dictionary" + % self.identifier.name, + [oldParent.location, self.parent.location], + ) + + # Make sure the parent resolves all its members before we start + # looking at them. + self.parent.finish(scope) + + # Now go ahead and merge in our partial dictionaries. + for partial in self._partialDictionaries: + partial.finish(scope) + self.members.extend(partial.members) + + for member in self.members: + member.resolve(self) + if not member.isComplete(): + member.complete(scope) + assert member.type.isComplete() + + # Members of a dictionary are sorted in lexicographic order, + # unless the dictionary opts out. + if not self.getExtendedAttribute("Unsorted"): + self.members.sort(key=lambda x: x.identifier.name) + + inheritedMembers = [] + ancestor = self.parent + while ancestor: + if ancestor == self: + raise WebIDLError( + "Dictionary %s has itself as an ancestor" % self.identifier.name, + [self.identifier.location], + ) + inheritedMembers.extend(ancestor.members) + ancestor = ancestor.parent + + # Catch name duplication + for inheritedMember in inheritedMembers: + for member in self.members: + if member.identifier.name == inheritedMember.identifier.name: + raise WebIDLError( + "Dictionary %s has two members with name %s" + % (self.identifier.name, member.identifier.name), + [member.location, inheritedMember.location], + ) + + def validate(self): + def typeContainsDictionary(memberType, dictionary): + """ + Returns a tuple whose: + + - First element is a Boolean value indicating whether + memberType contains dictionary. + + - Second element is: + A list of locations that leads from the type that was passed in + the memberType argument, to the dictionary being validated, + if the boolean value in the first element is True. + + None, if the boolean value in the first element is False. + """ + + if ( + memberType.nullable() + or memberType.isSequence() + or memberType.isRecord() + ): + return typeContainsDictionary(memberType.inner, dictionary) + + if memberType.isDictionary(): + if memberType.inner == dictionary: + return (True, [memberType.location]) + + (contains, locations) = dictionaryContainsDictionary( + memberType.inner, dictionary + ) + if contains: + return (True, [memberType.location] + locations) + + if memberType.isUnion(): + for member in memberType.flatMemberTypes: + (contains, locations) = typeContainsDictionary(member, dictionary) + if contains: + return (True, locations) + + return (False, None) + + def dictionaryContainsDictionary(dictMember, dictionary): + for member in dictMember.members: + (contains, locations) = typeContainsDictionary(member.type, dictionary) + if contains: + return (True, [member.location] + locations) + + if dictMember.parent: + if dictMember.parent == dictionary: + return (True, [dictMember.location]) + else: + (contains, locations) = dictionaryContainsDictionary( + dictMember.parent, dictionary + ) + if contains: + return (True, [dictMember.location] + locations) + + return (False, None) + + for member in self.members: + if member.type.isDictionary() and member.type.nullable(): + raise WebIDLError( + "Dictionary %s has member with nullable " + "dictionary type" % self.identifier.name, + [member.location], + ) + (contains, locations) = typeContainsDictionary(member.type, self) + if contains: + raise WebIDLError( + "Dictionary %s has member with itself as type." + % self.identifier.name, + [member.location] + locations, + ) + + if member.type.isUndefined(): + raise WebIDLError( + "Dictionary %s has member with undefined as its type." + % self.identifier.name, + [member.location], + ) + elif member.type.isUnion(): + for unionMember in member.type.unroll().flatMemberTypes: + if unionMember.isUndefined(): + raise WebIDLError( + "Dictionary %s has member with a union containing " + "undefined as a type." % self.identifier.name, + [unionMember.location], + ) + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier == "GenerateInitFromJSON" or identifier == "GenerateInit": + if not attr.noArguments(): + raise WebIDLError( + "[%s] must not have arguments" % identifier, [attr.location] + ) + self.needsConversionFromJS = True + elif ( + identifier == "GenerateConversionToJS" or identifier == "GenerateToJSON" + ): + if not attr.noArguments(): + raise WebIDLError( + "[%s] must not have arguments" % identifier, [attr.location] + ) + # ToJSON methods require to-JS conversion, because we + # implement ToJSON by converting to a JS object and + # then using JSON.stringify. + self.needsConversionToJS = True + elif identifier == "Unsorted": + if not attr.noArguments(): + raise WebIDLError( + "[Unsorted] must take no arguments", [attr.location] + ) + else: + raise WebIDLError( + "[%s] extended attribute not allowed on " + "dictionaries" % identifier, + [attr.location], + ) + + self._extendedAttrDict[identifier] = True + + def _getDependentObjects(self): + deps = set(self.members) + if self.parent: + deps.add(self.parent) + return deps + + def addPartialDictionary(self, partial): + assert self.identifier.name == partial.identifier.name + self._partialDictionaries.append(partial) + + +class IDLEnum(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, name, values): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + + if len(values) != len(set(values)): + raise WebIDLError( + "Enum %s has multiple identical strings" % name.name, [location] + ) + + IDLObjectWithIdentifier.__init__(self, location, parentScope, name) + self._values = values + + def values(self): + return self._values + + def finish(self, scope): + pass + + def validate(self): + pass + + def isEnum(self): + return True + + def addExtendedAttributes(self, attrs): + if len(attrs) != 0: + raise WebIDLError( + "There are no extended attributes that are " "allowed on enums", + [attrs[0].location, self.location], + ) + + def _getDependentObjects(self): + return set() + + +class IDLType(IDLObject): + Tags = enum( + # The integer types + "int8", + "uint8", + "int16", + "uint16", + "int32", + "uint32", + "int64", + "uint64", + # Additional primitive types + "bool", + "unrestricted_float", + "float", + "unrestricted_double", + # "double" last primitive type to match IDLBuiltinType + "double", + # Other types + "any", + "undefined", + "domstring", + "bytestring", + "usvstring", + "utf8string", + "jsstring", + "object", + # Funny stuff + "interface", + "dictionary", + "enum", + "callback", + "union", + "sequence", + "record", + "promise", + "observablearray", + ) + + def __init__(self, location, name): + IDLObject.__init__(self, location) + self.name = name + self.builtin = False + self.legacyNullToEmptyString = False + self._clamp = False + self._enforceRange = False + self._allowShared = False + self._extendedAttrDict = {} + + def __hash__(self): + return ( + hash(self.builtin) + + hash(self.name) + + hash(self._clamp) + + hash(self._enforceRange) + + hash(self.legacyNullToEmptyString) + + hash(self._allowShared) + ) + + def __eq__(self, other): + return ( + other + and self.builtin == other.builtin + and self.name == other.name + and self._clamp == other.hasClamp() + and self._enforceRange == other.hasEnforceRange() + and self.legacyNullToEmptyString == other.legacyNullToEmptyString + and self._allowShared == other.hasAllowShared() + ) + + def __ne__(self, other): + return not self == other + + def __str__(self): + return str(self.name) + + def prettyName(self): + """ + A name that looks like what this type is named in the IDL spec. By default + this is just our .name, but types that have more interesting spec + representations should override this. + """ + return str(self.name) + + def isType(self): + return True + + def nullable(self): + return False + + def isPrimitive(self): + return False + + def isBoolean(self): + return False + + def isNumeric(self): + return False + + def isString(self): + return False + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isUSVString(self): + return False + + def isUTF8String(self): + return False + + def isJSString(self): + return False + + def isUndefined(self): + return False + + def isSequence(self): + return False + + def isRecord(self): + return False + + def isArrayBuffer(self): + return False + + def isArrayBufferView(self): + return False + + def isTypedArray(self): + return False + + def isBufferSource(self): + return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray() + + def isCallbackInterface(self): + return False + + def isNonCallbackInterface(self): + return False + + def isGeckoInterface(self): + """Returns a boolean indicating whether this type is an 'interface' + type that is implemented in Gecko. At the moment, this returns + true for all interface types that are not types from the TypedArray + spec.""" + return self.isInterface() and not self.isSpiderMonkeyInterface() + + def isSpiderMonkeyInterface(self): + """Returns a boolean indicating whether this type is an 'interface' + type that is implemented in SpiderMonkey.""" + return self.isInterface() and self.isBufferSource() + + def isAny(self): + return self.tag() == IDLType.Tags.any + + def isObject(self): + return self.tag() == IDLType.Tags.object + + def isPromise(self): + return False + + def isComplete(self): + return True + + def includesRestrictedFloat(self): + return False + + def isFloat(self): + return False + + def isUnrestricted(self): + # Should only call this on float types + assert self.isFloat() + + def isJSONType(self): + return False + + def isObservableArray(self): + return False + + def isDictionaryLike(self): + return self.isDictionary() or self.isRecord() or self.isCallbackInterface() + + def hasClamp(self): + return self._clamp + + def hasEnforceRange(self): + return self._enforceRange + + def hasAllowShared(self): + return self._allowShared + + def tag(self): + assert False # Override me! + + def treatNonCallableAsNull(self): + assert self.tag() == IDLType.Tags.callback + return self.nullable() and self.inner.callback._treatNonCallableAsNull + + def treatNonObjectAsNull(self): + assert self.tag() == IDLType.Tags.callback + return self.nullable() and self.inner.callback._treatNonObjectAsNull + + def withExtendedAttributes(self, attrs): + if len(attrs) > 0: + raise WebIDLError( + "Extended attributes on types only supported for builtins", + [attrs[0].location, self.location], + ) + return self + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def resolveType(self, parentScope): + pass + + def unroll(self): + return self + + def isDistinguishableFrom(self, other): + raise TypeError( + "Can't tell whether a generic type is or is not " + "distinguishable from other things" + ) + + def isExposedInAllOf(self, exposureSet): + return True + + +class IDLUnresolvedType(IDLType): + """ + Unresolved types are interface types + """ + + def __init__(self, location, name, attrs=[]): + IDLType.__init__(self, location, name) + self.extraTypeAttributes = attrs + + def isComplete(self): + return False + + def complete(self, scope): + obj = None + try: + obj = scope._lookupIdentifier(self.name) + except Exception: + raise WebIDLError("Unresolved type '%s'." % self.name, [self.location]) + + assert obj + assert not obj.isType() + if obj.isTypedef(): + assert self.name.name == obj.identifier.name + typedefType = IDLTypedefType(self.location, obj.innerType, obj.identifier) + assert not typedefType.isComplete() + return typedefType.complete(scope).withExtendedAttributes( + self.extraTypeAttributes + ) + elif obj.isCallback() and not obj.isInterface(): + assert self.name.name == obj.identifier.name + return IDLCallbackType(self.location, obj) + + return IDLWrapperType(self.location, obj) + + def withExtendedAttributes(self, attrs): + return IDLUnresolvedType(self.location, self.name, attrs) + + def isDistinguishableFrom(self, other): + raise TypeError( + "Can't tell whether an unresolved type is or is not " + "distinguishable from other things" + ) + + +class IDLParametrizedType(IDLType): + def __init__(self, location, name, innerType): + IDLType.__init__(self, location, name) + self.builtin = False + self.inner = innerType + + def includesRestrictedFloat(self): + return self.inner.includesRestrictedFloat() + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolveType(parentScope) + + def isComplete(self): + return self.inner.isComplete() + + def unroll(self): + return self.inner.unroll() + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + + +class IDLNullableType(IDLParametrizedType): + def __init__(self, location, innerType): + assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any] + + IDLParametrizedType.__init__(self, location, None, innerType) + + def __hash__(self): + return hash(self.inner) + + def __eq__(self, other): + return isinstance(other, IDLNullableType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "OrNull" + + def prettyName(self): + return self.inner.prettyName() + "?" + + def nullable(self): + return True + + def isCallback(self): + return self.inner.isCallback() + + def isPrimitive(self): + return self.inner.isPrimitive() + + def isBoolean(self): + return self.inner.isBoolean() + + def isNumeric(self): + return self.inner.isNumeric() + + def isString(self): + return self.inner.isString() + + def isByteString(self): + return self.inner.isByteString() + + def isDOMString(self): + return self.inner.isDOMString() + + def isUSVString(self): + return self.inner.isUSVString() + + def isUTF8String(self): + return self.inner.isUTF8String() + + def isJSString(self): + return self.inner.isJSString() + + def isFloat(self): + return self.inner.isFloat() + + def isUnrestricted(self): + return self.inner.isUnrestricted() + + def isInteger(self): + return self.inner.isInteger() + + def isUndefined(self): + return self.inner.isUndefined() + + def isSequence(self): + return self.inner.isSequence() + + def isRecord(self): + return self.inner.isRecord() + + def isArrayBuffer(self): + return self.inner.isArrayBuffer() + + def isArrayBufferView(self): + return self.inner.isArrayBufferView() + + def isTypedArray(self): + return self.inner.isTypedArray() + + def isDictionary(self): + return self.inner.isDictionary() + + def isInterface(self): + return self.inner.isInterface() + + def isPromise(self): + # There is no such thing as a nullable Promise. + assert not self.inner.isPromise() + return False + + def isCallbackInterface(self): + return self.inner.isCallbackInterface() + + def isNonCallbackInterface(self): + return self.inner.isNonCallbackInterface() + + def isEnum(self): + return self.inner.isEnum() + + def isUnion(self): + return self.inner.isUnion() + + def isJSONType(self): + return self.inner.isJSONType() + + def isObservableArray(self): + return self.inner.isObservableArray() + + def hasClamp(self): + return self.inner.hasClamp() + + def hasEnforceRange(self): + return self.inner.hasEnforceRange() + + def hasAllowShared(self): + return self.inner.hasAllowShared() + + def isComplete(self): + return self.name is not None + + def tag(self): + return self.inner.tag() + + def complete(self, scope): + if not self.inner.isComplete(): + self.inner = self.inner.complete(scope) + assert self.inner.isComplete() + + if self.inner.nullable(): + raise WebIDLError( + "The inner type of a nullable type must not be a nullable type", + [self.location, self.inner.location], + ) + if self.inner.isUnion(): + if self.inner.hasNullableType: + raise WebIDLError( + "The inner type of a nullable type must not " + "be a union type that itself has a nullable " + "type as a member type", + [self.location], + ) + if self.inner.isDOMString(): + if self.inner.legacyNullToEmptyString: + raise WebIDLError( + "[LegacyNullToEmptyString] not allowed on a nullable DOMString", + [self.location, self.inner.location], + ) + if self.inner.isObservableArray(): + raise WebIDLError( + "The inner type of a nullable type must not be an ObservableArray type", + [self.location, self.inner.location], + ) + + self.name = self.inner.name + "OrNull" + return self + + def isDistinguishableFrom(self, other): + if ( + other.nullable() + or other.isDictionary() + or ( + other.isUnion() and (other.hasNullableType or other.hasDictionaryType()) + ) + ): + # Can't tell which type null should become + return False + return self.inner.isDistinguishableFrom(other) + + def withExtendedAttributes(self, attrs): + # See https://github.com/heycam/webidl/issues/827#issuecomment-565131350 + # Allowing extended attributes to apply to a nullable type is an intermediate solution. + # A potential longer term solution is to introduce a null type and get rid of nullables. + # For example, we could do `([Clamp] long or null) foo` in the future. + return IDLNullableType(self.location, self.inner.withExtendedAttributes(attrs)) + + +class IDLSequenceType(IDLParametrizedType): + def __init__(self, location, parameterType): + assert not parameterType.isUndefined() + + IDLParametrizedType.__init__(self, location, parameterType.name, parameterType) + # Need to set self.name up front if our inner type is already complete, + # since in that case our .complete() won't be called. + if self.inner.isComplete(): + self.name = self.inner.name + "Sequence" + + def __hash__(self): + return hash(self.inner) + + def __eq__(self, other): + return isinstance(other, IDLSequenceType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "Sequence" + + def prettyName(self): + return "sequence<%s>" % self.inner.prettyName() + + def isSequence(self): + return True + + def isJSONType(self): + return self.inner.isJSONType() + + def tag(self): + return IDLType.Tags.sequence + + def complete(self, scope): + if self.inner.isObservableArray(): + raise WebIDLError( + "The inner type of a sequence type must not be an ObservableArray type", + [self.location, self.inner.location], + ) + + self.inner = self.inner.complete(scope) + self.name = self.inner.name + "Sequence" + return self + + def isDistinguishableFrom(self, other): + if other.isPromise(): + return False + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return ( + other.isUndefined() + or other.isPrimitive() + or other.isString() + or other.isEnum() + or other.isInterface() + or other.isDictionary() + or other.isCallback() + or other.isRecord() + ) + + +class IDLRecordType(IDLParametrizedType): + def __init__(self, location, keyType, valueType): + assert keyType.isString() + assert keyType.isComplete() + assert not valueType.isUndefined() + + IDLParametrizedType.__init__(self, location, valueType.name, valueType) + self.keyType = keyType + + # Need to set self.name up front if our inner type is already complete, + # since in that case our .complete() won't be called. + if self.inner.isComplete(): + self.name = self.keyType.name + self.inner.name + "Record" + + def __hash__(self): + return hash(self.inner) + + def __eq__(self, other): + return isinstance(other, IDLRecordType) and self.inner == other.inner + + def __str__(self): + return self.keyType.__str__() + self.inner.__str__() + "Record" + + def prettyName(self): + return "record<%s, %s>" % (self.keyType.prettyName(), self.inner.prettyName()) + + def isRecord(self): + return True + + def isJSONType(self): + return self.inner.isJSONType() + + def tag(self): + return IDLType.Tags.record + + def complete(self, scope): + if self.inner.isObservableArray(): + raise WebIDLError( + "The value type of a record type must not be an ObservableArray type", + [self.location, self.inner.location], + ) + + self.inner = self.inner.complete(scope) + self.name = self.keyType.name + self.inner.name + "Record" + return self + + def unroll(self): + # We do not unroll our inner. Just stop at ourselves. That + # lets us add headers for both ourselves and our inner as + # needed. + return self + + def isDistinguishableFrom(self, other): + if other.isPromise(): + return False + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return ( + other.isPrimitive() + or other.isString() + or other.isEnum() + or other.isNonCallbackInterface() + or other.isSequence() + ) + + def isExposedInAllOf(self, exposureSet): + return self.inner.unroll().isExposedInAllOf(exposureSet) + + +class IDLObservableArrayType(IDLParametrizedType): + def __init__(self, location, innerType): + assert not innerType.isUndefined() + IDLParametrizedType.__init__(self, location, None, innerType) + + def __hash__(self): + return hash(self.inner) + + def __eq__(self, other): + return isinstance(other, IDLObservableArrayType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "ObservableArray" + + def prettyName(self): + return "ObservableArray<%s>" % self.inner.prettyName() + + def isJSONType(self): + return self.inner.isJSONType() + + def isObservableArray(self): + return True + + def isComplete(self): + return self.name is not None + + def tag(self): + return IDLType.Tags.observablearray + + def complete(self, scope): + if not self.inner.isComplete(): + self.inner = self.inner.complete(scope) + assert self.inner.isComplete() + + if self.inner.isDictionary(): + raise WebIDLError( + "The inner type of an ObservableArray type must not " + "be a dictionary type", + [self.location, self.inner.location], + ) + if self.inner.isSequence(): + raise WebIDLError( + "The inner type of an ObservableArray type must not " + "be a sequence type", + [self.location, self.inner.location], + ) + if self.inner.isRecord(): + raise WebIDLError( + "The inner type of an ObservableArray type must not be a record type", + [self.location, self.inner.location], + ) + if self.inner.isObservableArray(): + raise WebIDLError( + "The inner type of an ObservableArray type must not " + "be an ObservableArray type", + [self.location, self.inner.location], + ) + + self.name = self.inner.name + "ObservableArray" + return self + + def isDistinguishableFrom(self, other): + # ObservableArrays are not distinguishable from anything. + return False + + +class IDLUnionType(IDLType): + def __init__(self, location, memberTypes): + IDLType.__init__(self, location, "") + self.memberTypes = memberTypes + self.hasNullableType = False + self._dictionaryType = None + self.flatMemberTypes = None + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLUnionType) and self.memberTypes == other.memberTypes + + def __hash__(self): + assert self.isComplete() + return self.name.__hash__() + + def prettyName(self): + return "(" + " or ".join(m.prettyName() for m in self.memberTypes) + ")" + + def isUnion(self): + return True + + def isJSONType(self): + return all(m.isJSONType() for m in self.memberTypes) + + def includesRestrictedFloat(self): + return any(t.includesRestrictedFloat() for t in self.memberTypes) + + def tag(self): + return IDLType.Tags.union + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + for t in self.memberTypes: + t.resolveType(parentScope) + + def isComplete(self): + return self.flatMemberTypes is not None + + def complete(self, scope): + def typeName(type): + if isinstance(type, IDLNullableType): + return typeName(type.inner) + "OrNull" + if isinstance(type, IDLWrapperType): + return typeName(type._identifier.object()) + if isinstance(type, IDLObjectWithIdentifier): + return typeName(type.identifier) + if isinstance(type, IDLBuiltinType) and type.hasAllowShared(): + assert type.isBufferSource() + return "MaybeShared" + type.name + return type.name + + for (i, type) in enumerate(self.memberTypes): + if not type.isComplete(): + self.memberTypes[i] = type.complete(scope) + + self.name = "Or".join(typeName(type) for type in self.memberTypes) + self.flatMemberTypes = list(self.memberTypes) + i = 0 + while i < len(self.flatMemberTypes): + if self.flatMemberTypes[i].nullable(): + if self.hasNullableType: + raise WebIDLError( + "Can't have more than one nullable types in a union", + [nullableType.location, self.flatMemberTypes[i].location], + ) + if self.hasDictionaryType(): + raise WebIDLError( + "Can't have a nullable type and a " + "dictionary type in a union", + [ + self._dictionaryType.location, + self.flatMemberTypes[i].location, + ], + ) + self.hasNullableType = True + nullableType = self.flatMemberTypes[i] + self.flatMemberTypes[i] = self.flatMemberTypes[i].inner + continue + if self.flatMemberTypes[i].isDictionary(): + if self.hasNullableType: + raise WebIDLError( + "Can't have a nullable type and a " + "dictionary type in a union", + [nullableType.location, self.flatMemberTypes[i].location], + ) + self._dictionaryType = self.flatMemberTypes[i] + self.flatMemberTypes[i].inner.needsConversionFromJS = True + elif self.flatMemberTypes[i].isUnion(): + self.flatMemberTypes[i : i + 1] = self.flatMemberTypes[i].memberTypes + continue + i += 1 + + for (i, t) in enumerate(self.flatMemberTypes[:-1]): + for u in self.flatMemberTypes[i + 1 :]: + if not t.isDistinguishableFrom(u): + raise WebIDLError( + "Flat member types of a union should be " + "distinguishable, " + str(t) + " is not " + "distinguishable from " + str(u), + [self.location, t.location, u.location], + ) + + return self + + def isDistinguishableFrom(self, other): + if self.hasNullableType and other.nullable(): + # Can't tell which type null should become + return False + if other.isUnion(): + otherTypes = other.unroll().memberTypes + else: + otherTypes = [other] + # For every type in otherTypes, check that it's distinguishable from + # every type in our types + for u in otherTypes: + if any(not t.isDistinguishableFrom(u) for t in self.memberTypes): + return False + return True + + def isExposedInAllOf(self, exposureSet): + # We could have different member types in different globals. + # Just make sure that each thing in exposureSet has one of our member types exposed in it. + for globalName in exposureSet: + if not any( + t.unroll().isExposedInAllOf(set([globalName])) + for t in self.flatMemberTypes + ): + return False + return True + + def hasDictionaryType(self): + return self._dictionaryType is not None + + def hasPossiblyEmptyDictionaryType(self): + return ( + self._dictionaryType is not None and self._dictionaryType.inner.canBeEmpty() + ) + + def _getDependentObjects(self): + return set(self.memberTypes) + + +class IDLTypedefType(IDLType): + def __init__(self, location, innerType, name): + IDLType.__init__(self, location, name) + self.inner = innerType + self.builtin = False + + def __hash__(self): + return hash(self.inner) + + def __eq__(self, other): + return isinstance(other, IDLTypedefType) and self.inner == other.inner + + def __str__(self): + return self.name + + def nullable(self): + return self.inner.nullable() + + def isPrimitive(self): + return self.inner.isPrimitive() + + def isBoolean(self): + return self.inner.isBoolean() + + def isNumeric(self): + return self.inner.isNumeric() + + def isString(self): + return self.inner.isString() + + def isByteString(self): + return self.inner.isByteString() + + def isDOMString(self): + return self.inner.isDOMString() + + def isUSVString(self): + return self.inner.isUSVString() + + def isUTF8String(self): + return self.inner.isUTF8String() + + def isJSString(self): + return self.inner.isJSString() + + def isUndefined(self): + return self.inner.isUndefined() + + def isJSONType(self): + return self.inner.isJSONType() + + def isSequence(self): + return self.inner.isSequence() + + def isRecord(self): + return self.inner.isRecord() + + def isDictionary(self): + return self.inner.isDictionary() + + def isArrayBuffer(self): + return self.inner.isArrayBuffer() + + def isArrayBufferView(self): + return self.inner.isArrayBufferView() + + def isTypedArray(self): + return self.inner.isTypedArray() + + def isInterface(self): + return self.inner.isInterface() + + def isCallbackInterface(self): + return self.inner.isCallbackInterface() + + def isNonCallbackInterface(self): + return self.inner.isNonCallbackInterface() + + def isComplete(self): + return False + + def complete(self, parentScope): + if not self.inner.isComplete(): + self.inner = self.inner.complete(parentScope) + assert self.inner.isComplete() + return self.inner + + # Do we need a resolveType impl? I don't think it's particularly useful.... + + def tag(self): + return self.inner.tag() + + def unroll(self): + return self.inner.unroll() + + def isDistinguishableFrom(self, other): + return self.inner.isDistinguishableFrom(other) + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + + def withExtendedAttributes(self, attrs): + return IDLTypedefType( + self.location, self.inner.withExtendedAttributes(attrs), self.name + ) + + +class IDLTypedef(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, innerType, name): + # Set self.innerType first, because IDLObjectWithIdentifier.__init__ + # will call our __str__, which wants to use it. + self.innerType = innerType + identifier = IDLUnresolvedIdentifier(location, name) + IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) + + def __str__(self): + return "Typedef %s %s" % (self.identifier.name, self.innerType) + + def finish(self, parentScope): + if not self.innerType.isComplete(): + self.innerType = self.innerType.complete(parentScope) + + def validate(self): + pass + + def isTypedef(self): + return True + + def addExtendedAttributes(self, attrs): + if len(attrs) != 0: + raise WebIDLError( + "There are no extended attributes that are " "allowed on typedefs", + [attrs[0].location, self.location], + ) + + def _getDependentObjects(self): + return self.innerType._getDependentObjects() + + +class IDLWrapperType(IDLType): + def __init__(self, location, inner): + IDLType.__init__(self, location, inner.identifier.name) + self.inner = inner + self._identifier = inner.identifier + self.builtin = False + + def __hash__(self): + return hash(self._identifier) + hash(self.builtin) + + def __eq__(self, other): + return ( + isinstance(other, IDLWrapperType) + and self._identifier == other._identifier + and self.builtin == other.builtin + ) + + def __str__(self): + return str(self.name) + " (Wrapper)" + + def isDictionary(self): + return isinstance(self.inner, IDLDictionary) + + def isInterface(self): + return isinstance(self.inner, IDLInterface) or isinstance( + self.inner, IDLExternalInterface + ) + + def isCallbackInterface(self): + return self.isInterface() and self.inner.isCallback() + + def isNonCallbackInterface(self): + return self.isInterface() and not self.inner.isCallback() + + def isEnum(self): + return isinstance(self.inner, IDLEnum) + + def isJSONType(self): + if self.isInterface(): + if self.inner.isExternal(): + return False + iface = self.inner + while iface: + if any(m.isMethod() and m.isToJSON() for m in iface.members): + return True + iface = iface.parent + return False + elif self.isEnum(): + return True + elif self.isDictionary(): + dictionary = self.inner + while dictionary: + if not all(m.type.isJSONType() for m in dictionary.members): + return False + dictionary = dictionary.parent + return True + else: + raise WebIDLError( + "IDLWrapperType wraps type %s that we don't know if " + "is serializable" % type(self.inner), + [self.location], + ) + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolve(parentScope) + + def isComplete(self): + return True + + def tag(self): + if self.isInterface(): + return IDLType.Tags.interface + elif self.isEnum(): + return IDLType.Tags.enum + elif self.isDictionary(): + return IDLType.Tags.dictionary + else: + assert False + + def isDistinguishableFrom(self, other): + if other.isPromise(): + return False + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + assert self.isInterface() or self.isEnum() or self.isDictionary() + if self.isEnum(): + return ( + other.isUndefined() + or other.isPrimitive() + or other.isInterface() + or other.isObject() + or other.isCallback() + or other.isDictionary() + or other.isSequence() + or other.isRecord() + ) + if self.isDictionary() and (other.nullable() or other.isUndefined()): + return False + if ( + other.isPrimitive() + or other.isString() + or other.isEnum() + or other.isSequence() + ): + return True + if self.isDictionary(): + return other.isNonCallbackInterface() + + assert self.isInterface() + if other.isInterface(): + if other.isSpiderMonkeyInterface(): + # Just let |other| handle things + return other.isDistinguishableFrom(self) + assert self.isGeckoInterface() and other.isGeckoInterface() + if self.inner.isExternal() or other.unroll().inner.isExternal(): + return self != other + return len( + self.inner.interfacesBasedOnSelf + & other.unroll().inner.interfacesBasedOnSelf + ) == 0 and (self.isNonCallbackInterface() or other.isNonCallbackInterface()) + if ( + other.isUndefined() + or other.isDictionary() + or other.isCallback() + or other.isRecord() + ): + return self.isNonCallbackInterface() + + # Not much else |other| can be + assert other.isObject() + return False + + def isExposedInAllOf(self, exposureSet): + if not self.isInterface(): + return True + iface = self.inner + if iface.isExternal(): + # Let's say true, so we don't have to implement exposure mixins on + # external interfaces and sprinkle [Exposed=Window] on every single + # external interface declaration. + return True + return iface.exposureSet.issuperset(exposureSet) + + def _getDependentObjects(self): + # NB: The codegen for an interface type depends on + # a) That the identifier is in fact an interface (as opposed to + # a dictionary or something else). + # b) The native type of the interface. + # If we depend on the interface object we will also depend on + # anything the interface depends on which is undesirable. We + # considered implementing a dependency just on the interface type + # file, but then every modification to an interface would cause this + # to be regenerated which is still undesirable. We decided not to + # depend on anything, reasoning that: + # 1) Changing the concrete type of the interface requires modifying + # Bindings.conf, which is still a global dependency. + # 2) Changing an interface to a dictionary (or vice versa) with the + # same identifier should be incredibly rare. + # + # On the other hand, if our type is a dictionary, we should + # depend on it, because the member types of a dictionary + # affect whether a method taking the dictionary as an argument + # takes a JSContext* argument or not. + if self.isDictionary(): + return set([self.inner]) + return set() + + +class IDLPromiseType(IDLParametrizedType): + def __init__(self, location, innerType): + IDLParametrizedType.__init__(self, location, "Promise", innerType) + + def __hash__(self): + return hash(self.promiseInnerType()) + + def __eq__(self, other): + return ( + isinstance(other, IDLPromiseType) + and self.promiseInnerType() == other.promiseInnerType() + ) + + def __str__(self): + return self.inner.__str__() + "Promise" + + def prettyName(self): + return "Promise<%s>" % self.inner.prettyName() + + def isPromise(self): + return True + + def promiseInnerType(self): + return self.inner + + def tag(self): + return IDLType.Tags.promise + + def complete(self, scope): + if self.inner.isObservableArray(): + raise WebIDLError( + "The inner type of a promise type must not be an ObservableArray type", + [self.location, self.inner.location], + ) + + self.inner = self.promiseInnerType().complete(scope) + return self + + def unroll(self): + # We do not unroll our inner. Just stop at ourselves. That + # lets us add headers for both ourselves and our inner as + # needed. + return self + + def isDistinguishableFrom(self, other): + # Promises are not distinguishable from anything. + return False + + def isExposedInAllOf(self, exposureSet): + # Check the internal type + return self.promiseInnerType().unroll().isExposedInAllOf(exposureSet) + + +class IDLBuiltinType(IDLType): + + Types = enum( + # The integer types + "byte", + "octet", + "short", + "unsigned_short", + "long", + "unsigned_long", + "long_long", + "unsigned_long_long", + # Additional primitive types + "boolean", + "unrestricted_float", + "float", + "unrestricted_double", + # IMPORTANT: "double" must be the last primitive type listed + "double", + # Other types + "any", + "undefined", + "domstring", + "bytestring", + "usvstring", + "utf8string", + "jsstring", + "object", + # Funny stuff + "ArrayBuffer", + "ArrayBufferView", + "Int8Array", + "Uint8Array", + "Uint8ClampedArray", + "Int16Array", + "Uint16Array", + "Int32Array", + "Uint32Array", + "Float32Array", + "Float64Array", + ) + + TagLookup = { + Types.byte: IDLType.Tags.int8, + Types.octet: IDLType.Tags.uint8, + Types.short: IDLType.Tags.int16, + Types.unsigned_short: IDLType.Tags.uint16, + Types.long: IDLType.Tags.int32, + Types.unsigned_long: IDLType.Tags.uint32, + Types.long_long: IDLType.Tags.int64, + Types.unsigned_long_long: IDLType.Tags.uint64, + Types.boolean: IDLType.Tags.bool, + Types.unrestricted_float: IDLType.Tags.unrestricted_float, + Types.float: IDLType.Tags.float, + Types.unrestricted_double: IDLType.Tags.unrestricted_double, + Types.double: IDLType.Tags.double, + Types.any: IDLType.Tags.any, + Types.undefined: IDLType.Tags.undefined, + Types.domstring: IDLType.Tags.domstring, + Types.bytestring: IDLType.Tags.bytestring, + Types.usvstring: IDLType.Tags.usvstring, + Types.utf8string: IDLType.Tags.utf8string, + Types.jsstring: IDLType.Tags.jsstring, + Types.object: IDLType.Tags.object, + Types.ArrayBuffer: IDLType.Tags.interface, + Types.ArrayBufferView: IDLType.Tags.interface, + Types.Int8Array: IDLType.Tags.interface, + Types.Uint8Array: IDLType.Tags.interface, + Types.Uint8ClampedArray: IDLType.Tags.interface, + Types.Int16Array: IDLType.Tags.interface, + Types.Uint16Array: IDLType.Tags.interface, + Types.Int32Array: IDLType.Tags.interface, + Types.Uint32Array: IDLType.Tags.interface, + Types.Float32Array: IDLType.Tags.interface, + Types.Float64Array: IDLType.Tags.interface, + } + + PrettyNames = { + Types.byte: "byte", + Types.octet: "octet", + Types.short: "short", + Types.unsigned_short: "unsigned short", + Types.long: "long", + Types.unsigned_long: "unsigned long", + Types.long_long: "long long", + Types.unsigned_long_long: "unsigned long long", + Types.boolean: "boolean", + Types.unrestricted_float: "unrestricted float", + Types.float: "float", + Types.unrestricted_double: "unrestricted double", + Types.double: "double", + Types.any: "any", + Types.undefined: "undefined", + Types.domstring: "DOMString", + Types.bytestring: "ByteString", + Types.usvstring: "USVString", + Types.utf8string: "USVString", # That's what it is in spec terms + Types.jsstring: "USVString", # Again, that's what it is in spec terms + Types.object: "object", + Types.ArrayBuffer: "ArrayBuffer", + Types.ArrayBufferView: "ArrayBufferView", + Types.Int8Array: "Int8Array", + Types.Uint8Array: "Uint8Array", + Types.Uint8ClampedArray: "Uint8ClampedArray", + Types.Int16Array: "Int16Array", + Types.Uint16Array: "Uint16Array", + Types.Int32Array: "Int32Array", + Types.Uint32Array: "Uint32Array", + Types.Float32Array: "Float32Array", + Types.Float64Array: "Float64Array", + } + + def __init__( + self, + location, + name, + type, + clamp=False, + enforceRange=False, + legacyNullToEmptyString=False, + allowShared=False, + attrLocation=[], + ): + """ + The mutually exclusive clamp/enforceRange/legacyNullToEmptyString/allowShared arguments + are used to create instances of this type with the appropriate attributes attached. Use + .clamped(), .rangeEnforced(), .withLegacyNullToEmptyString() and .withAllowShared(). + + attrLocation is an array of source locations of these attributes for error reporting. + """ + IDLType.__init__(self, location, name) + self.builtin = True + self._typeTag = type + self._clamped = None + self._rangeEnforced = None + self._withLegacyNullToEmptyString = None + self._withAllowShared = None + if self.isInteger(): + if clamp: + self._clamp = True + self.name = "Clamped" + self.name + self._extendedAttrDict["Clamp"] = True + elif enforceRange: + self._enforceRange = True + self.name = "RangeEnforced" + self.name + self._extendedAttrDict["EnforceRange"] = True + elif clamp or enforceRange: + raise WebIDLError( + "Non-integer types cannot be [Clamp] or [EnforceRange]", attrLocation + ) + if self.isDOMString() or self.isUTF8String(): + if legacyNullToEmptyString: + self.legacyNullToEmptyString = True + self.name = "NullIsEmpty" + self.name + self._extendedAttrDict["LegacyNullToEmptyString"] = True + elif legacyNullToEmptyString: + raise WebIDLError( + "Non-string types cannot be [LegacyNullToEmptyString]", attrLocation + ) + if self.isBufferSource(): + if allowShared: + self._allowShared = True + self._extendedAttrDict["AllowShared"] = True + elif allowShared: + raise WebIDLError( + "Types that are not buffer source types cannot be [AllowShared]", + attrLocation, + ) + + def __str__(self): + if self._allowShared: + assert self.isBufferSource() + return "MaybeShared" + str(self.name) + return str(self.name) + + def prettyName(self): + return IDLBuiltinType.PrettyNames[self._typeTag] + + def clamped(self, attrLocation): + if not self._clamped: + self._clamped = IDLBuiltinType( + self.location, + self.name, + self._typeTag, + clamp=True, + attrLocation=attrLocation, + ) + return self._clamped + + def rangeEnforced(self, attrLocation): + if not self._rangeEnforced: + self._rangeEnforced = IDLBuiltinType( + self.location, + self.name, + self._typeTag, + enforceRange=True, + attrLocation=attrLocation, + ) + return self._rangeEnforced + + def withLegacyNullToEmptyString(self, attrLocation): + if not self._withLegacyNullToEmptyString: + self._withLegacyNullToEmptyString = IDLBuiltinType( + self.location, + self.name, + self._typeTag, + legacyNullToEmptyString=True, + attrLocation=attrLocation, + ) + return self._withLegacyNullToEmptyString + + def withAllowShared(self, attrLocation): + if not self._withAllowShared: + self._withAllowShared = IDLBuiltinType( + self.location, + self.name, + self._typeTag, + allowShared=True, + attrLocation=attrLocation, + ) + return self._withAllowShared + + def isPrimitive(self): + return self._typeTag <= IDLBuiltinType.Types.double + + def isBoolean(self): + return self._typeTag == IDLBuiltinType.Types.boolean + + def isUndefined(self): + return self._typeTag == IDLBuiltinType.Types.undefined + + def isNumeric(self): + return self.isPrimitive() and not self.isBoolean() + + def isString(self): + return ( + self._typeTag == IDLBuiltinType.Types.domstring + or self._typeTag == IDLBuiltinType.Types.bytestring + or self._typeTag == IDLBuiltinType.Types.usvstring + or self._typeTag == IDLBuiltinType.Types.utf8string + or self._typeTag == IDLBuiltinType.Types.jsstring + ) + + def isByteString(self): + return self._typeTag == IDLBuiltinType.Types.bytestring + + def isDOMString(self): + return self._typeTag == IDLBuiltinType.Types.domstring + + def isUSVString(self): + return self._typeTag == IDLBuiltinType.Types.usvstring + + def isUTF8String(self): + return self._typeTag == IDLBuiltinType.Types.utf8string + + def isJSString(self): + return self._typeTag == IDLBuiltinType.Types.jsstring + + def isInteger(self): + return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long + + def isArrayBuffer(self): + return self._typeTag == IDLBuiltinType.Types.ArrayBuffer + + def isArrayBufferView(self): + return self._typeTag == IDLBuiltinType.Types.ArrayBufferView + + def isTypedArray(self): + return ( + self._typeTag >= IDLBuiltinType.Types.Int8Array + and self._typeTag <= IDLBuiltinType.Types.Float64Array + ) + + def isInterface(self): + # TypedArray things are interface types per the TypedArray spec, + # but we handle them as builtins because SpiderMonkey implements + # all of it internally. + return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray() + + def isNonCallbackInterface(self): + # All the interfaces we can be are non-callback + return self.isInterface() + + def isFloat(self): + return ( + self._typeTag == IDLBuiltinType.Types.float + or self._typeTag == IDLBuiltinType.Types.double + or self._typeTag == IDLBuiltinType.Types.unrestricted_float + or self._typeTag == IDLBuiltinType.Types.unrestricted_double + ) + + def isUnrestricted(self): + assert self.isFloat() + return ( + self._typeTag == IDLBuiltinType.Types.unrestricted_float + or self._typeTag == IDLBuiltinType.Types.unrestricted_double + ) + + def isJSONType(self): + return self.isPrimitive() or self.isString() or self.isObject() + + def includesRestrictedFloat(self): + return self.isFloat() and not self.isUnrestricted() + + def tag(self): + return IDLBuiltinType.TagLookup[self._typeTag] + + def isDistinguishableFrom(self, other): + if other.isPromise(): + return False + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + if self.isUndefined(): + return not (other.isUndefined() or other.isDictionaryLike()) + if self.isPrimitive(): + if ( + other.isUndefined() + or other.isString() + or other.isEnum() + or other.isInterface() + or other.isObject() + or other.isCallback() + or other.isDictionary() + or other.isSequence() + or other.isRecord() + ): + return True + if self.isBoolean(): + return other.isNumeric() + assert self.isNumeric() + return other.isBoolean() + if self.isString(): + return ( + other.isUndefined() + or other.isPrimitive() + or other.isInterface() + or other.isObject() + or other.isCallback() + or other.isDictionary() + or other.isSequence() + or other.isRecord() + ) + if self.isAny(): + # Can't tell "any" apart from anything + return False + if self.isObject(): + return ( + other.isUndefined() + or other.isPrimitive() + or other.isString() + or other.isEnum() + ) + # Not much else we could be! + assert self.isSpiderMonkeyInterface() + # Like interfaces, but we know we're not a callback + return ( + other.isUndefined() + or other.isPrimitive() + or other.isString() + or other.isEnum() + or other.isCallback() + or other.isDictionary() + or other.isSequence() + or other.isRecord() + or ( + other.isInterface() + and ( + # ArrayBuffer is distinguishable from everything + # that's not an ArrayBuffer or a callback interface + (self.isArrayBuffer() and not other.isArrayBuffer()) + or + # ArrayBufferView is distinguishable from everything + # that's not an ArrayBufferView or typed array. + ( + self.isArrayBufferView() + and not other.isArrayBufferView() + and not other.isTypedArray() + ) + or + # Typed arrays are distinguishable from everything + # except ArrayBufferView and the same type of typed + # array + ( + self.isTypedArray() + and not other.isArrayBufferView() + and not (other.isTypedArray() and other.name == self.name) + ) + ) + ) + ) + + def _getDependentObjects(self): + return set() + + def withExtendedAttributes(self, attrs): + ret = self + for attribute in attrs: + identifier = attribute.identifier() + if identifier == "Clamp": + if not attribute.noArguments(): + raise WebIDLError( + "[Clamp] must take no arguments", [attribute.location] + ) + if ret.hasEnforceRange() or self._enforceRange: + raise WebIDLError( + "[EnforceRange] and [Clamp] are mutually exclusive", + [self.location, attribute.location], + ) + ret = self.clamped([self.location, attribute.location]) + elif identifier == "EnforceRange": + if not attribute.noArguments(): + raise WebIDLError( + "[EnforceRange] must take no arguments", [attribute.location] + ) + if ret.hasClamp() or self._clamp: + raise WebIDLError( + "[EnforceRange] and [Clamp] are mutually exclusive", + [self.location, attribute.location], + ) + ret = self.rangeEnforced([self.location, attribute.location]) + elif identifier == "LegacyNullToEmptyString": + if not (self.isDOMString() or self.isUTF8String()): + raise WebIDLError( + "[LegacyNullToEmptyString] only allowed on DOMStrings and UTF8Strings", + [self.location, attribute.location], + ) + assert not self.nullable() + if attribute.hasValue(): + raise WebIDLError( + "[LegacyNullToEmptyString] must take no identifier argument", + [attribute.location], + ) + ret = self.withLegacyNullToEmptyString( + [self.location, attribute.location] + ) + elif identifier == "AllowShared": + if not attribute.noArguments(): + raise WebIDLError( + "[AllowShared] must take no arguments", [attribute.location] + ) + if not self.isBufferSource(): + raise WebIDLError( + "[AllowShared] only allowed on buffer source types", + [self.location, attribute.location], + ) + ret = self.withAllowShared([self.location, attribute.location]) + + else: + raise WebIDLError( + "Unhandled extended attribute on type", + [self.location, attribute.location], + ) + return ret + + +BuiltinTypes = { + IDLBuiltinType.Types.byte: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Byte", IDLBuiltinType.Types.byte + ), + IDLBuiltinType.Types.octet: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Octet", IDLBuiltinType.Types.octet + ), + IDLBuiltinType.Types.short: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Short", IDLBuiltinType.Types.short + ), + IDLBuiltinType.Types.unsigned_short: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "UnsignedShort", + IDLBuiltinType.Types.unsigned_short, + ), + IDLBuiltinType.Types.long: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Long", IDLBuiltinType.Types.long + ), + IDLBuiltinType.Types.unsigned_long: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "UnsignedLong", + IDLBuiltinType.Types.unsigned_long, + ), + IDLBuiltinType.Types.long_long: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "LongLong", IDLBuiltinType.Types.long_long + ), + IDLBuiltinType.Types.unsigned_long_long: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "UnsignedLongLong", + IDLBuiltinType.Types.unsigned_long_long, + ), + IDLBuiltinType.Types.undefined: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Undefined", IDLBuiltinType.Types.undefined + ), + IDLBuiltinType.Types.boolean: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Boolean", IDLBuiltinType.Types.boolean + ), + IDLBuiltinType.Types.float: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Float", IDLBuiltinType.Types.float + ), + IDLBuiltinType.Types.unrestricted_float: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "UnrestrictedFloat", + IDLBuiltinType.Types.unrestricted_float, + ), + IDLBuiltinType.Types.double: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Double", IDLBuiltinType.Types.double + ), + IDLBuiltinType.Types.unrestricted_double: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "UnrestrictedDouble", + IDLBuiltinType.Types.unrestricted_double, + ), + IDLBuiltinType.Types.any: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Any", IDLBuiltinType.Types.any + ), + IDLBuiltinType.Types.domstring: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "String", IDLBuiltinType.Types.domstring + ), + IDLBuiltinType.Types.bytestring: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "ByteString", IDLBuiltinType.Types.bytestring + ), + IDLBuiltinType.Types.usvstring: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "USVString", IDLBuiltinType.Types.usvstring + ), + IDLBuiltinType.Types.utf8string: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "UTF8String", IDLBuiltinType.Types.utf8string + ), + IDLBuiltinType.Types.jsstring: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "JSString", IDLBuiltinType.Types.jsstring + ), + IDLBuiltinType.Types.object: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Object", IDLBuiltinType.Types.object + ), + IDLBuiltinType.Types.ArrayBuffer: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "ArrayBuffer", + IDLBuiltinType.Types.ArrayBuffer, + ), + IDLBuiltinType.Types.ArrayBufferView: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "ArrayBufferView", + IDLBuiltinType.Types.ArrayBufferView, + ), + IDLBuiltinType.Types.Int8Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Int8Array", IDLBuiltinType.Types.Int8Array + ), + IDLBuiltinType.Types.Uint8Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Uint8Array", IDLBuiltinType.Types.Uint8Array + ), + IDLBuiltinType.Types.Uint8ClampedArray: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "Uint8ClampedArray", + IDLBuiltinType.Types.Uint8ClampedArray, + ), + IDLBuiltinType.Types.Int16Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Int16Array", IDLBuiltinType.Types.Int16Array + ), + IDLBuiltinType.Types.Uint16Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "Uint16Array", + IDLBuiltinType.Types.Uint16Array, + ), + IDLBuiltinType.Types.Int32Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), "Int32Array", IDLBuiltinType.Types.Int32Array + ), + IDLBuiltinType.Types.Uint32Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "Uint32Array", + IDLBuiltinType.Types.Uint32Array, + ), + IDLBuiltinType.Types.Float32Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "Float32Array", + IDLBuiltinType.Types.Float32Array, + ), + IDLBuiltinType.Types.Float64Array: IDLBuiltinType( + BuiltinLocation("<builtin type>"), + "Float64Array", + IDLBuiltinType.Types.Float64Array, + ), +} + + +integerTypeSizes = { + IDLBuiltinType.Types.byte: (-128, 127), + IDLBuiltinType.Types.octet: (0, 255), + IDLBuiltinType.Types.short: (-32768, 32767), + IDLBuiltinType.Types.unsigned_short: (0, 65535), + IDLBuiltinType.Types.long: (-2147483648, 2147483647), + IDLBuiltinType.Types.unsigned_long: (0, 4294967295), + IDLBuiltinType.Types.long_long: (-9223372036854775808, 9223372036854775807), + IDLBuiltinType.Types.unsigned_long_long: (0, 18446744073709551615), +} + + +def matchIntegerValueToType(value): + for type, extremes in integerTypeSizes.items(): + (min, max) = extremes + if value <= max and value >= min: + return BuiltinTypes[type] + + return None + + +class NoCoercionFoundError(WebIDLError): + """ + A class we use to indicate generic coercion failures because none of the + types worked out in IDLValue.coerceToType. + """ + + +class IDLValue(IDLObject): + def __init__(self, location, type, value): + IDLObject.__init__(self, location) + self.type = type + assert isinstance(type, IDLType) + + self.value = value + + def coerceToType(self, type, location): + if type == self.type: + return self # Nothing to do + + # We first check for unions to ensure that even if the union is nullable + # we end up with the right flat member type, not the union's type. + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + coercedValue = self.coerceToType(subtype, location) + # Create a new IDLValue to make sure that we have the + # correct float/double type. This is necessary because we + # use the value's type when it is a default value of a + # union, and the union cares about the exact float type. + return IDLValue(self.location, subtype, coercedValue.value) + except Exception as e: + # Make sure to propagate out WebIDLErrors that are not the + # generic "hey, we could not coerce to this type at all" + # exception, because those are specific "coercion failed for + # reason X" exceptions. Note that we want to swallow + # non-WebIDLErrors here, because those can just happen if + # "type" is not something that can have a default value at + # all. + if isinstance(e, WebIDLError) and not isinstance( + e, NoCoercionFoundError + ): + raise e + + # If the type allows null, rerun this matching on the inner type, except + # nullable enums. We handle those specially, because we want our + # default string values to stay strings even when assigned to a nullable + # enum. + elif type.nullable() and not type.isEnum(): + innerValue = self.coerceToType(type.inner, location) + return IDLValue(self.location, type, innerValue.value) + + elif self.type.isInteger() and type.isInteger(): + # We're both integer types. See if we fit. + + (min, max) = integerTypeSizes[type._typeTag] + if self.value <= max and self.value >= min: + # Promote + return IDLValue(self.location, type, self.value) + else: + raise WebIDLError( + "Value %s is out of range for type %s." % (self.value, type), + [location], + ) + elif self.type.isInteger() and type.isFloat(): + # Convert an integer literal into float + if -(2 ** 24) <= self.value <= 2 ** 24: + return IDLValue(self.location, type, float(self.value)) + else: + raise WebIDLError( + "Converting value %s to %s will lose precision." + % (self.value, type), + [location], + ) + elif self.type.isString() and type.isEnum(): + # Just keep our string, but make sure it's a valid value for this enum + enum = type.unroll().inner + if self.value not in enum.values(): + raise WebIDLError( + "'%s' is not a valid default value for enum %s" + % (self.value, enum.identifier.name), + [location, enum.location], + ) + return self + elif self.type.isFloat() and type.isFloat(): + if not type.isUnrestricted() and ( + self.value == float("inf") + or self.value == float("-inf") + or math.isnan(self.value) + ): + raise WebIDLError( + "Trying to convert unrestricted value %s to non-unrestricted" + % self.value, + [location], + ) + return IDLValue(self.location, type, self.value) + elif self.type.isString() and type.isUSVString(): + # Allow USVStrings to use default value just like + # DOMString. No coercion is required in this case as Codegen.py + # treats USVString just like DOMString, but with an + # extra normalization step. + assert self.type.isDOMString() + return self + elif self.type.isString() and ( + type.isByteString() or type.isJSString() or type.isUTF8String() + ): + # Allow ByteStrings, UTF8String, and JSStrings to use a default + # value like DOMString. + # No coercion is required as Codegen.py will handle the + # extra steps. We want to make sure that our string contains + # only valid characters, so we check that here. + valid_ascii_lit = ( + " " + string.ascii_letters + string.digits + string.punctuation + ) + for idx, c in enumerate(self.value): + if c not in valid_ascii_lit: + raise WebIDLError( + "Coercing this string literal %s to a ByteString is not supported yet. " + "Coercion failed due to an unsupported byte %d at index %d." + % (self.value.__repr__(), ord(c), idx), + [location], + ) + + return IDLValue(self.location, type, self.value) + elif self.type.isDOMString() and type.legacyNullToEmptyString: + # LegacyNullToEmptyString is a different type for resolution reasons, + # however once you have a value it doesn't matter + return self + + raise NoCoercionFoundError( + "Cannot coerce type %s to type %s." % (self.type, type), [location] + ) + + def _getDependentObjects(self): + return set() + + +class IDLNullValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if ( + not isinstance(type, IDLNullableType) + and not (type.isUnion() and type.hasNullableType) + and not type.isAny() + ): + raise WebIDLError("Cannot coerce null value to type %s." % type, [location]) + + nullValue = IDLNullValue(self.location) + if type.isUnion() and not type.nullable() and type.hasDictionaryType(): + # We're actually a default value for the union's dictionary member. + # Use its type. + for t in type.flatMemberTypes: + if t.isDictionary(): + nullValue.type = t + return nullValue + nullValue.type = type + return nullValue + + def _getDependentObjects(self): + return set() + + +class IDLEmptySequenceValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + return self.coerceToType(subtype, location) + except Exception: + pass + + if not type.isSequence(): + raise WebIDLError( + "Cannot coerce empty sequence value to type %s." % type, [location] + ) + + emptySequenceValue = IDLEmptySequenceValue(self.location) + emptySequenceValue.type = type + return emptySequenceValue + + def _getDependentObjects(self): + return set() + + +class IDLDefaultDictionaryValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + return self.coerceToType(subtype, location) + except Exception: + pass + + if not type.isDictionary(): + raise WebIDLError( + "Cannot coerce default dictionary value to type %s." % type, [location] + ) + + defaultDictionaryValue = IDLDefaultDictionaryValue(self.location) + defaultDictionaryValue.type = type + return defaultDictionaryValue + + def _getDependentObjects(self): + return set() + + +class IDLUndefinedValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if not type.isAny(): + raise WebIDLError( + "Cannot coerce undefined value to type %s." % type, [location] + ) + + undefinedValue = IDLUndefinedValue(self.location) + undefinedValue.type = type + return undefinedValue + + def _getDependentObjects(self): + return set() + + +class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): + + Tags = enum( + "Const", "Attr", "Method", "MaplikeOrSetlike", "AsyncIterable", "Iterable" + ) + + Special = enum("Static", "Stringifier") + + AffectsValues = ("Nothing", "Everything") + DependsOnValues = ("Nothing", "DOMState", "DeviceState", "Everything") + + def __init__(self, location, identifier, tag, extendedAttrDict=None): + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + IDLExposureMixins.__init__(self, location) + self.tag = tag + if extendedAttrDict is None: + self._extendedAttrDict = {} + else: + self._extendedAttrDict = extendedAttrDict + + def isMethod(self): + return self.tag == IDLInterfaceMember.Tags.Method + + def isAttr(self): + return self.tag == IDLInterfaceMember.Tags.Attr + + def isConst(self): + return self.tag == IDLInterfaceMember.Tags.Const + + def isMaplikeOrSetlikeOrIterable(self): + return ( + self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike + or self.tag == IDLInterfaceMember.Tags.AsyncIterable + or self.tag == IDLInterfaceMember.Tags.Iterable + ) + + def isMaplikeOrSetlike(self): + return self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike + + def addExtendedAttributes(self, attrs): + for attr in attrs: + self.handleExtendedAttribute(attr) + attrlist = attr.listValue() + self._extendedAttrDict[attr.identifier()] = ( + attrlist if len(attrlist) else True + ) + + def handleExtendedAttribute(self, attr): + pass + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def finish(self, scope): + IDLExposureMixins.finish(self, scope) + + def validate(self): + if self.isAttr() or self.isMethod(): + if self.affects == "Everything" and self.dependsOn != "Everything": + raise WebIDLError( + "Interface member is flagged as affecting " + "everything but not depending on everything. " + "That seems rather unlikely.", + [self.location], + ) + + if self.getExtendedAttribute("NewObject"): + if self.dependsOn == "Nothing" or self.dependsOn == "DOMState": + raise WebIDLError( + "A [NewObject] method is not idempotent, " + "so it has to depend on something other than DOM state.", + [self.location], + ) + if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( + "StoreInSlot" + ): + raise WebIDLError( + "A [NewObject] attribute shouldnt be " + "[Cached] or [StoreInSlot], since the point " + "of those is to keep returning the same " + "thing across multiple calls, which is not " + "what [NewObject] does.", + [self.location], + ) + + def _setDependsOn(self, dependsOn): + if self.dependsOn != "Everything": + raise WebIDLError( + "Trying to specify multiple different DependsOn, " + "Pure, or Constant extended attributes for " + "attribute", + [self.location], + ) + if dependsOn not in IDLInterfaceMember.DependsOnValues: + raise WebIDLError( + "Invalid [DependsOn=%s] on attribute" % dependsOn, [self.location] + ) + self.dependsOn = dependsOn + + def _setAffects(self, affects): + if self.affects != "Everything": + raise WebIDLError( + "Trying to specify multiple different Affects, " + "Pure, or Constant extended attributes for " + "attribute", + [self.location], + ) + if affects not in IDLInterfaceMember.AffectsValues: + raise WebIDLError( + "Invalid [Affects=%s] on attribute" % affects, [self.location] + ) + self.affects = affects + + def _addAlias(self, alias): + if alias in self.aliases: + raise WebIDLError( + "Duplicate [Alias=%s] on attribute" % alias, [self.location] + ) + self.aliases.append(alias) + + def _addBindingAlias(self, bindingAlias): + if bindingAlias in self.bindingAliases: + raise WebIDLError( + "Duplicate [BindingAlias=%s] on attribute" % bindingAlias, + [self.location], + ) + self.bindingAliases.append(bindingAlias) + + +class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): + def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind): + IDLInterfaceMember.__init__(self, location, identifier, ifaceKind) + if keyType is not None: + assert isinstance(keyType, IDLType) + else: + assert valueType is not None + assert ifaceType in ["maplike", "setlike", "iterable", "asynciterable"] + if valueType is not None: + assert isinstance(valueType, IDLType) + self.keyType = keyType + self.valueType = valueType + self.maplikeOrSetlikeOrIterableType = ifaceType + self.disallowedMemberNames = [] + self.disallowedNonMethodNames = [] + + def isMaplike(self): + return self.maplikeOrSetlikeOrIterableType == "maplike" + + def isSetlike(self): + return self.maplikeOrSetlikeOrIterableType == "setlike" + + def isIterable(self): + return self.maplikeOrSetlikeOrIterableType == "iterable" + + def isAsyncIterable(self): + return self.maplikeOrSetlikeOrIterableType == "asynciterable" + + def hasKeyType(self): + return self.keyType is not None + + def hasValueType(self): + return self.valueType is not None + + def checkCollisions(self, members, isAncestor): + for member in members: + # Check that there are no disallowed members + if member.identifier.name in self.disallowedMemberNames and not ( + ( + member.isMethod() + and ( + member.isStatic() or member.isMaplikeOrSetlikeOrIterableMethod() + ) + ) + or (member.isAttr() and member.isMaplikeOrSetlikeAttr()) + ): + raise WebIDLError( + "Member '%s' conflicts " + "with reserved %s name." + % (member.identifier.name, self.maplikeOrSetlikeOrIterableType), + [self.location, member.location], + ) + # Check that there are no disallowed non-method members. + # Ancestor members are always disallowed here; own members + # are disallowed only if they're non-methods. + if ( + isAncestor or member.isAttr() or member.isConst() + ) and member.identifier.name in self.disallowedNonMethodNames: + raise WebIDLError( + "Member '%s' conflicts " + "with reserved %s method." + % (member.identifier.name, self.maplikeOrSetlikeOrIterableType), + [self.location, member.location], + ) + + def addMethod( + self, + name, + members, + allowExistingOperations, + returnType, + args=[], + chromeOnly=False, + isPure=False, + affectsNothing=False, + newObject=False, + isIteratorAlias=False, + ): + """ + Create an IDLMethod based on the parameters passed in. + + - members is the member list to add this function to, since this is + called during the member expansion portion of interface object + building. + + - chromeOnly is only True for read-only js implemented classes, to + implement underscore prefixed convenience functions which would + otherwise not be available, unlike the case of C++ bindings. + + - isPure is only True for idempotent functions, so it is not valid for + things like keys, values, etc. that return a new object every time. + + - affectsNothing means that nothing changes due to this method, which + affects JIT optimization behavior + + - newObject means the method creates and returns a new object. + + """ + # Only add name to lists for collision checks if it's not chrome + # only. + if chromeOnly: + name = "__" + name + else: + if not allowExistingOperations: + self.disallowedMemberNames.append(name) + else: + self.disallowedNonMethodNames.append(name) + # If allowExistingOperations is True, and another operation exists + # with the same name as the one we're trying to add, don't add the + # maplike/setlike operation. + if allowExistingOperations: + for m in members: + if m.identifier.name == name and m.isMethod() and not m.isStatic(): + return + method = IDLMethod( + self.location, + IDLUnresolvedIdentifier( + self.location, name, allowDoubleUnderscore=chromeOnly + ), + returnType, + args, + maplikeOrSetlikeOrIterable=self, + ) + # We need to be able to throw from declaration methods + method.addExtendedAttributes([IDLExtendedAttribute(self.location, ("Throws",))]) + if chromeOnly: + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("ChromeOnly",))] + ) + if isPure: + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("Pure",))] + ) + # Following attributes are used for keys/values/entries. Can't mark + # them pure, since they return a new object each time they are run. + if affectsNothing: + method.addExtendedAttributes( + [ + IDLExtendedAttribute(self.location, ("DependsOn", "Everything")), + IDLExtendedAttribute(self.location, ("Affects", "Nothing")), + ] + ) + if newObject: + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NewObject",))] + ) + if isIteratorAlias: + if not self.isAsyncIterable(): + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))] + ) + else: + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("Alias", "@@asyncIterator"))] + ) + members.append(method) + + def resolve(self, parentScope): + if self.keyType: + self.keyType.resolveType(parentScope) + if self.valueType: + self.valueType.resolveType(parentScope) + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + if self.keyType and not self.keyType.isComplete(): + t = self.keyType.complete(scope) + + assert not isinstance(t, IDLUnresolvedType) + assert not isinstance(t, IDLTypedefType) + assert not isinstance(t.name, IDLUnresolvedIdentifier) + self.keyType = t + if self.valueType and not self.valueType.isComplete(): + t = self.valueType.complete(scope) + + assert not isinstance(t, IDLUnresolvedType) + assert not isinstance(t, IDLTypedefType) + assert not isinstance(t.name, IDLUnresolvedIdentifier) + self.valueType = t + + def validate(self): + IDLInterfaceMember.validate(self) + + def handleExtendedAttribute(self, attr): + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def _getDependentObjects(self): + deps = set() + if self.keyType: + deps.add(self.keyType) + if self.valueType: + deps.add(self.valueType) + return deps + + def getForEachArguments(self): + return [ + IDLArgument( + self.location, + IDLUnresolvedIdentifier( + BuiltinLocation("<auto-generated-identifier>"), "callback" + ), + BuiltinTypes[IDLBuiltinType.Types.object], + ), + IDLArgument( + self.location, + IDLUnresolvedIdentifier( + BuiltinLocation("<auto-generated-identifier>"), "thisArg" + ), + BuiltinTypes[IDLBuiltinType.Types.any], + optional=True, + ), + ] + + +# Iterable adds ES6 iterator style functions and traits +# (keys/values/entries/@@iterator) to an interface. +class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): + def __init__(self, location, identifier, keyType, valueType, scope): + IDLMaplikeOrSetlikeOrIterableBase.__init__( + self, + location, + identifier, + "iterable", + keyType, + valueType, + IDLInterfaceMember.Tags.Iterable, + ) + self.iteratorType = None + + def __str__(self): + return "declared iterable with key '%s' and value '%s'" % ( + self.keyType, + self.valueType, + ) + + def expand(self, members): + """ + In order to take advantage of all of the method machinery in Codegen, + we generate our functions as if they were part of the interface + specification during parsing. + """ + # We only need to add entries/keys/values here if we're a pair iterator. + # Value iterators just copy these from %ArrayPrototype% instead. + if not self.isPairIterator(): + return + + # object entries() + self.addMethod( + "entries", + members, + False, + self.iteratorType, + affectsNothing=True, + newObject=True, + isIteratorAlias=True, + ) + # object keys() + self.addMethod( + "keys", + members, + False, + self.iteratorType, + affectsNothing=True, + newObject=True, + ) + # object values() + self.addMethod( + "values", + members, + False, + self.iteratorType, + affectsNothing=True, + newObject=True, + ) + + # undefined forEach(callback(valueType, keyType), optional any thisArg) + self.addMethod( + "forEach", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.undefined], + self.getForEachArguments(), + ) + + def isValueIterator(self): + return not self.isPairIterator() + + def isPairIterator(self): + return self.hasKeyType() + + +class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase): + def __init__(self, location, identifier, keyType, valueType, argList, scope): + for arg in argList: + if not arg.optional: + raise WebIDLError( + "The arguments of the asynchronously iterable declaration on " + "%s must all be optional arguments." % identifier, + [arg.location], + ) + + IDLMaplikeOrSetlikeOrIterableBase.__init__( + self, + location, + identifier, + "asynciterable", + keyType, + valueType, + IDLInterfaceMember.Tags.AsyncIterable, + ) + self.iteratorType = None + self.argList = argList + + def __str__(self): + return "declared async iterable with key '%s' and value '%s'" % ( + self.keyType, + self.valueType, + ) + + def expand(self, members): + """ + In order to take advantage of all of the method machinery in Codegen, + we generate our functions as if they were part of the interface + specification during parsing. + """ + # object values() + self.addMethod( + "values", + members, + False, + self.iteratorType, + self.argList, + affectsNothing=True, + newObject=True, + isIteratorAlias=(not self.isPairIterator()), + ) + + # We only need to add entries/keys here if we're a pair iterator. + if not self.isPairIterator(): + return + + # Methods can't share their IDLArguments, so we need to make copies here. + def copyArgList(argList): + return map(copy.copy, argList) + + # object entries() + self.addMethod( + "entries", + members, + False, + self.iteratorType, + copyArgList(self.argList), + affectsNothing=True, + newObject=True, + isIteratorAlias=True, + ) + # object keys() + self.addMethod( + "keys", + members, + False, + self.iteratorType, + copyArgList(self.argList), + affectsNothing=True, + newObject=True, + ) + + def isValueIterator(self): + return not self.isPairIterator() + + def isPairIterator(self): + return self.hasKeyType() + + +# MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface. +class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): + def __init__( + self, location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType + ): + IDLMaplikeOrSetlikeOrIterableBase.__init__( + self, + location, + identifier, + maplikeOrSetlikeType, + keyType, + valueType, + IDLInterfaceMember.Tags.MaplikeOrSetlike, + ) + self.readonly = readonly + self.slotIndices = None + + # When generating JSAPI access code, we need to know the backing object + # type prefix to create the correct function. Generate here for reuse. + if self.isMaplike(): + self.prefix = "Map" + elif self.isSetlike(): + self.prefix = "Set" + + def __str__(self): + return "declared '%s' with key '%s'" % ( + self.maplikeOrSetlikeOrIterableType, + self.keyType, + ) + + def expand(self, members): + """ + In order to take advantage of all of the method machinery in Codegen, + we generate our functions as if they were part of the interface + specification during parsing. + """ + # Both maplike and setlike have a size attribute + members.append( + IDLAttribute( + self.location, + IDLUnresolvedIdentifier( + BuiltinLocation("<auto-generated-identifier>"), "size" + ), + BuiltinTypes[IDLBuiltinType.Types.unsigned_long], + True, + maplikeOrSetlike=self, + ) + ) + self.reserved_ro_names = ["size"] + self.disallowedMemberNames.append("size") + + # object entries() + self.addMethod( + "entries", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.object], + affectsNothing=True, + isIteratorAlias=self.isMaplike(), + ) + # object keys() + self.addMethod( + "keys", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.object], + affectsNothing=True, + ) + # object values() + self.addMethod( + "values", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.object], + affectsNothing=True, + isIteratorAlias=self.isSetlike(), + ) + + # undefined forEach(callback(valueType, keyType), thisVal) + self.addMethod( + "forEach", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.undefined], + self.getForEachArguments(), + ) + + def getKeyArg(): + return IDLArgument( + self.location, + IDLUnresolvedIdentifier(self.location, "key"), + self.keyType, + ) + + # boolean has(keyType key) + self.addMethod( + "has", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.boolean], + [getKeyArg()], + isPure=True, + ) + + if not self.readonly: + # undefined clear() + self.addMethod( + "clear", members, True, BuiltinTypes[IDLBuiltinType.Types.undefined], [] + ) + # boolean delete(keyType key) + self.addMethod( + "delete", + members, + True, + BuiltinTypes[IDLBuiltinType.Types.boolean], + [getKeyArg()], + ) + + if self.isSetlike(): + if not self.readonly: + # Add returns the set object it just added to. + # object add(keyType key) + + self.addMethod( + "add", + members, + True, + BuiltinTypes[IDLBuiltinType.Types.object], + [getKeyArg()], + ) + return + + # If we get this far, we're a maplike declaration. + + # valueType get(keyType key) + # + # Note that instead of the value type, we're using any here. The + # validity checks should happen as things are inserted into the map, + # and using any as the return type makes code generation much simpler. + # + # TODO: Bug 1155340 may change this to use specific type to provide + # more info to JIT. + self.addMethod( + "get", + members, + False, + BuiltinTypes[IDLBuiltinType.Types.any], + [getKeyArg()], + isPure=True, + ) + + def getValueArg(): + return IDLArgument( + self.location, + IDLUnresolvedIdentifier(self.location, "value"), + self.valueType, + ) + + if not self.readonly: + self.addMethod( + "set", + members, + True, + BuiltinTypes[IDLBuiltinType.Types.object], + [getKeyArg(), getValueArg()], + ) + + +class IDLConst(IDLInterfaceMember): + def __init__(self, location, identifier, type, value): + IDLInterfaceMember.__init__( + self, location, identifier, IDLInterfaceMember.Tags.Const + ) + + assert isinstance(type, IDLType) + if type.isDictionary(): + raise WebIDLError( + "A constant cannot be of a dictionary type", [self.location] + ) + if type.isRecord(): + raise WebIDLError("A constant cannot be of a record type", [self.location]) + self.type = type + self.value = value + + if identifier.name == "prototype": + raise WebIDLError( + "The identifier of a constant must not be 'prototype'", [location] + ) + + def __str__(self): + return "'%s' const '%s'" % (self.type, self.identifier) + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + + if not self.type.isComplete(): + type = self.type.complete(scope) + if not type.isPrimitive() and not type.isString(): + locations = [self.type.location, type.location] + try: + locations.append(type.inner.location) + except Exception: + pass + raise WebIDLError("Incorrect type for constant", locations) + self.type = type + + # The value might not match the type + coercedValue = self.value.coerceToType(self.type, self.location) + assert coercedValue + + self.value = coercedValue + + def validate(self): + IDLInterfaceMember.validate(self) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif ( + identifier == "Pref" + or identifier == "ChromeOnly" + or identifier == "Func" + or identifier == "Trial" + or identifier == "SecureContext" + or identifier == "NonEnumerable" + ): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError( + "Unknown extended attribute %s on constant" % identifier, + [attr.location], + ) + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def _getDependentObjects(self): + return set([self.type, self.value]) + + +class IDLAttribute(IDLInterfaceMember): + def __init__( + self, + location, + identifier, + type, + readonly, + inherit=False, + static=False, + stringifier=False, + maplikeOrSetlike=None, + extendedAttrDict=None, + ): + IDLInterfaceMember.__init__( + self, + location, + identifier, + IDLInterfaceMember.Tags.Attr, + extendedAttrDict=extendedAttrDict, + ) + + assert isinstance(type, IDLType) + self.type = type + self.readonly = readonly + self.inherit = inherit + self._static = static + self.legacyLenientThis = False + self._legacyUnforgeable = False + self.stringifier = stringifier + self.slotIndices = None + assert maplikeOrSetlike is None or isinstance( + maplikeOrSetlike, IDLMaplikeOrSetlike + ) + self.maplikeOrSetlike = maplikeOrSetlike + self.dependsOn = "Everything" + self.affects = "Everything" + self.bindingAliases = [] + + if static and identifier.name == "prototype": + raise WebIDLError( + "The identifier of a static attribute must not be 'prototype'", + [location], + ) + + if readonly and inherit: + raise WebIDLError( + "An attribute cannot be both 'readonly' and 'inherit'", [self.location] + ) + + def isStatic(self): + return self._static + + def forceStatic(self): + self._static = True + + def __str__(self): + return "'%s' attribute '%s'" % (self.type, self.identifier) + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + + if not self.type.isComplete(): + t = self.type.complete(scope) + + assert not isinstance(t, IDLUnresolvedType) + assert not isinstance(t, IDLTypedefType) + assert not isinstance(t.name, IDLUnresolvedIdentifier) + self.type = t + + if self.readonly and ( + self.type.hasClamp() + or self.type.hasEnforceRange() + or self.type.hasAllowShared() + or self.type.legacyNullToEmptyString + ): + raise WebIDLError( + "A readonly attribute cannot be [Clamp] or [EnforceRange] or [AllowShared]", + [self.location], + ) + if self.type.isDictionary() and not self.getExtendedAttribute("Cached"): + raise WebIDLError( + "An attribute cannot be of a dictionary type", [self.location] + ) + if self.type.isSequence() and not self.getExtendedAttribute("Cached"): + raise WebIDLError( + "A non-cached attribute cannot be of a sequence " "type", + [self.location], + ) + if self.type.isRecord() and not self.getExtendedAttribute("Cached"): + raise WebIDLError( + "A non-cached attribute cannot be of a record " "type", [self.location] + ) + if self.type.isUnion(): + for f in self.type.unroll().flatMemberTypes: + if f.isDictionary(): + raise WebIDLError( + "An attribute cannot be of a union " + "type if one of its member types (or " + "one of its member types's member " + "types, and so on) is a dictionary " + "type", + [self.location, f.location], + ) + if f.isSequence(): + raise WebIDLError( + "An attribute cannot be of a union " + "type if one of its member types (or " + "one of its member types's member " + "types, and so on) is a sequence " + "type", + [self.location, f.location], + ) + if f.isRecord(): + raise WebIDLError( + "An attribute cannot be of a union " + "type if one of its member types (or " + "one of its member types's member " + "types, and so on) is a record " + "type", + [self.location, f.location], + ) + if not self.type.isInterface() and self.getExtendedAttribute("PutForwards"): + raise WebIDLError( + "An attribute with [PutForwards] must have an " + "interface type as its type", + [self.location], + ) + + if not self.type.isInterface() and self.getExtendedAttribute("SameObject"): + raise WebIDLError( + "An attribute with [SameObject] must have an " + "interface type as its type", + [self.location], + ) + + if self.type.isPromise() and not self.readonly: + raise WebIDLError( + "Promise-returning attributes must be readonly", [self.location] + ) + + if self.type.isObservableArray(): + if self.isStatic(): + raise WebIDLError( + "A static attribute cannot have an ObservableArray type", + [self.location], + ) + if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( + "StoreInSlot" + ): + raise WebIDLError( + "[Cached] and [StoreInSlot] must not be used " + "on an attribute whose type is ObservableArray", + [self.location], + ) + + def validate(self): + def typeContainsChromeOnlyDictionaryMember(type): + if type.nullable() or type.isSequence() or type.isRecord(): + return typeContainsChromeOnlyDictionaryMember(type.inner) + + if type.isUnion(): + for memberType in type.flatMemberTypes: + (contains, location) = typeContainsChromeOnlyDictionaryMember( + memberType + ) + if contains: + return (True, location) + + if type.isDictionary(): + dictionary = type.inner + while dictionary: + (contains, location) = dictionaryContainsChromeOnlyMember( + dictionary + ) + if contains: + return (True, location) + dictionary = dictionary.parent + + return (False, None) + + def dictionaryContainsChromeOnlyMember(dictionary): + for member in dictionary.members: + if member.getExtendedAttribute("ChromeOnly"): + return (True, member.location) + (contains, location) = typeContainsChromeOnlyDictionaryMember( + member.type + ) + if contains: + return (True, location) + return (False, None) + + IDLInterfaceMember.validate(self) + + if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( + "StoreInSlot" + ): + if not self.affects == "Nothing": + raise WebIDLError( + "Cached attributes and attributes stored in " + "slots must be Constant or Pure or " + "Affects=Nothing, since the getter won't always " + "be called.", + [self.location], + ) + (contains, location) = typeContainsChromeOnlyDictionaryMember(self.type) + if contains: + raise WebIDLError( + "[Cached] and [StoreInSlot] must not be used " + "on an attribute whose type contains a " + "[ChromeOnly] dictionary member", + [self.location, location], + ) + if self.getExtendedAttribute("Frozen"): + if ( + not self.type.isSequence() + and not self.type.isDictionary() + and not self.type.isRecord() + ): + raise WebIDLError( + "[Frozen] is only allowed on " + "sequence-valued, dictionary-valued, and " + "record-valued attributes", + [self.location], + ) + if not self.type.unroll().isExposedInAllOf(self.exposureSet): + raise WebIDLError( + "Attribute returns a type that is not exposed " + "everywhere where the attribute is exposed", + [self.location], + ) + if self.getExtendedAttribute("CEReactions"): + if self.readonly: + raise WebIDLError( + "[CEReactions] is not allowed on " "readonly attributes", + [self.location], + ) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if ( + identifier == "SetterThrows" + or identifier == "SetterCanOOM" + or identifier == "SetterNeedsSubjectPrincipal" + ) and self.readonly: + raise WebIDLError( + "Readonly attributes must not be flagged as " "[%s]" % identifier, + [self.location], + ) + elif identifier == "BindingAlias": + if not attr.hasValue(): + raise WebIDLError( + "[BindingAlias] takes an identifier or string", [attr.location] + ) + self._addBindingAlias(attr.value()) + elif ( + ( + identifier == "Throws" + or identifier == "GetterThrows" + or identifier == "CanOOM" + or identifier == "GetterCanOOM" + ) + and self.getExtendedAttribute("StoreInSlot") + ) or ( + identifier == "StoreInSlot" + and ( + self.getExtendedAttribute("Throws") + or self.getExtendedAttribute("GetterThrows") + or self.getExtendedAttribute("CanOOM") + or self.getExtendedAttribute("GetterCanOOM") + ) + ): + raise WebIDLError("Throwing things can't be [StoreInSlot]", [attr.location]) + elif identifier == "LegacyLenientThis": + if not attr.noArguments(): + raise WebIDLError( + "[LegacyLenientThis] must take no arguments", [attr.location] + ) + if self.isStatic(): + raise WebIDLError( + "[LegacyLenientThis] is only allowed on non-static " "attributes", + [attr.location, self.location], + ) + if self.getExtendedAttribute("CrossOriginReadable"): + raise WebIDLError( + "[LegacyLenientThis] is not allowed in combination " + "with [CrossOriginReadable]", + [attr.location, self.location], + ) + if self.getExtendedAttribute("CrossOriginWritable"): + raise WebIDLError( + "[LegacyLenientThis] is not allowed in combination " + "with [CrossOriginWritable]", + [attr.location, self.location], + ) + self.legacyLenientThis = True + elif identifier == "LegacyUnforgeable": + if self.isStatic(): + raise WebIDLError( + "[LegacyUnforgeable] is only allowed on non-static " "attributes", + [attr.location, self.location], + ) + self._legacyUnforgeable = True + elif identifier == "SameObject" and not self.readonly: + raise WebIDLError( + "[SameObject] only allowed on readonly attributes", + [attr.location, self.location], + ) + elif identifier == "Constant" and not self.readonly: + raise WebIDLError( + "[Constant] only allowed on readonly attributes", + [attr.location, self.location], + ) + elif identifier == "PutForwards": + if not self.readonly: + raise WebIDLError( + "[PutForwards] is only allowed on readonly " "attributes", + [attr.location, self.location], + ) + if self.type.isPromise(): + raise WebIDLError( + "[PutForwards] is not allowed on " "Promise-typed attributes", + [attr.location, self.location], + ) + if self.isStatic(): + raise WebIDLError( + "[PutForwards] is only allowed on non-static " "attributes", + [attr.location, self.location], + ) + if self.getExtendedAttribute("Replaceable") is not None: + raise WebIDLError( + "[PutForwards] and [Replaceable] can't both " + "appear on the same attribute", + [attr.location, self.location], + ) + if not attr.hasValue(): + raise WebIDLError( + "[PutForwards] takes an identifier", [attr.location, self.location] + ) + elif identifier == "Replaceable": + if not attr.noArguments(): + raise WebIDLError( + "[Replaceable] must take no arguments", [attr.location] + ) + if not self.readonly: + raise WebIDLError( + "[Replaceable] is only allowed on readonly " "attributes", + [attr.location, self.location], + ) + if self.type.isPromise(): + raise WebIDLError( + "[Replaceable] is not allowed on " "Promise-typed attributes", + [attr.location, self.location], + ) + if self.isStatic(): + raise WebIDLError( + "[Replaceable] is only allowed on non-static " "attributes", + [attr.location, self.location], + ) + if self.getExtendedAttribute("PutForwards") is not None: + raise WebIDLError( + "[PutForwards] and [Replaceable] can't both " + "appear on the same attribute", + [attr.location, self.location], + ) + elif identifier == "LegacyLenientSetter": + if not attr.noArguments(): + raise WebIDLError( + "[LegacyLenientSetter] must take no arguments", [attr.location] + ) + if not self.readonly: + raise WebIDLError( + "[LegacyLenientSetter] is only allowed on readonly " "attributes", + [attr.location, self.location], + ) + if self.type.isPromise(): + raise WebIDLError( + "[LegacyLenientSetter] is not allowed on " + "Promise-typed attributes", + [attr.location, self.location], + ) + if self.isStatic(): + raise WebIDLError( + "[LegacyLenientSetter] is only allowed on non-static " "attributes", + [attr.location, self.location], + ) + if self.getExtendedAttribute("PutForwards") is not None: + raise WebIDLError( + "[LegacyLenientSetter] and [PutForwards] can't both " + "appear on the same attribute", + [attr.location, self.location], + ) + if self.getExtendedAttribute("Replaceable") is not None: + raise WebIDLError( + "[LegacyLenientSetter] and [Replaceable] can't both " + "appear on the same attribute", + [attr.location, self.location], + ) + elif identifier == "LenientFloat": + if self.readonly: + raise WebIDLError( + "[LenientFloat] used on a readonly attribute", + [attr.location, self.location], + ) + if not self.type.includesRestrictedFloat(): + raise WebIDLError( + "[LenientFloat] used on an attribute with a " + "non-restricted-float type", + [attr.location, self.location], + ) + elif identifier == "StoreInSlot": + if self.getExtendedAttribute("Cached"): + raise WebIDLError( + "[StoreInSlot] and [Cached] must not be " + "specified on the same attribute", + [attr.location, self.location], + ) + elif identifier == "Cached": + if self.getExtendedAttribute("StoreInSlot"): + raise WebIDLError( + "[Cached] and [StoreInSlot] must not be " + "specified on the same attribute", + [attr.location, self.location], + ) + elif identifier == "CrossOriginReadable" or identifier == "CrossOriginWritable": + if not attr.noArguments(): + raise WebIDLError( + "[%s] must take no arguments" % identifier, [attr.location] + ) + if self.isStatic(): + raise WebIDLError( + "[%s] is only allowed on non-static " "attributes" % identifier, + [attr.location, self.location], + ) + if self.getExtendedAttribute("LegacyLenientThis"): + raise WebIDLError( + "[LegacyLenientThis] is not allowed in combination " + "with [%s]" % identifier, + [attr.location, self.location], + ) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif identifier == "Pure": + if not attr.noArguments(): + raise WebIDLError("[Pure] must take no arguments", [attr.location]) + self._setDependsOn("DOMState") + self._setAffects("Nothing") + elif identifier == "Constant" or identifier == "SameObject": + if not attr.noArguments(): + raise WebIDLError( + "[%s] must take no arguments" % identifier, [attr.location] + ) + self._setDependsOn("Nothing") + self._setAffects("Nothing") + elif identifier == "Affects": + if not attr.hasValue(): + raise WebIDLError("[Affects] takes an identifier", [attr.location]) + self._setAffects(attr.value()) + elif identifier == "DependsOn": + if not attr.hasValue(): + raise WebIDLError("[DependsOn] takes an identifier", [attr.location]) + if ( + attr.value() != "Everything" + and attr.value() != "DOMState" + and not self.readonly + ): + raise WebIDLError( + "[DependsOn=%s] only allowed on " + "readonly attributes" % attr.value(), + [attr.location, self.location], + ) + self._setDependsOn(attr.value()) + elif identifier == "UseCounter": + if self.stringifier: + raise WebIDLError( + "[UseCounter] must not be used on a " "stringifier attribute", + [attr.location, self.location], + ) + elif identifier == "Unscopable": + if not attr.noArguments(): + raise WebIDLError( + "[Unscopable] must take no arguments", [attr.location] + ) + if self.isStatic(): + raise WebIDLError( + "[Unscopable] is only allowed on non-static " + "attributes and operations", + [attr.location, self.location], + ) + elif identifier == "CEReactions": + if not attr.noArguments(): + raise WebIDLError( + "[CEReactions] must take no arguments", [attr.location] + ) + elif ( + identifier == "Pref" + or identifier == "Deprecated" + or identifier == "SetterThrows" + or identifier == "Throws" + or identifier == "GetterThrows" + or identifier == "SetterCanOOM" + or identifier == "CanOOM" + or identifier == "GetterCanOOM" + or identifier == "ChromeOnly" + or identifier == "Func" + or identifier == "Trial" + or identifier == "SecureContext" + or identifier == "Frozen" + or identifier == "NewObject" + or identifier == "NeedsSubjectPrincipal" + or identifier == "SetterNeedsSubjectPrincipal" + or identifier == "GetterNeedsSubjectPrincipal" + or identifier == "NeedsCallerType" + or identifier == "ReturnValueNeedsContainsHack" + or identifier == "BinaryName" + or identifier == "NonEnumerable" + ): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError( + "Unknown extended attribute %s on attribute" % identifier, + [attr.location], + ) + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.type.resolveType(parentScope) + IDLObjectWithIdentifier.resolve(self, parentScope) + + def hasLegacyLenientThis(self): + return self.legacyLenientThis + + def isMaplikeOrSetlikeAttr(self): + """ + True if this attribute was generated from an interface with + maplike/setlike (e.g. this is the size attribute for + maplike/setlike) + """ + return self.maplikeOrSetlike is not None + + def isLegacyUnforgeable(self): + return self._legacyUnforgeable + + def _getDependentObjects(self): + return set([self.type]) + + def expand(self, members): + assert self.stringifier + if ( + not self.type.isDOMString() + and not self.type.isUSVString() + and not self.type.isUTF8String() + ): + raise WebIDLError( + "The type of a stringifer attribute must be " + "either DOMString, USVString or UTF8String", + [self.location], + ) + identifier = IDLUnresolvedIdentifier( + self.location, "__stringifier", allowDoubleUnderscore=True + ) + method = IDLMethod( + self.location, + identifier, + returnType=self.type, + arguments=[], + stringifier=True, + underlyingAttr=self, + ) + allowedExtAttrs = ["Throws", "NeedsSubjectPrincipal", "Pure"] + # Safe to ignore these as they are only meaningful for attributes + attributeOnlyExtAttrs = [ + "CEReactions", + "CrossOriginWritable", + "SetterThrows", + ] + for (key, value) in self._extendedAttrDict.items(): + if key in allowedExtAttrs: + if value is not True: + raise WebIDLError( + "[%s] with a value is currently " + "unsupported in stringifier attributes, " + "please file a bug to add support" % key, + [self.location], + ) + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, (key,))] + ) + elif key not in attributeOnlyExtAttrs: + raise WebIDLError( + "[%s] is currently unsupported in " + "stringifier attributes, please file a bug " + "to add support" % key, + [self.location], + ) + members.append(method) + + +class IDLArgument(IDLObjectWithIdentifier): + def __init__( + self, + location, + identifier, + type, + optional=False, + defaultValue=None, + variadic=False, + dictionaryMember=False, + allowTypeAttributes=False, + ): + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + + assert isinstance(type, IDLType) + self.type = type + + self.optional = optional + self.defaultValue = defaultValue + self.variadic = variadic + self.dictionaryMember = dictionaryMember + self._isComplete = False + self._allowTreatNonCallableAsNull = False + self._extendedAttrDict = {} + self.allowTypeAttributes = allowTypeAttributes + + assert not variadic or optional + assert not variadic or not defaultValue + + def addExtendedAttributes(self, attrs): + for attribute in attrs: + identifier = attribute.identifier() + if self.allowTypeAttributes and ( + identifier == "EnforceRange" + or identifier == "Clamp" + or identifier == "LegacyNullToEmptyString" + or identifier == "AllowShared" + ): + self.type = self.type.withExtendedAttributes([attribute]) + elif identifier == "TreatNonCallableAsNull": + self._allowTreatNonCallableAsNull = True + elif self.dictionaryMember and ( + identifier == "ChromeOnly" + or identifier == "Func" + or identifier == "Trial" + or identifier == "Pref" + ): + if not self.optional: + raise WebIDLError( + "[%s] must not be used on a required " + "dictionary member" % identifier, + [attribute.location], + ) + else: + raise WebIDLError( + "Unhandled extended attribute on %s" + % ( + "a dictionary member" + if self.dictionaryMember + else "an argument" + ), + [attribute.location], + ) + attrlist = attribute.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def isComplete(self): + return self._isComplete + + def complete(self, scope): + if self._isComplete: + return + + self._isComplete = True + + if not self.type.isComplete(): + type = self.type.complete(scope) + assert not isinstance(type, IDLUnresolvedType) + assert not isinstance(type, IDLTypedefType) + assert not isinstance(type.name, IDLUnresolvedIdentifier) + self.type = type + + if self.type.isUndefined(): + raise WebIDLError( + "undefined must not be used as the type of an argument in any circumstance", + [self.location], + ) + + if self.type.isAny(): + assert self.defaultValue is None or isinstance( + self.defaultValue, IDLNullValue + ) + # optional 'any' values always have a default value + if self.optional and not self.defaultValue and not self.variadic: + # Set the default value to undefined, for simplicity, so the + # codegen doesn't have to special-case this. + self.defaultValue = IDLUndefinedValue(self.location) + + if self.dictionaryMember and self.type.legacyNullToEmptyString: + raise WebIDLError( + "Dictionary members cannot be [LegacyNullToEmptyString]", + [self.location], + ) + if self.type.isObservableArray(): + raise WebIDLError( + "%s cannot have an ObservableArray type" + % ("Dictionary members" if self.dictionaryMember else "Arguments"), + [self.location], + ) + # Now do the coercing thing; this needs to happen after the + # above creation of a default value. + if self.defaultValue: + self.defaultValue = self.defaultValue.coerceToType(self.type, self.location) + assert self.defaultValue + + def allowTreatNonCallableAsNull(self): + return self._allowTreatNonCallableAsNull + + def _getDependentObjects(self): + deps = set([self.type]) + if self.defaultValue: + deps.add(self.defaultValue) + return deps + + def canHaveMissingValue(self): + return self.optional and not self.defaultValue + + +class IDLCallback(IDLObjectWithScope): + def __init__( + self, location, parentScope, identifier, returnType, arguments, isConstructor + ): + assert isinstance(returnType, IDLType) + + self._returnType = returnType + # Clone the list + self._arguments = list(arguments) + + IDLObjectWithScope.__init__(self, location, parentScope, identifier) + + for (returnType, arguments) in self.signatures(): + for argument in arguments: + argument.resolve(self) + + self._treatNonCallableAsNull = False + self._treatNonObjectAsNull = False + self._isRunScriptBoundary = False + self._isConstructor = isConstructor + + def isCallback(self): + return True + + def isConstructor(self): + return self._isConstructor + + def signatures(self): + return [(self._returnType, self._arguments)] + + def finish(self, scope): + if not self._returnType.isComplete(): + type = self._returnType.complete(scope) + + assert not isinstance(type, IDLUnresolvedType) + assert not isinstance(type, IDLTypedefType) + assert not isinstance(type.name, IDLUnresolvedIdentifier) + self._returnType = type + + for argument in self._arguments: + if argument.type.isComplete(): + continue + + type = argument.type.complete(scope) + + assert not isinstance(type, IDLUnresolvedType) + assert not isinstance(type, IDLTypedefType) + assert not isinstance(type.name, IDLUnresolvedIdentifier) + argument.type = type + + def validate(self): + for argument in self._arguments: + if argument.type.isUndefined(): + raise WebIDLError( + "undefined must not be used as the type of an argument in any circumstance", + [self.location], + ) + + def addExtendedAttributes(self, attrs): + unhandledAttrs = [] + for attr in attrs: + if attr.identifier() == "TreatNonCallableAsNull": + self._treatNonCallableAsNull = True + elif attr.identifier() == "LegacyTreatNonObjectAsNull": + if self._isConstructor: + raise WebIDLError( + "[LegacyTreatNonObjectAsNull] is not supported " + "on constructors", + [self.location], + ) + self._treatNonObjectAsNull = True + elif attr.identifier() == "MOZ_CAN_RUN_SCRIPT_BOUNDARY": + if self._isConstructor: + raise WebIDLError( + "[MOZ_CAN_RUN_SCRIPT_BOUNDARY] is not " + "permitted on constructors", + [self.location], + ) + self._isRunScriptBoundary = True + else: + unhandledAttrs.append(attr) + if self._treatNonCallableAsNull and self._treatNonObjectAsNull: + raise WebIDLError( + "Cannot specify both [TreatNonCallableAsNull] " + "and [LegacyTreatNonObjectAsNull]", + [self.location], + ) + if len(unhandledAttrs) != 0: + IDLType.addExtendedAttributes(self, unhandledAttrs) + + def _getDependentObjects(self): + return set([self._returnType] + self._arguments) + + def isRunScriptBoundary(self): + return self._isRunScriptBoundary + + +class IDLCallbackType(IDLType): + def __init__(self, location, callback): + IDLType.__init__(self, location, callback.identifier.name) + self.callback = callback + + def isCallback(self): + return True + + def tag(self): + return IDLType.Tags.callback + + def isDistinguishableFrom(self, other): + if other.isPromise(): + return False + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return ( + other.isUndefined() + or other.isPrimitive() + or other.isString() + or other.isEnum() + or other.isNonCallbackInterface() + or other.isSequence() + ) + + def _getDependentObjects(self): + return self.callback._getDependentObjects() + + +class IDLMethodOverload: + """ + A class that represents a single overload of a WebIDL method. This is not + quite the same as an element of the "effective overload set" in the spec, + because separate IDLMethodOverloads are not created based on arguments being + optional. Rather, when multiple methods have the same name, there is an + IDLMethodOverload for each one, all hanging off an IDLMethod representing + the full set of overloads. + """ + + def __init__(self, returnType, arguments, location): + self.returnType = returnType + # Clone the list of arguments, just in case + self.arguments = list(arguments) + self.location = location + + def _getDependentObjects(self): + deps = set(self.arguments) + deps.add(self.returnType) + return deps + + def includesRestrictedFloatArgument(self): + return any(arg.type.includesRestrictedFloat() for arg in self.arguments) + + +class IDLMethod(IDLInterfaceMember, IDLScope): + + Special = enum( + "Getter", "Setter", "Deleter", "LegacyCaller", base=IDLInterfaceMember.Special + ) + + NamedOrIndexed = enum("Neither", "Named", "Indexed") + + def __init__( + self, + location, + identifier, + returnType, + arguments, + static=False, + getter=False, + setter=False, + deleter=False, + specialType=NamedOrIndexed.Neither, + legacycaller=False, + stringifier=False, + maplikeOrSetlikeOrIterable=None, + underlyingAttr=None, + ): + # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up. + IDLInterfaceMember.__init__( + self, location, identifier, IDLInterfaceMember.Tags.Method + ) + + self._hasOverloads = False + + assert isinstance(returnType, IDLType) + + # self._overloads is a list of IDLMethodOverloads + self._overloads = [IDLMethodOverload(returnType, arguments, location)] + + assert isinstance(static, bool) + self._static = static + assert isinstance(getter, bool) + self._getter = getter + assert isinstance(setter, bool) + self._setter = setter + assert isinstance(deleter, bool) + self._deleter = deleter + assert isinstance(legacycaller, bool) + self._legacycaller = legacycaller + assert isinstance(stringifier, bool) + self._stringifier = stringifier + assert maplikeOrSetlikeOrIterable is None or isinstance( + maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase + ) + self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable + self._htmlConstructor = False + self.underlyingAttr = underlyingAttr + self._specialType = specialType + self._legacyUnforgeable = False + self.dependsOn = "Everything" + self.affects = "Everything" + self.aliases = [] + + if static and identifier.name == "prototype": + raise WebIDLError( + "The identifier of a static operation must not be 'prototype'", + [location], + ) + + self.assertSignatureConstraints() + + def __str__(self): + return "Method '%s'" % self.identifier + + def assertSignatureConstraints(self): + if self._getter or self._deleter: + assert len(self._overloads) == 1 + overload = self._overloads[0] + arguments = overload.arguments + assert len(arguments) == 1 + assert ( + arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] + or arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long] + ) + assert not arguments[0].optional and not arguments[0].variadic + assert not self._getter or not overload.returnType.isUndefined() + + if self._setter: + assert len(self._overloads) == 1 + arguments = self._overloads[0].arguments + assert len(arguments) == 2 + assert ( + arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] + or arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long] + ) + assert not arguments[0].optional and not arguments[0].variadic + assert not arguments[1].optional and not arguments[1].variadic + + if self._stringifier: + assert len(self._overloads) == 1 + overload = self._overloads[0] + assert len(overload.arguments) == 0 + if not self.underlyingAttr: + assert ( + overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring] + ) + + def isStatic(self): + return self._static + + def forceStatic(self): + self._static = True + + def isGetter(self): + return self._getter + + def isSetter(self): + return self._setter + + def isDeleter(self): + return self._deleter + + def isNamed(self): + assert ( + self._specialType == IDLMethod.NamedOrIndexed.Named + or self._specialType == IDLMethod.NamedOrIndexed.Indexed + ) + return self._specialType == IDLMethod.NamedOrIndexed.Named + + def isIndexed(self): + assert ( + self._specialType == IDLMethod.NamedOrIndexed.Named + or self._specialType == IDLMethod.NamedOrIndexed.Indexed + ) + return self._specialType == IDLMethod.NamedOrIndexed.Indexed + + def isLegacycaller(self): + return self._legacycaller + + def isStringifier(self): + return self._stringifier + + def isToJSON(self): + return self.identifier.name == "toJSON" + + def isDefaultToJSON(self): + return self.isToJSON() and self.getExtendedAttribute("Default") + + def isMaplikeOrSetlikeOrIterableMethod(self): + """ + True if this method was generated as part of a + maplike/setlike/etc interface (e.g. has/get methods) + """ + return self.maplikeOrSetlikeOrIterable is not None + + def isSpecial(self): + return ( + self.isGetter() + or self.isSetter() + or self.isDeleter() + or self.isLegacycaller() + or self.isStringifier() + ) + + def isHTMLConstructor(self): + return self._htmlConstructor + + def hasOverloads(self): + return self._hasOverloads + + def isIdentifierLess(self): + """ + True if the method name started with __, and if the method is not a + maplike/setlike method. Interfaces with maplike/setlike will generate + methods starting with __ for chrome only backing object access in JS + implemented interfaces, so while these functions use what is considered + an non-identifier name, they actually DO have an identifier. + """ + return ( + self.identifier.name[:2] == "__" + and not self.isMaplikeOrSetlikeOrIterableMethod() + ) + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + IDLObjectWithIdentifier.resolve(self, parentScope) + IDLScope.__init__(self, self.location, parentScope, self.identifier) + for (returnType, arguments) in self.signatures(): + for argument in arguments: + argument.resolve(self) + + def addOverload(self, method): + assert len(method._overloads) == 1 + + if self._extendedAttrDict != method._extendedAttrDict: + extendedAttrDiff = set(self._extendedAttrDict.keys()) ^ set( + method._extendedAttrDict.keys() + ) + + if extendedAttrDiff == {"LenientFloat"}: + if "LenientFloat" not in self._extendedAttrDict: + for overload in self._overloads: + if overload.includesRestrictedFloatArgument(): + raise WebIDLError( + "Restricted float behavior differs on different " + "overloads of %s" % method.identifier, + [overload.location, method.location], + ) + self._extendedAttrDict["LenientFloat"] = method._extendedAttrDict[ + "LenientFloat" + ] + elif method._overloads[0].includesRestrictedFloatArgument(): + raise WebIDLError( + "Restricted float behavior differs on different " + "overloads of %s" % method.identifier, + [self.location, method.location], + ) + else: + raise WebIDLError( + "Extended attributes differ on different " + "overloads of %s" % method.identifier, + [self.location, method.location], + ) + + self._overloads.extend(method._overloads) + + self._hasOverloads = True + + if self.isStatic() != method.isStatic(): + raise WebIDLError( + "Overloaded identifier %s appears with different values of the 'static' attribute" + % method.identifier, + [method.location], + ) + + if self.isLegacycaller() != method.isLegacycaller(): + raise WebIDLError( + ( + "Overloaded identifier %s appears with different " + "values of the 'legacycaller' attribute" % method.identifier + ), + [method.location], + ) + + # Can't overload special things! + assert not self.isGetter() + assert not method.isGetter() + assert not self.isSetter() + assert not method.isSetter() + assert not self.isDeleter() + assert not method.isDeleter() + assert not self.isStringifier() + assert not method.isStringifier() + assert not self.isHTMLConstructor() + assert not method.isHTMLConstructor() + + return self + + def signatures(self): + return [ + (overload.returnType, overload.arguments) for overload in self._overloads + ] + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + + for overload in self._overloads: + returnType = overload.returnType + if not returnType.isComplete(): + returnType = returnType.complete(scope) + assert not isinstance(returnType, IDLUnresolvedType) + assert not isinstance(returnType, IDLTypedefType) + assert not isinstance(returnType.name, IDLUnresolvedIdentifier) + overload.returnType = returnType + + for argument in overload.arguments: + if not argument.isComplete(): + argument.complete(scope) + assert argument.type.isComplete() + + # Now compute various information that will be used by the + # WebIDL overload resolution algorithm. + self.maxArgCount = max(len(s[1]) for s in self.signatures()) + self.allowedArgCounts = [ + i + for i in range(self.maxArgCount + 1) + if len(self.signaturesForArgCount(i)) != 0 + ] + + def validate(self): + IDLInterfaceMember.validate(self) + + # Make sure our overloads are properly distinguishable and don't have + # different argument types before the distinguishing args. + for argCount in self.allowedArgCounts: + possibleOverloads = self.overloadsForArgCount(argCount) + if len(possibleOverloads) == 1: + continue + distinguishingIndex = self.distinguishingIndexForArgCount(argCount) + for idx in range(distinguishingIndex): + firstSigType = possibleOverloads[0].arguments[idx].type + for overload in possibleOverloads[1:]: + if overload.arguments[idx].type != firstSigType: + raise WebIDLError( + "Signatures for method '%s' with %d arguments have " + "different types of arguments at index %d, which " + "is before distinguishing index %d" + % ( + self.identifier.name, + argCount, + idx, + distinguishingIndex, + ), + [self.location, overload.location], + ) + + overloadWithPromiseReturnType = None + overloadWithoutPromiseReturnType = None + for overload in self._overloads: + returnType = overload.returnType + if not returnType.unroll().isExposedInAllOf(self.exposureSet): + raise WebIDLError( + "Overload returns a type that is not exposed " + "everywhere where the method is exposed", + [overload.location], + ) + + variadicArgument = None + + arguments = overload.arguments + for (idx, argument) in enumerate(arguments): + assert argument.type.isComplete() + + if ( + argument.type.isDictionary() + and argument.type.unroll().inner.canBeEmpty() + ) or ( + argument.type.isUnion() + and argument.type.unroll().hasPossiblyEmptyDictionaryType() + ): + # Optional dictionaries and unions containing optional + # dictionaries at the end of the list or followed by + # optional arguments must be optional. + if not argument.optional and all( + arg.optional for arg in arguments[idx + 1 :] + ): + raise WebIDLError( + "Dictionary argument without any " + "required fields or union argument " + "containing such dictionary not " + "followed by a required argument " + "must be optional", + [argument.location], + ) + + if not argument.defaultValue and all( + arg.optional for arg in arguments[idx + 1 :] + ): + raise WebIDLError( + "Dictionary argument without any " + "required fields or union argument " + "containing such dictionary not " + "followed by a required argument " + "must have a default value", + [argument.location], + ) + + # An argument cannot be a nullable dictionary or a + # nullable union containing a dictionary. + if argument.type.nullable() and ( + argument.type.isDictionary() + or ( + argument.type.isUnion() + and argument.type.unroll().hasDictionaryType() + ) + ): + raise WebIDLError( + "An argument cannot be a nullable " + "dictionary or nullable union " + "containing a dictionary", + [argument.location], + ) + + # Only the last argument can be variadic + if variadicArgument: + raise WebIDLError( + "Variadic argument is not last argument", + [variadicArgument.location], + ) + if argument.variadic: + variadicArgument = argument + + if returnType.isPromise(): + overloadWithPromiseReturnType = overload + else: + overloadWithoutPromiseReturnType = overload + + # Make sure either all our overloads return Promises or none do + if overloadWithPromiseReturnType and overloadWithoutPromiseReturnType: + raise WebIDLError( + "We have overloads with both Promise and " "non-Promise return types", + [ + overloadWithPromiseReturnType.location, + overloadWithoutPromiseReturnType.location, + ], + ) + + if overloadWithPromiseReturnType and self._legacycaller: + raise WebIDLError( + "May not have a Promise return type for a " "legacycaller.", + [overloadWithPromiseReturnType.location], + ) + + if self.getExtendedAttribute("StaticClassOverride") and not ( + self.identifier.scope.isJSImplemented() and self.isStatic() + ): + raise WebIDLError( + "StaticClassOverride can be applied to static" + " methods on JS-implemented classes only.", + [self.location], + ) + + # Ensure that toJSON methods satisfy the spec constraints on them. + if self.identifier.name == "toJSON": + if len(self.signatures()) != 1: + raise WebIDLError( + "toJSON method has multiple overloads", + [self._overloads[0].location, self._overloads[1].location], + ) + if len(self.signatures()[0][1]) != 0: + raise WebIDLError("toJSON method has arguments", [self.location]) + if not self.signatures()[0][0].isJSONType(): + raise WebIDLError( + "toJSON method has non-JSON return type", [self.location] + ) + + def overloadsForArgCount(self, argc): + return [ + overload + for overload in self._overloads + if len(overload.arguments) == argc + or ( + len(overload.arguments) > argc + and all(arg.optional for arg in overload.arguments[argc:]) + ) + or ( + len(overload.arguments) < argc + and len(overload.arguments) > 0 + and overload.arguments[-1].variadic + ) + ] + + def signaturesForArgCount(self, argc): + return [ + (overload.returnType, overload.arguments) + for overload in self.overloadsForArgCount(argc) + ] + + def locationsForArgCount(self, argc): + return [overload.location for overload in self.overloadsForArgCount(argc)] + + def distinguishingIndexForArgCount(self, argc): + def isValidDistinguishingIndex(idx, signatures): + for (firstSigIndex, (firstRetval, firstArgs)) in enumerate(signatures[:-1]): + for (secondRetval, secondArgs) in signatures[firstSigIndex + 1 :]: + if idx < len(firstArgs): + firstType = firstArgs[idx].type + else: + assert firstArgs[-1].variadic + firstType = firstArgs[-1].type + if idx < len(secondArgs): + secondType = secondArgs[idx].type + else: + assert secondArgs[-1].variadic + secondType = secondArgs[-1].type + if not firstType.isDistinguishableFrom(secondType): + return False + return True + + signatures = self.signaturesForArgCount(argc) + for idx in range(argc): + if isValidDistinguishingIndex(idx, signatures): + return idx + # No valid distinguishing index. Time to throw + locations = self.locationsForArgCount(argc) + raise WebIDLError( + "Signatures with %d arguments for method '%s' are not " + "distinguishable" % (argc, self.identifier.name), + locations, + ) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if ( + identifier == "GetterThrows" + or identifier == "SetterThrows" + or identifier == "GetterCanOOM" + or identifier == "SetterCanOOM" + or identifier == "SetterNeedsSubjectPrincipal" + or identifier == "GetterNeedsSubjectPrincipal" + ): + raise WebIDLError( + "Methods must not be flagged as " "[%s]" % identifier, + [attr.location, self.location], + ) + elif identifier == "LegacyUnforgeable": + if self.isStatic(): + raise WebIDLError( + "[LegacyUnforgeable] is only allowed on non-static " "methods", + [attr.location, self.location], + ) + self._legacyUnforgeable = True + elif identifier == "SameObject": + raise WebIDLError( + "Methods must not be flagged as [SameObject]", + [attr.location, self.location], + ) + elif identifier == "Constant": + raise WebIDLError( + "Methods must not be flagged as [Constant]", + [attr.location, self.location], + ) + elif identifier == "PutForwards": + raise WebIDLError( + "Only attributes support [PutForwards]", [attr.location, self.location] + ) + elif identifier == "LegacyLenientSetter": + raise WebIDLError( + "Only attributes support [LegacyLenientSetter]", + [attr.location, self.location], + ) + elif identifier == "LenientFloat": + # This is called before we've done overload resolution + overloads = self._overloads + assert len(overloads) == 1 + if not overloads[0].returnType.isUndefined(): + raise WebIDLError( + "[LenientFloat] used on a non-undefined method", + [attr.location, self.location], + ) + if not overloads[0].includesRestrictedFloatArgument(): + raise WebIDLError( + "[LenientFloat] used on an operation with no " + "restricted float type arguments", + [attr.location, self.location], + ) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif ( + identifier == "CrossOriginCallable" + or identifier == "WebGLHandlesContextLoss" + ): + # Known no-argument attributes. + if not attr.noArguments(): + raise WebIDLError( + "[%s] must take no arguments" % identifier, [attr.location] + ) + if identifier == "CrossOriginCallable" and self.isStatic(): + raise WebIDLError( + "[CrossOriginCallable] is only allowed on non-static " "attributes", + [attr.location, self.location], + ) + elif identifier == "Pure": + if not attr.noArguments(): + raise WebIDLError("[Pure] must take no arguments", [attr.location]) + self._setDependsOn("DOMState") + self._setAffects("Nothing") + elif identifier == "Affects": + if not attr.hasValue(): + raise WebIDLError("[Affects] takes an identifier", [attr.location]) + self._setAffects(attr.value()) + elif identifier == "DependsOn": + if not attr.hasValue(): + raise WebIDLError("[DependsOn] takes an identifier", [attr.location]) + self._setDependsOn(attr.value()) + elif identifier == "Alias": + if not attr.hasValue(): + raise WebIDLError( + "[Alias] takes an identifier or string", [attr.location] + ) + self._addAlias(attr.value()) + elif identifier == "UseCounter": + if self.isSpecial(): + raise WebIDLError( + "[UseCounter] must not be used on a special " "operation", + [attr.location, self.location], + ) + elif identifier == "Unscopable": + if not attr.noArguments(): + raise WebIDLError( + "[Unscopable] must take no arguments", [attr.location] + ) + if self.isStatic(): + raise WebIDLError( + "[Unscopable] is only allowed on non-static " + "attributes and operations", + [attr.location, self.location], + ) + elif identifier == "CEReactions": + if not attr.noArguments(): + raise WebIDLError( + "[CEReactions] must take no arguments", [attr.location] + ) + + if self.isSpecial() and not self.isSetter() and not self.isDeleter(): + raise WebIDLError( + "[CEReactions] is only allowed on operation, " + "attribute, setter, and deleter", + [attr.location, self.location], + ) + elif identifier == "Default": + if not attr.noArguments(): + raise WebIDLError("[Default] must take no arguments", [attr.location]) + + if not self.isToJSON(): + raise WebIDLError( + "[Default] is only allowed on toJSON operations", + [attr.location, self.location], + ) + + if self.signatures()[0][0] != BuiltinTypes[IDLBuiltinType.Types.object]: + raise WebIDLError( + "The return type of the default toJSON " + "operation must be 'object'", + [attr.location, self.location], + ) + elif ( + identifier == "Throws" + or identifier == "CanOOM" + or identifier == "NewObject" + or identifier == "ChromeOnly" + or identifier == "Pref" + or identifier == "Deprecated" + or identifier == "Func" + or identifier == "Trial" + or identifier == "SecureContext" + or identifier == "BinaryName" + or identifier == "NeedsSubjectPrincipal" + or identifier == "NeedsCallerType" + or identifier == "StaticClassOverride" + or identifier == "NonEnumerable" + or identifier == "Unexposed" + or identifier == "WebExtensionStub" + ): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError( + "Unknown extended attribute %s on method" % identifier, [attr.location] + ) + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def returnsPromise(self): + return self._overloads[0].returnType.isPromise() + + def isLegacyUnforgeable(self): + return self._legacyUnforgeable + + def _getDependentObjects(self): + deps = set() + for overload in self._overloads: + deps.update(overload._getDependentObjects()) + return deps + + +class IDLConstructor(IDLMethod): + def __init__(self, location, args, name): + # We can't actually init our IDLMethod yet, because we do not know the + # return type yet. Just save the info we have for now and we will init + # it later. + self._initLocation = location + self._initArgs = args + self._initName = name + self._inited = False + self._initExtendedAttrs = [] + + def addExtendedAttributes(self, attrs): + if self._inited: + return IDLMethod.addExtendedAttributes(self, attrs) + self._initExtendedAttrs.extend(attrs) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if ( + identifier == "BinaryName" + or identifier == "ChromeOnly" + or identifier == "NewObject" + or identifier == "SecureContext" + or identifier == "Throws" + or identifier == "Func" + or identifier == "Trial" + or identifier == "Pref" + or identifier == "UseCounter" + ): + IDLMethod.handleExtendedAttribute(self, attr) + elif identifier == "HTMLConstructor": + if not attr.noArguments(): + raise WebIDLError( + "[HTMLConstructor] must take no arguments", [attr.location] + ) + # We shouldn't end up here for legacy factory functions. + assert self.identifier.name == "constructor" + + if any(len(sig[1]) != 0 for sig in self.signatures()): + raise WebIDLError( + "[HTMLConstructor] must not be applied to a " + "constructor operation that has arguments.", + [attr.location], + ) + self._htmlConstructor = True + else: + raise WebIDLError( + "Unknown extended attribute %s on method" % identifier, [attr.location] + ) + + def reallyInit(self, parentInterface): + name = self._initName + location = self._initLocation + identifier = IDLUnresolvedIdentifier(location, name, allowForbidden=True) + retType = IDLWrapperType(parentInterface.location, parentInterface) + IDLMethod.__init__( + self, location, identifier, retType, self._initArgs, static=True + ) + self._inited = True + # Propagate through whatever extended attributes we already had + self.addExtendedAttributes(self._initExtendedAttrs) + self._initExtendedAttrs = [] + # Constructors are always NewObject. Whether they throw or not is + # indicated by [Throws] annotations in the usual way. + self.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NewObject",))] + ) + + +class IDLIncludesStatement(IDLObject): + def __init__(self, location, interface, mixin): + IDLObject.__init__(self, location) + self.interface = interface + self.mixin = mixin + self._finished = False + + def finish(self, scope): + if self._finished: + return + self._finished = True + assert isinstance(self.interface, IDLIdentifierPlaceholder) + assert isinstance(self.mixin, IDLIdentifierPlaceholder) + interface = self.interface.finish(scope) + mixin = self.mixin.finish(scope) + # NOTE: we depend on not setting self.interface and + # self.mixin here to keep track of the original + # locations. + if not isinstance(interface, IDLInterface): + raise WebIDLError( + "Left-hand side of 'includes' is not an " "interface", + [self.interface.location, interface.location], + ) + if interface.isCallback(): + raise WebIDLError( + "Left-hand side of 'includes' is a callback " "interface", + [self.interface.location, interface.location], + ) + if not isinstance(mixin, IDLInterfaceMixin): + raise WebIDLError( + "Right-hand side of 'includes' is not an " "interface mixin", + [self.mixin.location, mixin.location], + ) + + mixin.actualExposureGlobalNames.update(interface._exposureGlobalNames) + + interface.addIncludedMixin(mixin) + self.interface = interface + self.mixin = mixin + + def validate(self): + pass + + def addExtendedAttributes(self, attrs): + if len(attrs) != 0: + raise WebIDLError( + "There are no extended attributes that are " + "allowed on includes statements", + [attrs[0].location, self.location], + ) + + +class IDLExtendedAttribute(IDLObject): + """ + A class to represent IDL extended attributes so we can give them locations + """ + + def __init__(self, location, tuple): + IDLObject.__init__(self, location) + self._tuple = tuple + + def identifier(self): + return self._tuple[0] + + def noArguments(self): + return len(self._tuple) == 1 + + def hasValue(self): + return len(self._tuple) >= 2 and isinstance(self._tuple[1], str) + + def value(self): + assert self.hasValue() + return self._tuple[1] + + def hasArgs(self): + return ( + len(self._tuple) == 2 + and isinstance(self._tuple[1], list) + or len(self._tuple) == 3 + ) + + def args(self): + assert self.hasArgs() + # Our args are our last element + return self._tuple[-1] + + def listValue(self): + """ + Backdoor for storing random data in _extendedAttrDict + """ + return list(self._tuple)[1:] + + +# Parser + + +class Tokenizer(object): + tokens = ["INTEGER", "FLOATLITERAL", "IDENTIFIER", "STRING", "WHITESPACE", "OTHER"] + + def t_FLOATLITERAL(self, t): + r"(-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+|Infinity))|NaN" + t.value = float(t.value) + return t + + def t_INTEGER(self, t): + r"-?(0([0-7]+|[Xx][0-9A-Fa-f]+)?|[1-9][0-9]*)" + try: + # Can't use int(), because that doesn't handle octal properly. + t.value = parseInt(t.value) + except Exception: + raise WebIDLError( + "Invalid integer literal", + [ + Location( + lexer=self.lexer, + lineno=self.lexer.lineno, + lexpos=self.lexer.lexpos, + filename=self._filename, + ) + ], + ) + return t + + def t_IDENTIFIER(self, t): + r"[_-]?[A-Za-z][0-9A-Z_a-z-]*" + t.type = self.keywords.get(t.value, "IDENTIFIER") + return t + + def t_STRING(self, t): + r'"[^"]*"' + t.value = t.value[1:-1] + return t + + def t_WHITESPACE(self, t): + r"[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+" + pass + + def t_ELLIPSIS(self, t): + r"\.\.\." + t.type = self.keywords.get(t.value) + return t + + def t_OTHER(self, t): + r"[^\t\n\r 0-9A-Z_a-z]" + t.type = self.keywords.get(t.value, "OTHER") + return t + + keywords = { + "interface": "INTERFACE", + "partial": "PARTIAL", + "mixin": "MIXIN", + "dictionary": "DICTIONARY", + "exception": "EXCEPTION", + "enum": "ENUM", + "callback": "CALLBACK", + "typedef": "TYPEDEF", + "includes": "INCLUDES", + "const": "CONST", + "null": "NULL", + "true": "TRUE", + "false": "FALSE", + "serializer": "SERIALIZER", + "stringifier": "STRINGIFIER", + "unrestricted": "UNRESTRICTED", + "attribute": "ATTRIBUTE", + "readonly": "READONLY", + "inherit": "INHERIT", + "static": "STATIC", + "getter": "GETTER", + "setter": "SETTER", + "deleter": "DELETER", + "legacycaller": "LEGACYCALLER", + "optional": "OPTIONAL", + "...": "ELLIPSIS", + "::": "SCOPE", + "DOMString": "DOMSTRING", + "ByteString": "BYTESTRING", + "USVString": "USVSTRING", + "JSString": "JSSTRING", + "UTF8String": "UTF8STRING", + "any": "ANY", + "boolean": "BOOLEAN", + "byte": "BYTE", + "double": "DOUBLE", + "float": "FLOAT", + "long": "LONG", + "object": "OBJECT", + "ObservableArray": "OBSERVABLEARRAY", + "octet": "OCTET", + "Promise": "PROMISE", + "required": "REQUIRED", + "sequence": "SEQUENCE", + "record": "RECORD", + "short": "SHORT", + "unsigned": "UNSIGNED", + "undefined": "UNDEFINED", + ":": "COLON", + ";": "SEMICOLON", + "{": "LBRACE", + "}": "RBRACE", + "(": "LPAREN", + ")": "RPAREN", + "[": "LBRACKET", + "]": "RBRACKET", + "?": "QUESTIONMARK", + "*": "ASTERISK", + ",": "COMMA", + "=": "EQUALS", + "<": "LT", + ">": "GT", + "ArrayBuffer": "ARRAYBUFFER", + "or": "OR", + "maplike": "MAPLIKE", + "setlike": "SETLIKE", + "iterable": "ITERABLE", + "namespace": "NAMESPACE", + "constructor": "CONSTRUCTOR", + "symbol": "SYMBOL", + "async": "ASYNC", + } + + tokens.extend(keywords.values()) + + def t_error(self, t): + raise WebIDLError( + "Unrecognized Input", + [ + Location( + lexer=self.lexer, + lineno=self.lexer.lineno, + lexpos=self.lexer.lexpos, + filename=self.filename, + ) + ], + ) + + def __init__(self, outputdir, lexer=None): + if lexer: + self.lexer = lexer + else: + self.lexer = lex.lex(object=self, reflags=re.DOTALL) + + +class SqueakyCleanLogger(object): + errorWhitelist = [ + # Web IDL defines the WHITESPACE token, but doesn't actually + # use it ... so far. + "Token 'WHITESPACE' defined, but not used", + # And that means we have an unused token + "There is 1 unused token", + # Web IDL defines a OtherOrComma rule that's only used in + # ExtendedAttributeInner, which we don't use yet. + "Rule 'OtherOrComma' defined, but not used", + # And an unused rule + "There is 1 unused rule", + # And the OtherOrComma grammar symbol is unreachable. + "Symbol 'OtherOrComma' is unreachable", + # Which means the Other symbol is unreachable. + "Symbol 'Other' is unreachable", + ] + + def __init__(self): + self.errors = [] + + def debug(self, msg, *args, **kwargs): + pass + + info = debug + + def warning(self, msg, *args, **kwargs): + if ( + msg == "%s:%d: Rule %r defined, but not used" + or msg == "%s:%d: Rule '%s' defined, but not used" + ): + # Munge things so we don't have to hardcode filenames and + # line numbers in our whitelist. + whitelistmsg = "Rule %r defined, but not used" + whitelistargs = args[2:] + else: + whitelistmsg = msg + whitelistargs = args + if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist: + self.errors.append(msg % args) + + error = warning + + def reportGrammarErrors(self): + if self.errors: + raise WebIDLError("\n".join(self.errors), []) + + +class Parser(Tokenizer): + def getLocation(self, p, i): + return Location(self.lexer, p.lineno(i), p.lexpos(i), self._filename) + + def globalScope(self): + return self._globalScope + + # The p_Foo functions here must match the WebIDL spec's grammar. + # It's acceptable to split things at '|' boundaries. + def p_Definitions(self, p): + """ + Definitions : ExtendedAttributeList Definition Definitions + """ + if p[2]: + p[0] = [p[2]] + p[2].addExtendedAttributes(p[1]) + else: + assert not p[1] + p[0] = [] + + p[0].extend(p[3]) + + def p_DefinitionsEmpty(self, p): + """ + Definitions : + """ + p[0] = [] + + def p_Definition(self, p): + """ + Definition : CallbackOrInterfaceOrMixin + | Namespace + | Partial + | Dictionary + | Exception + | Enum + | Typedef + | IncludesStatement + """ + p[0] = p[1] + assert p[1] # We might not have implemented something ... + + def p_CallbackOrInterfaceOrMixinCallback(self, p): + """ + CallbackOrInterfaceOrMixin : CALLBACK CallbackRestOrInterface + """ + if p[2].isInterface(): + assert isinstance(p[2], IDLInterface) + p[2].setCallback(True) + + p[0] = p[2] + + def p_CallbackOrInterfaceOrMixinInterfaceOrMixin(self, p): + """ + CallbackOrInterfaceOrMixin : INTERFACE InterfaceOrMixin + """ + p[0] = p[2] + + def p_CallbackRestOrInterface(self, p): + """ + CallbackRestOrInterface : CallbackRest + | CallbackConstructorRest + | CallbackInterface + """ + assert p[1] + p[0] = p[1] + + def handleNonPartialObject( + self, location, identifier, constructor, constructorArgs, nonPartialArgs + ): + """ + This handles non-partial objects (interfaces, namespaces and + dictionaries) by checking for an existing partial object, and promoting + it to non-partial as needed. The return value is the non-partial + object. + + constructorArgs are all the args for the constructor except the last + one: isKnownNonPartial. + + nonPartialArgs are the args for the setNonPartial call. + """ + # The name of the class starts with "IDL", so strip that off. + # Also, starts with a capital letter after that, so nix that + # as well. + prettyname = constructor.__name__[3:].lower() + + try: + existingObj = self.globalScope()._lookupIdentifier(identifier) + if existingObj: + if not isinstance(existingObj, constructor): + raise WebIDLError( + "%s has the same name as " + "non-%s object" % (prettyname.capitalize(), prettyname), + [location, existingObj.location], + ) + existingObj.setNonPartial(*nonPartialArgs) + return existingObj + except Exception as ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + # True for isKnownNonPartial + return constructor(*(constructorArgs + [True])) + + def p_InterfaceOrMixin(self, p): + """ + InterfaceOrMixin : InterfaceRest + | MixinRest + """ + p[0] = p[1] + + def p_CallbackInterface(self, p): + """ + CallbackInterface : INTERFACE InterfaceRest + """ + p[0] = p[2] + + def p_InterfaceRest(self, p): + """ + InterfaceRest : IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(location, p[1]) + members = p[4] + parent = p[2] + + p[0] = self.handleNonPartialObject( + location, + identifier, + IDLInterface, + [location, self.globalScope(), identifier, parent, members], + [location, parent, members], + ) + + def p_InterfaceForwardDecl(self, p): + """ + InterfaceRest : IDENTIFIER SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(location, p[1]) + + try: + if self.globalScope()._lookupIdentifier(identifier): + p[0] = self.globalScope()._lookupIdentifier(identifier) + if not isinstance(p[0], IDLExternalInterface): + raise WebIDLError( + "Name collision between external " + "interface declaration for identifier " + "%s and %s" % (identifier.name, p[0]), + [location, p[0].location], + ) + return + except Exception as ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + p[0] = IDLExternalInterface(location, self.globalScope(), identifier) + + def p_MixinRest(self, p): + """ + MixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handleNonPartialObject( + location, + identifier, + IDLInterfaceMixin, + [location, self.globalScope(), identifier, members], + [location, members], + ) + + def p_Namespace(self, p): + """ + Namespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handleNonPartialObject( + location, + identifier, + IDLNamespace, + [location, self.globalScope(), identifier, members], + [location, None, members], + ) + + def p_Partial(self, p): + """ + Partial : PARTIAL PartialDefinition + """ + p[0] = p[2] + + def p_PartialDefinitionInterface(self, p): + """ + PartialDefinition : INTERFACE PartialInterfaceOrPartialMixin + """ + p[0] = p[2] + + def p_PartialDefinition(self, p): + """ + PartialDefinition : PartialNamespace + | PartialDictionary + """ + p[0] = p[1] + + def handlePartialObject( + self, + location, + identifier, + nonPartialConstructor, + nonPartialConstructorArgs, + partialConstructorArgs, + ): + """ + This handles partial objects (interfaces, namespaces and dictionaries) + by checking for an existing non-partial object, and adding ourselves to + it as needed. The return value is our partial object. We use + IDLPartialInterfaceOrNamespace for partial interfaces or namespaces, + and IDLPartialDictionary for partial dictionaries. + + nonPartialConstructorArgs are all the args for the non-partial + constructor except the last two: members and isKnownNonPartial. + + partialConstructorArgs are the arguments for the partial object + constructor, except the last one (the non-partial object). + """ + # The name of the class starts with "IDL", so strip that off. + # Also, starts with a capital letter after that, so nix that + # as well. + prettyname = nonPartialConstructor.__name__[3:].lower() + + nonPartialObject = None + try: + nonPartialObject = self.globalScope()._lookupIdentifier(identifier) + if nonPartialObject: + if not isinstance(nonPartialObject, nonPartialConstructor): + raise WebIDLError( + "Partial %s has the same name as " + "non-%s object" % (prettyname, prettyname), + [location, nonPartialObject.location], + ) + except Exception as ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + if not nonPartialObject: + nonPartialObject = nonPartialConstructor( + # No members, False for isKnownNonPartial + *(nonPartialConstructorArgs), + members=[], + isKnownNonPartial=False + ) + + partialObject = None + if isinstance(nonPartialObject, IDLDictionary): + partialObject = IDLPartialDictionary( + *(partialConstructorArgs + [nonPartialObject]) + ) + elif isinstance( + nonPartialObject, (IDLInterface, IDLInterfaceMixin, IDLNamespace) + ): + partialObject = IDLPartialInterfaceOrNamespace( + *(partialConstructorArgs + [nonPartialObject]) + ) + else: + raise WebIDLError( + "Unknown partial object type %s" % type(partialObject), [location] + ) + + return partialObject + + def p_PartialInterfaceOrPartialMixin(self, p): + """ + PartialInterfaceOrPartialMixin : PartialInterfaceRest + | PartialMixinRest + """ + p[0] = p[1] + + def p_PartialInterfaceRest(self, p): + """ + PartialInterfaceRest : IDENTIFIER LBRACE PartialInterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(location, p[1]) + members = p[3] + + p[0] = self.handlePartialObject( + location, + identifier, + IDLInterface, + [location, self.globalScope(), identifier, None], + [location, identifier, members], + ) + + def p_PartialMixinRest(self, p): + """ + PartialMixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handlePartialObject( + location, + identifier, + IDLInterfaceMixin, + [location, self.globalScope(), identifier], + [location, identifier, members], + ) + + def p_PartialNamespace(self, p): + """ + PartialNamespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handlePartialObject( + location, + identifier, + IDLNamespace, + [location, self.globalScope(), identifier], + [location, identifier, members], + ) + + def p_PartialDictionary(self, p): + """ + PartialDictionary : DICTIONARY IDENTIFIER LBRACE DictionaryMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handlePartialObject( + location, + identifier, + IDLDictionary, + [location, self.globalScope(), identifier], + [location, identifier, members], + ) + + def p_Inheritance(self, p): + """ + Inheritance : COLON ScopedName + """ + p[0] = IDLIdentifierPlaceholder(self.getLocation(p, 2), p[2]) + + def p_InheritanceEmpty(self, p): + """ + Inheritance : + """ + pass + + def p_InterfaceMembers(self, p): + """ + InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers + """ + p[0] = [p[2]] + + assert not p[1] or p[2] + p[2].addExtendedAttributes(p[1]) + + p[0].extend(p[3]) + + def p_InterfaceMembersEmpty(self, p): + """ + InterfaceMembers : + """ + p[0] = [] + + def p_InterfaceMember(self, p): + """ + InterfaceMember : PartialInterfaceMember + | Constructor + """ + p[0] = p[1] + + def p_Constructor(self, p): + """ + Constructor : CONSTRUCTOR LPAREN ArgumentList RPAREN SEMICOLON + """ + p[0] = IDLConstructor(self.getLocation(p, 1), p[3], "constructor") + + def p_PartialInterfaceMembers(self, p): + """ + PartialInterfaceMembers : ExtendedAttributeList PartialInterfaceMember PartialInterfaceMembers + """ + p[0] = [p[2]] + + assert not p[1] or p[2] + p[2].addExtendedAttributes(p[1]) + + p[0].extend(p[3]) + + def p_PartialInterfaceMembersEmpty(self, p): + """ + PartialInterfaceMembers : + """ + p[0] = [] + + def p_PartialInterfaceMember(self, p): + """ + PartialInterfaceMember : Const + | AttributeOrOperationOrMaplikeOrSetlikeOrIterable + """ + p[0] = p[1] + + def p_MixinMembersEmpty(self, p): + """ + MixinMembers : + """ + p[0] = [] + + def p_MixinMembers(self, p): + """ + MixinMembers : ExtendedAttributeList MixinMember MixinMembers + """ + p[0] = [p[2]] + + assert not p[1] or p[2] + p[2].addExtendedAttributes(p[1]) + + p[0].extend(p[3]) + + def p_MixinMember(self, p): + """ + MixinMember : Const + | Attribute + | Operation + """ + p[0] = p[1] + + def p_Dictionary(self, p): + """ + Dictionary : DICTIONARY IDENTIFIER Inheritance LBRACE DictionaryMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[5] + p[0] = IDLDictionary(location, self.globalScope(), identifier, p[3], members) + + def p_DictionaryMembers(self, p): + """ + DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers + | + """ + if len(p) == 1: + # We're at the end of the list + p[0] = [] + return + p[2].addExtendedAttributes(p[1]) + p[0] = [p[2]] + p[0].extend(p[3]) + + def p_DictionaryMemberRequired(self, p): + """ + DictionaryMember : REQUIRED TypeWithExtendedAttributes IDENTIFIER SEMICOLON + """ + # These quack a lot like required arguments, so just treat them that way. + t = p[2] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) + + p[0] = IDLArgument( + self.getLocation(p, 3), + identifier, + t, + optional=False, + defaultValue=None, + variadic=False, + dictionaryMember=True, + ) + + def p_DictionaryMember(self, p): + """ + DictionaryMember : Type IDENTIFIER Default SEMICOLON + """ + # These quack a lot like optional arguments, so just treat them that way. + t = p[1] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + defaultValue = p[3] + + # Any attributes that precede this may apply to the type, so + # we configure the argument to forward type attributes down instead of producing + # a parse error + p[0] = IDLArgument( + self.getLocation(p, 2), + identifier, + t, + optional=True, + defaultValue=defaultValue, + variadic=False, + dictionaryMember=True, + allowTypeAttributes=True, + ) + + def p_Default(self, p): + """ + Default : EQUALS DefaultValue + | + """ + if len(p) > 1: + p[0] = p[2] + else: + p[0] = None + + def p_DefaultValue(self, p): + """ + DefaultValue : ConstValue + | LBRACKET RBRACKET + | LBRACE RBRACE + """ + if len(p) == 2: + p[0] = p[1] + else: + assert len(p) == 3 # Must be [] or {} + if p[1] == "[": + p[0] = IDLEmptySequenceValue(self.getLocation(p, 1)) + else: + assert p[1] == "{" + p[0] = IDLDefaultDictionaryValue(self.getLocation(p, 1)) + + def p_DefaultValueNull(self, p): + """ + DefaultValue : NULL + """ + p[0] = IDLNullValue(self.getLocation(p, 1)) + + def p_DefaultValueUndefined(self, p): + """ + DefaultValue : UNDEFINED + """ + p[0] = IDLUndefinedValue(self.getLocation(p, 1)) + + def p_Exception(self, p): + """ + Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON + """ + pass + + def p_Enum(self, p): + """ + Enum : ENUM IDENTIFIER LBRACE EnumValueList RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + + values = p[4] + assert values + p[0] = IDLEnum(location, self.globalScope(), identifier, values) + + def p_EnumValueList(self, p): + """ + EnumValueList : STRING EnumValueListComma + """ + p[0] = [p[1]] + p[0].extend(p[2]) + + def p_EnumValueListComma(self, p): + """ + EnumValueListComma : COMMA EnumValueListString + """ + p[0] = p[2] + + def p_EnumValueListCommaEmpty(self, p): + """ + EnumValueListComma : + """ + p[0] = [] + + def p_EnumValueListString(self, p): + """ + EnumValueListString : STRING EnumValueListComma + """ + p[0] = [p[1]] + p[0].extend(p[2]) + + def p_EnumValueListStringEmpty(self, p): + """ + EnumValueListString : + """ + p[0] = [] + + def p_CallbackRest(self, p): + """ + CallbackRest : IDENTIFIER EQUALS Type LPAREN ArgumentList RPAREN SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + p[0] = IDLCallback( + self.getLocation(p, 1), + self.globalScope(), + identifier, + p[3], + p[5], + isConstructor=False, + ) + + def p_CallbackConstructorRest(self, p): + """ + CallbackConstructorRest : CONSTRUCTOR IDENTIFIER EQUALS Type LPAREN ArgumentList RPAREN SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + p[0] = IDLCallback( + self.getLocation(p, 2), + self.globalScope(), + identifier, + p[4], + p[6], + isConstructor=True, + ) + + def p_ExceptionMembers(self, p): + """ + ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers + | + """ + pass + + def p_Typedef(self, p): + """ + Typedef : TYPEDEF TypeWithExtendedAttributes IDENTIFIER SEMICOLON + """ + typedef = IDLTypedef(self.getLocation(p, 1), self.globalScope(), p[2], p[3]) + p[0] = typedef + + def p_IncludesStatement(self, p): + """ + IncludesStatement : ScopedName INCLUDES ScopedName SEMICOLON + """ + assert p[2] == "includes" + interface = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1]) + mixin = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3]) + p[0] = IDLIncludesStatement(self.getLocation(p, 1), interface, mixin) + + def p_Const(self, p): + """ + Const : CONST ConstType IDENTIFIER EQUALS ConstValue SEMICOLON + """ + location = self.getLocation(p, 1) + type = p[2] + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) + value = p[5] + p[0] = IDLConst(location, identifier, type, value) + + def p_ConstValueBoolean(self, p): + """ + ConstValue : BooleanLiteral + """ + location = self.getLocation(p, 1) + booleanType = BuiltinTypes[IDLBuiltinType.Types.boolean] + p[0] = IDLValue(location, booleanType, p[1]) + + def p_ConstValueInteger(self, p): + """ + ConstValue : INTEGER + """ + location = self.getLocation(p, 1) + + # We don't know ahead of time what type the integer literal is. + # Determine the smallest type it could possibly fit in and use that. + integerType = matchIntegerValueToType(p[1]) + if integerType is None: + raise WebIDLError("Integer literal out of range", [location]) + + p[0] = IDLValue(location, integerType, p[1]) + + def p_ConstValueFloat(self, p): + """ + ConstValue : FLOATLITERAL + """ + location = self.getLocation(p, 1) + p[0] = IDLValue( + location, BuiltinTypes[IDLBuiltinType.Types.unrestricted_float], p[1] + ) + + def p_ConstValueString(self, p): + """ + ConstValue : STRING + """ + location = self.getLocation(p, 1) + stringType = BuiltinTypes[IDLBuiltinType.Types.domstring] + p[0] = IDLValue(location, stringType, p[1]) + + def p_BooleanLiteralTrue(self, p): + """ + BooleanLiteral : TRUE + """ + p[0] = True + + def p_BooleanLiteralFalse(self, p): + """ + BooleanLiteral : FALSE + """ + p[0] = False + + def p_AttributeOrOperationOrMaplikeOrSetlikeOrIterable(self, p): + """ + AttributeOrOperationOrMaplikeOrSetlikeOrIterable : Attribute + | Maplike + | Setlike + | Iterable + | AsyncIterable + | Operation + """ + p[0] = p[1] + + def p_Iterable(self, p): + """ + Iterable : ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON + | ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON + """ + location = self.getLocation(p, 2) + identifier = IDLUnresolvedIdentifier( + location, "__iterable", allowDoubleUnderscore=True + ) + if len(p) > 6: + keyType = p[3] + valueType = p[5] + else: + keyType = None + valueType = p[3] + + p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope()) + + def p_AsyncIterable(self, p): + """ + AsyncIterable : ASYNC ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON + | ASYNC ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON + | ASYNC ITERABLE LT TypeWithExtendedAttributes GT LPAREN ArgumentList RPAREN SEMICOLON + | ASYNC ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT LPAREN ArgumentList RPAREN SEMICOLON + """ + location = self.getLocation(p, 2) + identifier = IDLUnresolvedIdentifier( + location, "__iterable", allowDoubleUnderscore=True + ) + if len(p) == 12: + keyType = p[4] + valueType = p[6] + argList = p[9] + elif len(p) == 10: + keyType = None + valueType = p[4] + argList = p[7] + elif len(p) == 9: + keyType = p[4] + valueType = p[6] + argList = [] + else: + keyType = None + valueType = p[4] + argList = [] + + p[0] = IDLAsyncIterable( + location, identifier, keyType, valueType, argList, self.globalScope() + ) + + def p_Setlike(self, p): + """ + Setlike : ReadOnly SETLIKE LT TypeWithExtendedAttributes GT SEMICOLON + """ + readonly = p[1] + maplikeOrSetlikeType = p[2] + location = self.getLocation(p, 2) + identifier = IDLUnresolvedIdentifier( + location, "__setlike", allowDoubleUnderscore=True + ) + keyType = p[4] + valueType = keyType + p[0] = IDLMaplikeOrSetlike( + location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType + ) + + def p_Maplike(self, p): + """ + Maplike : ReadOnly MAPLIKE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON + """ + readonly = p[1] + maplikeOrSetlikeType = p[2] + location = self.getLocation(p, 2) + identifier = IDLUnresolvedIdentifier( + location, "__maplike", allowDoubleUnderscore=True + ) + keyType = p[4] + valueType = p[6] + p[0] = IDLMaplikeOrSetlike( + location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType + ) + + def p_AttributeWithQualifier(self, p): + """ + Attribute : Qualifier AttributeRest + """ + static = IDLInterfaceMember.Special.Static in p[1] + stringifier = IDLInterfaceMember.Special.Stringifier in p[1] + (location, identifier, type, readonly) = p[2] + p[0] = IDLAttribute( + location, identifier, type, readonly, static=static, stringifier=stringifier + ) + + def p_AttributeInherited(self, p): + """ + Attribute : INHERIT AttributeRest + """ + (location, identifier, type, readonly) = p[2] + p[0] = IDLAttribute(location, identifier, type, readonly, inherit=True) + + def p_Attribute(self, p): + """ + Attribute : AttributeRest + """ + (location, identifier, type, readonly) = p[1] + p[0] = IDLAttribute(location, identifier, type, readonly, inherit=False) + + def p_AttributeRest(self, p): + """ + AttributeRest : ReadOnly ATTRIBUTE TypeWithExtendedAttributes AttributeName SEMICOLON + """ + location = self.getLocation(p, 2) + readonly = p[1] + t = p[3] + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 4), p[4]) + p[0] = (location, identifier, t, readonly) + + def p_ReadOnly(self, p): + """ + ReadOnly : READONLY + """ + p[0] = True + + def p_ReadOnlyEmpty(self, p): + """ + ReadOnly : + """ + p[0] = False + + def p_Operation(self, p): + """ + Operation : Qualifiers OperationRest + """ + qualifiers = p[1] + + # Disallow duplicates in the qualifier set + if not len(set(qualifiers)) == len(qualifiers): + raise WebIDLError( + "Duplicate qualifiers are not allowed", [self.getLocation(p, 1)] + ) + + static = IDLInterfaceMember.Special.Static in p[1] + # If static is there that's all that's allowed. This is disallowed + # by the parser, so we can assert here. + assert not static or len(qualifiers) == 1 + + stringifier = IDLInterfaceMember.Special.Stringifier in p[1] + # If stringifier is there that's all that's allowed. This is disallowed + # by the parser, so we can assert here. + assert not stringifier or len(qualifiers) == 1 + + getter = True if IDLMethod.Special.Getter in p[1] else False + setter = True if IDLMethod.Special.Setter in p[1] else False + deleter = True if IDLMethod.Special.Deleter in p[1] else False + legacycaller = True if IDLMethod.Special.LegacyCaller in p[1] else False + + if getter or deleter: + if setter: + raise WebIDLError( + "getter and deleter are incompatible with setter", + [self.getLocation(p, 1)], + ) + + (returnType, identifier, arguments) = p[2] + + assert isinstance(returnType, IDLType) + + specialType = IDLMethod.NamedOrIndexed.Neither + + if getter or deleter: + if len(arguments) != 1: + raise WebIDLError( + "%s has wrong number of arguments" + % ("getter" if getter else "deleter"), + [self.getLocation(p, 2)], + ) + argType = arguments[0].type + if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: + specialType = IDLMethod.NamedOrIndexed.Named + elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: + specialType = IDLMethod.NamedOrIndexed.Indexed + if deleter: + raise WebIDLError( + "There is no such thing as an indexed deleter.", + [self.getLocation(p, 1)], + ) + else: + raise WebIDLError( + "%s has wrong argument type (must be DOMString or UnsignedLong)" + % ("getter" if getter else "deleter"), + [arguments[0].location], + ) + if arguments[0].optional or arguments[0].variadic: + raise WebIDLError( + "%s cannot have %s argument" + % ( + "getter" if getter else "deleter", + "optional" if arguments[0].optional else "variadic", + ), + [arguments[0].location], + ) + if getter: + if returnType.isUndefined(): + raise WebIDLError( + "getter cannot have undefined return type", [self.getLocation(p, 2)] + ) + if setter: + if len(arguments) != 2: + raise WebIDLError( + "setter has wrong number of arguments", [self.getLocation(p, 2)] + ) + argType = arguments[0].type + if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: + specialType = IDLMethod.NamedOrIndexed.Named + elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: + specialType = IDLMethod.NamedOrIndexed.Indexed + else: + raise WebIDLError( + "settter has wrong argument type (must be DOMString or UnsignedLong)", + [arguments[0].location], + ) + if arguments[0].optional or arguments[0].variadic: + raise WebIDLError( + "setter cannot have %s argument" + % ("optional" if arguments[0].optional else "variadic"), + [arguments[0].location], + ) + if arguments[1].optional or arguments[1].variadic: + raise WebIDLError( + "setter cannot have %s argument" + % ("optional" if arguments[1].optional else "variadic"), + [arguments[1].location], + ) + + if stringifier: + if len(arguments) != 0: + raise WebIDLError( + "stringifier has wrong number of arguments", + [self.getLocation(p, 2)], + ) + if not returnType.isDOMString(): + raise WebIDLError( + "stringifier must have DOMString return type", + [self.getLocation(p, 2)], + ) + + # identifier might be None. This is only permitted for special methods. + if not identifier: + if ( + not getter + and not setter + and not deleter + and not legacycaller + and not stringifier + ): + raise WebIDLError( + "Identifier required for non-special methods", + [self.getLocation(p, 2)], + ) + + location = BuiltinLocation("<auto-generated-identifier>") + identifier = IDLUnresolvedIdentifier( + location, + "__%s%s%s%s%s%s" + % ( + "named" + if specialType == IDLMethod.NamedOrIndexed.Named + else "indexed" + if specialType == IDLMethod.NamedOrIndexed.Indexed + else "", + "getter" if getter else "", + "setter" if setter else "", + "deleter" if deleter else "", + "legacycaller" if legacycaller else "", + "stringifier" if stringifier else "", + ), + allowDoubleUnderscore=True, + ) + + method = IDLMethod( + self.getLocation(p, 2), + identifier, + returnType, + arguments, + static=static, + getter=getter, + setter=setter, + deleter=deleter, + specialType=specialType, + legacycaller=legacycaller, + stringifier=stringifier, + ) + p[0] = method + + def p_Stringifier(self, p): + """ + Operation : STRINGIFIER SEMICOLON + """ + identifier = IDLUnresolvedIdentifier( + BuiltinLocation("<auto-generated-identifier>"), + "__stringifier", + allowDoubleUnderscore=True, + ) + method = IDLMethod( + self.getLocation(p, 1), + identifier, + returnType=BuiltinTypes[IDLBuiltinType.Types.domstring], + arguments=[], + stringifier=True, + ) + p[0] = method + + def p_QualifierStatic(self, p): + """ + Qualifier : STATIC + """ + p[0] = [IDLInterfaceMember.Special.Static] + + def p_QualifierStringifier(self, p): + """ + Qualifier : STRINGIFIER + """ + p[0] = [IDLInterfaceMember.Special.Stringifier] + + def p_Qualifiers(self, p): + """ + Qualifiers : Qualifier + | Specials + """ + p[0] = p[1] + + def p_Specials(self, p): + """ + Specials : Special Specials + """ + p[0] = [p[1]] + p[0].extend(p[2]) + + def p_SpecialsEmpty(self, p): + """ + Specials : + """ + p[0] = [] + + def p_SpecialGetter(self, p): + """ + Special : GETTER + """ + p[0] = IDLMethod.Special.Getter + + def p_SpecialSetter(self, p): + """ + Special : SETTER + """ + p[0] = IDLMethod.Special.Setter + + def p_SpecialDeleter(self, p): + """ + Special : DELETER + """ + p[0] = IDLMethod.Special.Deleter + + def p_SpecialLegacyCaller(self, p): + """ + Special : LEGACYCALLER + """ + p[0] = IDLMethod.Special.LegacyCaller + + def p_OperationRest(self, p): + """ + OperationRest : Type OptionalIdentifier LPAREN ArgumentList RPAREN SEMICOLON + """ + p[0] = (p[1], p[2], p[4]) + + def p_OptionalIdentifier(self, p): + """ + OptionalIdentifier : IDENTIFIER + """ + p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + def p_OptionalIdentifierEmpty(self, p): + """ + OptionalIdentifier : + """ + pass + + def p_ArgumentList(self, p): + """ + ArgumentList : Argument Arguments + """ + p[0] = [p[1]] if p[1] else [] + p[0].extend(p[2]) + + def p_ArgumentListEmpty(self, p): + """ + ArgumentList : + """ + p[0] = [] + + def p_Arguments(self, p): + """ + Arguments : COMMA Argument Arguments + """ + p[0] = [p[2]] if p[2] else [] + p[0].extend(p[3]) + + def p_ArgumentsEmpty(self, p): + """ + Arguments : + """ + p[0] = [] + + def p_Argument(self, p): + """ + Argument : ExtendedAttributeList ArgumentRest + """ + p[0] = p[2] + p[0].addExtendedAttributes(p[1]) + + def p_ArgumentRestOptional(self, p): + """ + ArgumentRest : OPTIONAL TypeWithExtendedAttributes ArgumentName Default + """ + t = p[2] + assert isinstance(t, IDLType) + # Arg names can be reserved identifiers + identifier = IDLUnresolvedIdentifier( + self.getLocation(p, 3), p[3], allowForbidden=True + ) + + defaultValue = p[4] + + # We can't test t.isAny() here and give it a default value as needed, + # since at this point t is not a fully resolved type yet (e.g. it might + # be a typedef). We'll handle the 'any' case in IDLArgument.complete. + + p[0] = IDLArgument( + self.getLocation(p, 3), identifier, t, True, defaultValue, False + ) + + def p_ArgumentRest(self, p): + """ + ArgumentRest : Type Ellipsis ArgumentName + """ + t = p[1] + assert isinstance(t, IDLType) + # Arg names can be reserved identifiers + identifier = IDLUnresolvedIdentifier( + self.getLocation(p, 3), p[3], allowForbidden=True + ) + + variadic = p[2] + + # We can't test t.isAny() here and give it a default value as needed, + # since at this point t is not a fully resolved type yet (e.g. it might + # be a typedef). We'll handle the 'any' case in IDLArgument.complete. + + # variadic implies optional + # Any attributes that precede this may apply to the type, so + # we configure the argument to forward type attributes down instead of producing + # a parse error + p[0] = IDLArgument( + self.getLocation(p, 3), + identifier, + t, + variadic, + None, + variadic, + allowTypeAttributes=True, + ) + + def p_ArgumentName(self, p): + """ + ArgumentName : IDENTIFIER + | ArgumentNameKeyword + """ + p[0] = p[1] + + def p_ArgumentNameKeyword(self, p): + """ + ArgumentNameKeyword : ASYNC + | ATTRIBUTE + | CALLBACK + | CONST + | CONSTRUCTOR + | DELETER + | DICTIONARY + | ENUM + | EXCEPTION + | GETTER + | INCLUDES + | INHERIT + | INTERFACE + | ITERABLE + | LEGACYCALLER + | MAPLIKE + | MIXIN + | NAMESPACE + | PARTIAL + | READONLY + | REQUIRED + | SERIALIZER + | SETLIKE + | SETTER + | STATIC + | STRINGIFIER + | TYPEDEF + | UNRESTRICTED + """ + p[0] = p[1] + + def p_AttributeName(self, p): + """ + AttributeName : IDENTIFIER + | AttributeNameKeyword + """ + p[0] = p[1] + + def p_AttributeNameKeyword(self, p): + """ + AttributeNameKeyword : ASYNC + | REQUIRED + """ + p[0] = p[1] + + def p_Ellipsis(self, p): + """ + Ellipsis : ELLIPSIS + """ + p[0] = True + + def p_EllipsisEmpty(self, p): + """ + Ellipsis : + """ + p[0] = False + + def p_ExceptionMember(self, p): + """ + ExceptionMember : Const + | ExceptionField + """ + pass + + def p_ExceptionField(self, p): + """ + ExceptionField : Type IDENTIFIER SEMICOLON + """ + pass + + def p_ExtendedAttributeList(self, p): + """ + ExtendedAttributeList : LBRACKET ExtendedAttribute ExtendedAttributes RBRACKET + """ + p[0] = [p[2]] + if p[3]: + p[0].extend(p[3]) + + def p_ExtendedAttributeListEmpty(self, p): + """ + ExtendedAttributeList : + """ + p[0] = [] + + def p_ExtendedAttribute(self, p): + """ + ExtendedAttribute : ExtendedAttributeNoArgs + | ExtendedAttributeArgList + | ExtendedAttributeIdent + | ExtendedAttributeWildcard + | ExtendedAttributeNamedArgList + | ExtendedAttributeIdentList + """ + p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1]) + + def p_ExtendedAttributeEmpty(self, p): + """ + ExtendedAttribute : + """ + pass + + def p_ExtendedAttributes(self, p): + """ + ExtendedAttributes : COMMA ExtendedAttribute ExtendedAttributes + """ + p[0] = [p[2]] if p[2] else [] + p[0].extend(p[3]) + + def p_ExtendedAttributesEmpty(self, p): + """ + ExtendedAttributes : + """ + p[0] = [] + + def p_Other(self, p): + """ + Other : INTEGER + | FLOATLITERAL + | IDENTIFIER + | STRING + | OTHER + | ELLIPSIS + | COLON + | SCOPE + | SEMICOLON + | LT + | EQUALS + | GT + | QUESTIONMARK + | ASTERISK + | DOMSTRING + | BYTESTRING + | USVSTRING + | UTF8STRING + | JSSTRING + | PROMISE + | ANY + | BOOLEAN + | BYTE + | DOUBLE + | FALSE + | FLOAT + | LONG + | NULL + | OBJECT + | OCTET + | OR + | OPTIONAL + | RECORD + | SEQUENCE + | SHORT + | SYMBOL + | TRUE + | UNSIGNED + | UNDEFINED + | ArgumentNameKeyword + """ + pass + + def p_OtherOrComma(self, p): + """ + OtherOrComma : Other + | COMMA + """ + pass + + def p_TypeSingleType(self, p): + """ + Type : SingleType + """ + p[0] = p[1] + + def p_TypeUnionType(self, p): + """ + Type : UnionType Null + """ + p[0] = self.handleNullable(p[1], p[2]) + + def p_TypeWithExtendedAttributes(self, p): + """ + TypeWithExtendedAttributes : ExtendedAttributeList Type + """ + p[0] = p[2].withExtendedAttributes(p[1]) + + def p_SingleTypeDistinguishableType(self, p): + """ + SingleType : DistinguishableType + """ + p[0] = p[1] + + def p_SingleTypeAnyType(self, p): + """ + SingleType : ANY + """ + p[0] = BuiltinTypes[IDLBuiltinType.Types.any] + + def p_SingleTypePromiseType(self, p): + """ + SingleType : PROMISE LT Type GT + """ + p[0] = IDLPromiseType(self.getLocation(p, 1), p[3]) + + def p_UnionType(self, p): + """ + UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN + """ + types = [p[2], p[4]] + types.extend(p[5]) + p[0] = IDLUnionType(self.getLocation(p, 1), types) + + def p_UnionMemberTypeDistinguishableType(self, p): + """ + UnionMemberType : ExtendedAttributeList DistinguishableType + """ + p[0] = p[2].withExtendedAttributes(p[1]) + + def p_UnionMemberType(self, p): + """ + UnionMemberType : UnionType Null + """ + p[0] = self.handleNullable(p[1], p[2]) + + def p_UnionMemberTypes(self, p): + """ + UnionMemberTypes : OR UnionMemberType UnionMemberTypes + """ + p[0] = [p[2]] + p[0].extend(p[3]) + + def p_UnionMemberTypesEmpty(self, p): + """ + UnionMemberTypes : + """ + p[0] = [] + + def p_DistinguishableType(self, p): + """ + DistinguishableType : PrimitiveType Null + | ARRAYBUFFER Null + | OBJECT Null + | UNDEFINED Null + """ + if p[1] == "object": + type = BuiltinTypes[IDLBuiltinType.Types.object] + elif p[1] == "ArrayBuffer": + type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer] + elif p[1] == "undefined": + type = BuiltinTypes[IDLBuiltinType.Types.undefined] + else: + type = BuiltinTypes[p[1]] + + p[0] = self.handleNullable(type, p[2]) + + def p_DistinguishableTypeStringType(self, p): + """ + DistinguishableType : StringType Null + """ + p[0] = self.handleNullable(p[1], p[2]) + + def p_DistinguishableTypeSequenceType(self, p): + """ + DistinguishableType : SEQUENCE LT TypeWithExtendedAttributes GT Null + """ + innerType = p[3] + type = IDLSequenceType(self.getLocation(p, 1), innerType) + p[0] = self.handleNullable(type, p[5]) + + def p_DistinguishableTypeRecordType(self, p): + """ + DistinguishableType : RECORD LT StringType COMMA TypeWithExtendedAttributes GT Null + """ + keyType = p[3] + valueType = p[5] + type = IDLRecordType(self.getLocation(p, 1), keyType, valueType) + p[0] = self.handleNullable(type, p[7]) + + def p_DistinguishableTypeObservableArrayType(self, p): + """ + DistinguishableType : OBSERVABLEARRAY LT TypeWithExtendedAttributes GT Null + """ + innerType = p[3] + type = IDLObservableArrayType(self.getLocation(p, 1), innerType) + p[0] = self.handleNullable(type, p[5]) + + def p_DistinguishableTypeScopedName(self, p): + """ + DistinguishableType : ScopedName Null + """ + assert isinstance(p[1], IDLUnresolvedIdentifier) + + if p[1].name == "Promise": + raise WebIDLError( + "Promise used without saying what it's " "parametrized over", + [self.getLocation(p, 1)], + ) + + type = None + + try: + if self.globalScope()._lookupIdentifier(p[1]): + obj = self.globalScope()._lookupIdentifier(p[1]) + assert not obj.isType() + if obj.isTypedef(): + type = IDLTypedefType( + self.getLocation(p, 1), obj.innerType, obj.identifier.name + ) + elif obj.isCallback() and not obj.isInterface(): + type = IDLCallbackType(self.getLocation(p, 1), obj) + else: + type = IDLWrapperType(self.getLocation(p, 1), p[1]) + p[0] = self.handleNullable(type, p[2]) + return + except Exception: + pass + + type = IDLUnresolvedType(self.getLocation(p, 1), p[1]) + p[0] = self.handleNullable(type, p[2]) + + def p_ConstType(self, p): + """ + ConstType : PrimitiveType + """ + p[0] = BuiltinTypes[p[1]] + + def p_ConstTypeIdentifier(self, p): + """ + ConstType : IDENTIFIER + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + p[0] = IDLUnresolvedType(self.getLocation(p, 1), identifier) + + def p_PrimitiveTypeUint(self, p): + """ + PrimitiveType : UnsignedIntegerType + """ + p[0] = p[1] + + def p_PrimitiveTypeBoolean(self, p): + """ + PrimitiveType : BOOLEAN + """ + p[0] = IDLBuiltinType.Types.boolean + + def p_PrimitiveTypeByte(self, p): + """ + PrimitiveType : BYTE + """ + p[0] = IDLBuiltinType.Types.byte + + def p_PrimitiveTypeOctet(self, p): + """ + PrimitiveType : OCTET + """ + p[0] = IDLBuiltinType.Types.octet + + def p_PrimitiveTypeFloat(self, p): + """ + PrimitiveType : FLOAT + """ + p[0] = IDLBuiltinType.Types.float + + def p_PrimitiveTypeUnrestictedFloat(self, p): + """ + PrimitiveType : UNRESTRICTED FLOAT + """ + p[0] = IDLBuiltinType.Types.unrestricted_float + + def p_PrimitiveTypeDouble(self, p): + """ + PrimitiveType : DOUBLE + """ + p[0] = IDLBuiltinType.Types.double + + def p_PrimitiveTypeUnrestictedDouble(self, p): + """ + PrimitiveType : UNRESTRICTED DOUBLE + """ + p[0] = IDLBuiltinType.Types.unrestricted_double + + def p_StringType(self, p): + """ + StringType : BuiltinStringType + """ + p[0] = BuiltinTypes[p[1]] + + def p_BuiltinStringTypeDOMString(self, p): + """ + BuiltinStringType : DOMSTRING + """ + p[0] = IDLBuiltinType.Types.domstring + + def p_BuiltinStringTypeBytestring(self, p): + """ + BuiltinStringType : BYTESTRING + """ + p[0] = IDLBuiltinType.Types.bytestring + + def p_BuiltinStringTypeUSVString(self, p): + """ + BuiltinStringType : USVSTRING + """ + p[0] = IDLBuiltinType.Types.usvstring + + def p_BuiltinStringTypeUTF8String(self, p): + """ + BuiltinStringType : UTF8STRING + """ + p[0] = IDLBuiltinType.Types.utf8string + + def p_BuiltinStringTypeJSString(self, p): + """ + BuiltinStringType : JSSTRING + """ + p[0] = IDLBuiltinType.Types.jsstring + + def p_UnsignedIntegerTypeUnsigned(self, p): + """ + UnsignedIntegerType : UNSIGNED IntegerType + """ + # Adding one to a given signed integer type gets you the unsigned type: + p[0] = p[2] + 1 + + def p_UnsignedIntegerType(self, p): + """ + UnsignedIntegerType : IntegerType + """ + p[0] = p[1] + + def p_IntegerTypeShort(self, p): + """ + IntegerType : SHORT + """ + p[0] = IDLBuiltinType.Types.short + + def p_IntegerTypeLong(self, p): + """ + IntegerType : LONG OptionalLong + """ + if p[2]: + p[0] = IDLBuiltinType.Types.long_long + else: + p[0] = IDLBuiltinType.Types.long + + def p_OptionalLong(self, p): + """ + OptionalLong : LONG + """ + p[0] = True + + def p_OptionalLongEmpty(self, p): + """ + OptionalLong : + """ + p[0] = False + + def p_Null(self, p): + """ + Null : QUESTIONMARK + | + """ + if len(p) > 1: + p[0] = self.getLocation(p, 1) + else: + p[0] = None + + def p_ScopedName(self, p): + """ + ScopedName : AbsoluteScopedName + | RelativeScopedName + """ + p[0] = p[1] + + def p_AbsoluteScopedName(self, p): + """ + AbsoluteScopedName : SCOPE IDENTIFIER ScopedNameParts + """ + assert False + pass + + def p_RelativeScopedName(self, p): + """ + RelativeScopedName : IDENTIFIER ScopedNameParts + """ + assert not p[2] # Not implemented! + + p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + def p_ScopedNameParts(self, p): + """ + ScopedNameParts : SCOPE IDENTIFIER ScopedNameParts + """ + assert False + pass + + def p_ScopedNamePartsEmpty(self, p): + """ + ScopedNameParts : + """ + p[0] = None + + def p_ExtendedAttributeNoArgs(self, p): + """ + ExtendedAttributeNoArgs : IDENTIFIER + """ + p[0] = (p[1],) + + def p_ExtendedAttributeArgList(self, p): + """ + ExtendedAttributeArgList : IDENTIFIER LPAREN ArgumentList RPAREN + """ + p[0] = (p[1], p[3]) + + def p_ExtendedAttributeIdent(self, p): + """ + ExtendedAttributeIdent : IDENTIFIER EQUALS STRING + | IDENTIFIER EQUALS IDENTIFIER + """ + p[0] = (p[1], p[3]) + + def p_ExtendedAttributeWildcard(self, p): + """ + ExtendedAttributeWildcard : IDENTIFIER EQUALS ASTERISK + """ + p[0] = (p[1], p[3]) + + def p_ExtendedAttributeNamedArgList(self, p): + """ + ExtendedAttributeNamedArgList : IDENTIFIER EQUALS IDENTIFIER LPAREN ArgumentList RPAREN + """ + p[0] = (p[1], p[3], p[5]) + + def p_ExtendedAttributeIdentList(self, p): + """ + ExtendedAttributeIdentList : IDENTIFIER EQUALS LPAREN IdentifierList RPAREN + """ + p[0] = (p[1], p[4]) + + def p_IdentifierList(self, p): + """ + IdentifierList : IDENTIFIER Identifiers + """ + idents = list(p[2]) + # This is only used for identifier-list-valued extended attributes, and if + # we're going to restrict to IDENTIFIER here we should at least allow + # escaping with leading '_' as usual for identifiers. + ident = p[1] + if ident[0] == "_": + ident = ident[1:] + idents.insert(0, ident) + p[0] = idents + + def p_IdentifiersList(self, p): + """ + Identifiers : COMMA IDENTIFIER Identifiers + """ + idents = list(p[3]) + # This is only used for identifier-list-valued extended attributes, and if + # we're going to restrict to IDENTIFIER here we should at least allow + # escaping with leading '_' as usual for identifiers. + ident = p[2] + if ident[0] == "_": + ident = ident[1:] + idents.insert(0, ident) + p[0] = idents + + def p_IdentifiersEmpty(self, p): + """ + Identifiers : + """ + p[0] = [] + + def p_error(self, p): + if not p: + raise WebIDLError( + ( + "Syntax Error at end of file. Possibly due to " + "missing semicolon(;), braces(}) or both" + ), + [self._filename], + ) + else: + raise WebIDLError( + "invalid syntax", + [Location(self.lexer, p.lineno, p.lexpos, self._filename)], + ) + + def __init__(self, outputdir="", lexer=None): + Tokenizer.__init__(self, outputdir, lexer) + + logger = SqueakyCleanLogger() + try: + self.parser = yacc.yacc( + module=self, + outputdir=outputdir, + errorlog=logger, + write_tables=False, + # Pickling the grammar is a speedup in + # some cases (older Python?) but a + # significant slowdown in others. + # We're not pickling for now, until it + # becomes a speedup again. + # , picklefile='WebIDLGrammar.pkl' + ) + finally: + logger.reportGrammarErrors() + + self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None) + self._installBuiltins(self._globalScope) + self._productions = [] + + self._filename = "<builtin>" + self.lexer.input(Parser._builtins) + self._filename = None + + self.parser.parse(lexer=self.lexer, tracking=True) + + def _installBuiltins(self, scope): + assert isinstance(scope, IDLScope) + + # range omits the last value. + for x in range( + IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1 + ): + builtin = BuiltinTypes[x] + name = builtin.name + IDLTypedef(BuiltinLocation("<builtin type>"), scope, builtin, name) + + @staticmethod + def handleNullable(type, questionMarkLocation): + if questionMarkLocation is not None: + type = IDLNullableType(questionMarkLocation, type) + + return type + + def parse(self, t, filename=None): + self.lexer.input(t) + + # for tok in iter(self.lexer.token, None): + # print tok + + self._filename = filename + self._productions.extend(self.parser.parse(lexer=self.lexer, tracking=True)) + self._filename = None + + def finish(self): + # If we have interfaces that are iterable, create their + # iterator interfaces and add them to the productions array. + interfaceStatements = [] + for p in self._productions: + if isinstance(p, IDLInterface): + interfaceStatements.append(p) + + for iface in interfaceStatements: + iterable = None + # We haven't run finish() on the interface yet, so we don't know + # whether our interface is maplike/setlike/iterable or not. This + # means we have to loop through the members to see if we have an + # iterable member. + for m in iface.members: + if isinstance(m, (IDLIterable, IDLAsyncIterable)): + iterable = m + break + if iterable and (iterable.isPairIterator() or iterable.isAsyncIterable()): + + def simpleExtendedAttr(str): + return IDLExtendedAttribute(iface.location, (str,)) + + if isinstance(iterable, IDLAsyncIterable): + nextReturnType = IDLPromiseType( + iterable.location, BuiltinTypes[IDLBuiltinType.Types.any] + ) + else: + nextReturnType = BuiltinTypes[IDLBuiltinType.Types.object] + nextMethod = IDLMethod( + iterable.location, + IDLUnresolvedIdentifier(iterable.location, "next"), + nextReturnType, + [], + ) + nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) + + methods = [nextMethod] + + if iterable.getExtendedAttribute("GenerateReturnMethod"): + assert isinstance(iterable, IDLAsyncIterable) + + returnMethod = IDLMethod( + iterable.location, + IDLUnresolvedIdentifier(iterable.location, "return"), + IDLPromiseType( + iterable.location, BuiltinTypes[IDLBuiltinType.Types.any] + ), + [ + IDLArgument( + iterable.location, + IDLUnresolvedIdentifier( + BuiltinLocation("<auto-generated-identifier>"), + "value", + ), + BuiltinTypes[IDLBuiltinType.Types.any], + optional=True, + ), + ], + ) + returnMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) + methods.append(returnMethod) + + if iterable.isIterable(): + itr_suffix = "Iterator" + else: + itr_suffix = "AsyncIterator" + itr_ident = IDLUnresolvedIdentifier( + iface.location, iface.identifier.name + itr_suffix + ) + if iterable.isIterable(): + classNameOverride = iface.identifier.name + " Iterator" + elif iterable.isAsyncIterable(): + classNameOverride = iface.identifier.name + " AsyncIterator" + itr_iface = IDLInterface( + iface.location, + self.globalScope(), + itr_ident, + None, + methods, + isKnownNonPartial=True, + classNameOverride=classNameOverride, + ) + itr_iface.addExtendedAttributes( + [simpleExtendedAttr("LegacyNoInterfaceObject")] + ) + # Make sure the exposure set for the iterator interface is the + # same as the exposure set for the iterable interface, because + # we're going to generate methods on the iterable that return + # instances of the iterator. + itr_iface._exposureGlobalNames = set(iface._exposureGlobalNames) + # Always append generated iterable interfaces after the + # interface they're a member of, otherwise nativeType generation + # won't work correctly. + if iterable.isIterable(): + itr_iface.iterableInterface = iface + else: + itr_iface.asyncIterableInterface = iface + self._productions.append(itr_iface) + iterable.iteratorType = IDLWrapperType(iface.location, itr_iface) + + # Make sure we finish IDLIncludesStatements before we finish the + # IDLInterfaces. + # XXX khuey hates this bit and wants to nuke it from orbit. + includesStatements = [ + p for p in self._productions if isinstance(p, IDLIncludesStatement) + ] + otherStatements = [ + p for p in self._productions if not isinstance(p, IDLIncludesStatement) + ] + for production in includesStatements: + production.finish(self.globalScope()) + for production in otherStatements: + production.finish(self.globalScope()) + + # Do any post-finish validation we need to do + for production in self._productions: + production.validate() + + # De-duplicate self._productions, without modifying its order. + seen = set() + result = [] + for p in self._productions: + if p not in seen: + seen.add(p) + result.append(p) + return result + + def reset(self): + return Parser(lexer=self.lexer) + + # Builtin IDL defined by WebIDL + _builtins = """ + typedef (ArrayBufferView or ArrayBuffer) BufferSource; + """ + + +def main(): + # Parse arguments. + from optparse import OptionParser + + usageString = "usage: %prog [options] files" + o = OptionParser(usage=usageString) + o.add_option( + "--cachedir", + dest="cachedir", + default=None, + help="Directory in which to cache lex/parse tables.", + ) + o.add_option( + "--verbose-errors", + action="store_true", + default=False, + help="When an error happens, display the Python traceback.", + ) + (options, args) = o.parse_args() + + if len(args) < 1: + o.error(usageString) + + fileList = args + baseDir = os.getcwd() + + # Parse the WebIDL. + parser = Parser(options.cachedir) + try: + for filename in fileList: + fullPath = os.path.normpath(os.path.join(baseDir, filename)) + f = open(fullPath, "rb") + lines = f.readlines() + f.close() + print(fullPath) + parser.parse("".join(lines), fullPath) + parser.finish() + except WebIDLError as e: + if options.verbose_errors: + traceback.print_exc() + else: + print(e) + + +if __name__ == "__main__": + main() diff --git a/dom/bindings/parser/runtests.py b/dom/bindings/parser/runtests.py new file mode 100644 index 0000000000..6cef297e30 --- /dev/null +++ b/dom/bindings/parser/runtests.py @@ -0,0 +1,142 @@ +# 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/. + +from __future__ import print_function + +import argparse +import glob +import os +import sys +import traceback + +import WebIDL + + +class TestHarness(object): + def __init__(self, test, verbose): + self.test = test + self.verbose = verbose + self.printed_intro = False + self.passed = 0 + self.failures = [] + + def start(self): + if self.verbose: + self.maybe_print_intro() + + def finish(self): + if self.verbose or self.printed_intro: + print("Finished test %s" % self.test) + + def maybe_print_intro(self): + if not self.printed_intro: + print("Starting test %s" % self.test) + self.printed_intro = True + + def test_pass(self, msg): + self.passed += 1 + if self.verbose: + print("TEST-PASS | %s" % msg) + + def test_fail(self, msg): + self.maybe_print_intro() + self.failures.append(msg) + print("TEST-UNEXPECTED-FAIL | %s" % msg) + + def ok(self, condition, msg): + if condition: + self.test_pass(msg) + else: + self.test_fail(msg) + + def check(self, a, b, msg): + if a == b: + self.test_pass(msg) + else: + self.test_fail(msg + " | Got %s expected %s" % (a, b)) + + def should_throw(self, parser, code, msg): + parser = parser.reset() + threw = False + try: + parser.parse(code) + parser.finish() + except Exception: + threw = True + + self.ok(threw, "Should have thrown: %s" % msg) + + +def run_tests(tests, verbose): + testdir = os.path.join(os.path.dirname(__file__), "tests") + if not tests: + tests = glob.iglob(os.path.join(testdir, "*.py")) + sys.path.append(testdir) + + all_passed = 0 + failed_tests = [] + + for test in tests: + (testpath, ext) = os.path.splitext(os.path.basename(test)) + _test = __import__(testpath, globals(), locals(), ["WebIDLTest"]) + + harness = TestHarness(test, verbose) + harness.start() + try: + _test.WebIDLTest.__call__(WebIDL.Parser(), harness) + except Exception as ex: + harness.test_fail("Unhandled exception in test %s: %s" % (testpath, ex)) + traceback.print_exc() + finally: + harness.finish() + all_passed += harness.passed + if harness.failures: + failed_tests.append((test, harness.failures)) + + if verbose or failed_tests: + print() + print("Result summary:") + print("Successful: %d" % all_passed) + print("Unexpected: %d" % sum(len(failures) for _, failures in failed_tests)) + for test, failures in failed_tests: + print("%s:" % test) + for failure in failures: + print("TEST-UNEXPECTED-FAIL | %s" % failure) + return 1 if failed_tests else 0 + + +def get_parser(): + usage = """%(prog)s [OPTIONS] [TESTS] + Where TESTS are relative to the tests directory.""" + parser = argparse.ArgumentParser(usage=usage) + parser.add_argument( + "-q", + "--quiet", + action="store_false", + dest="verbose", + help="Don't print passing tests.", + default=None, + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + dest="verbose", + help="Run tests in verbose mode.", + ) + parser.add_argument("tests", nargs="*", help="Tests to run") + return parser + + +if __name__ == "__main__": + parser = get_parser() + args = parser.parse_args() + if args.verbose is None: + args.verbose = True + + # Make sure the current directory is in the python path so we can cache the + # result of the webidlyacc.py generation. + sys.path.append(".") + + sys.exit(run_tests(args.tests, verbose=args.verbose)) diff --git a/dom/bindings/parser/tests/test_any_null.py b/dom/bindings/parser/tests/test_any_null.py new file mode 100644 index 0000000000..d03216ed51 --- /dev/null +++ b/dom/bindings/parser/tests/test_any_null.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface DoubleNull { + attribute any? foo; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_argument_identifier_conflicts.py b/dom/bindings/parser/tests/test_argument_identifier_conflicts.py new file mode 100644 index 0000000000..3ee8dc6bd6 --- /dev/null +++ b/dom/bindings/parser/tests/test_argument_identifier_conflicts.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface ArgumentIdentifierConflict { + undefined foo(boolean arg1, boolean arg1); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_argument_keywords.py b/dom/bindings/parser/tests/test_argument_keywords.py new file mode 100644 index 0000000000..bbed33df92 --- /dev/null +++ b/dom/bindings/parser/tests/test_argument_keywords.py @@ -0,0 +1,22 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface Foo { + undefined foo(object constructor); + }; + """ + ) + + results = parser.finish() + harness.check(len(results), 1, "Should have an interface") + iface = results[0] + harness.check(len(iface.members), 1, "Should have an operation") + operation = iface.members[0] + harness.check(len(operation.signatures()), 1, "Should have one signature") + (retval, args) = operation.signatures()[0] + harness.check(len(args), 1, "Should have an argument") + harness.check( + args[0].identifier.name, + "constructor", + "Should have an identifier named 'constructor'", + ) diff --git a/dom/bindings/parser/tests/test_argument_novoid.py b/dom/bindings/parser/tests/test_argument_novoid.py new file mode 100644 index 0000000000..571fd0b5eb --- /dev/null +++ b/dom/bindings/parser/tests/test_argument_novoid.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface VoidArgument1 { + void foo(void arg2); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_arraybuffer.py b/dom/bindings/parser/tests/test_arraybuffer.py new file mode 100644 index 0000000000..b762d06ac2 --- /dev/null +++ b/dom/bindings/parser/tests/test_arraybuffer.py @@ -0,0 +1,95 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestArrayBuffer { + attribute ArrayBuffer bufferAttr; + undefined bufferMethod(ArrayBuffer arg1, ArrayBuffer? arg2, sequence<ArrayBuffer> arg3); + + attribute ArrayBufferView viewAttr; + undefined viewMethod(ArrayBufferView arg1, ArrayBufferView? arg2, sequence<ArrayBufferView> arg3); + + attribute Int8Array int8ArrayAttr; + undefined int8ArrayMethod(Int8Array arg1, Int8Array? arg2, sequence<Int8Array> arg3); + + attribute Uint8Array uint8ArrayAttr; + undefined uint8ArrayMethod(Uint8Array arg1, Uint8Array? arg2, sequence<Uint8Array> arg3); + + attribute Uint8ClampedArray uint8ClampedArrayAttr; + undefined uint8ClampedArrayMethod(Uint8ClampedArray arg1, Uint8ClampedArray? arg2, sequence<Uint8ClampedArray> arg3); + + attribute Int16Array int16ArrayAttr; + undefined int16ArrayMethod(Int16Array arg1, Int16Array? arg2, sequence<Int16Array> arg3); + + attribute Uint16Array uint16ArrayAttr; + undefined uint16ArrayMethod(Uint16Array arg1, Uint16Array? arg2, sequence<Uint16Array> arg3); + + attribute Int32Array int32ArrayAttr; + undefined int32ArrayMethod(Int32Array arg1, Int32Array? arg2, sequence<Int32Array> arg3); + + attribute Uint32Array uint32ArrayAttr; + undefined uint32ArrayMethod(Uint32Array arg1, Uint32Array? arg2, sequence<Uint32Array> arg3); + + attribute Float32Array float32ArrayAttr; + undefined float32ArrayMethod(Float32Array arg1, Float32Array? arg2, sequence<Float32Array> arg3); + + attribute Float64Array float64ArrayAttr; + undefined float64ArrayMethod(Float64Array arg1, Float64Array? arg2, sequence<Float64Array> arg3); + }; + """ + ) + + results = parser.finish() + + iface = results[0] + + harness.ok(True, "TestArrayBuffer interface parsed without error") + harness.check(len(iface.members), 22, "Interface should have twenty two members") + + members = iface.members + + def checkStuff(attr, method, t): + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Expect an IDLAttribute") + harness.ok(isinstance(method, WebIDL.IDLMethod), "Expect an IDLMethod") + + harness.check(str(attr.type), t, "Expect an ArrayBuffer type") + harness.ok(attr.type.isSpiderMonkeyInterface(), "Should test as a js interface") + + (retType, arguments) = method.signatures()[0] + harness.ok(retType.isUndefined(), "Should have an undefined return type") + harness.check(len(arguments), 3, "Expect 3 arguments") + + harness.check(str(arguments[0].type), t, "Expect an ArrayBuffer type") + harness.ok( + arguments[0].type.isSpiderMonkeyInterface(), "Should test as a js interface" + ) + + harness.check( + str(arguments[1].type), t + "OrNull", "Expect an ArrayBuffer type" + ) + harness.ok( + arguments[1].type.inner.isSpiderMonkeyInterface(), + "Should test as a js interface", + ) + + harness.check( + str(arguments[2].type), t + "Sequence", "Expect an ArrayBuffer type" + ) + harness.ok( + arguments[2].type.inner.isSpiderMonkeyInterface(), + "Should test as a js interface", + ) + + checkStuff(members[0], members[1], "ArrayBuffer") + checkStuff(members[2], members[3], "ArrayBufferView") + checkStuff(members[4], members[5], "Int8Array") + checkStuff(members[6], members[7], "Uint8Array") + checkStuff(members[8], members[9], "Uint8ClampedArray") + checkStuff(members[10], members[11], "Int16Array") + checkStuff(members[12], members[13], "Uint16Array") + checkStuff(members[14], members[15], "Int32Array") + checkStuff(members[16], members[17], "Uint32Array") + checkStuff(members[18], members[19], "Float32Array") + checkStuff(members[20], members[21], "Float64Array") diff --git a/dom/bindings/parser/tests/test_attr.py b/dom/bindings/parser/tests/test_attr.py new file mode 100644 index 0000000000..ebc47f8780 --- /dev/null +++ b/dom/bindings/parser/tests/test_attr.py @@ -0,0 +1,199 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + testData = [ + ("::TestAttr%s::b", "b", "Byte%s", False), + ("::TestAttr%s::rb", "rb", "Byte%s", True), + ("::TestAttr%s::o", "o", "Octet%s", False), + ("::TestAttr%s::ro", "ro", "Octet%s", True), + ("::TestAttr%s::s", "s", "Short%s", False), + ("::TestAttr%s::rs", "rs", "Short%s", True), + ("::TestAttr%s::us", "us", "UnsignedShort%s", False), + ("::TestAttr%s::rus", "rus", "UnsignedShort%s", True), + ("::TestAttr%s::l", "l", "Long%s", False), + ("::TestAttr%s::rl", "rl", "Long%s", True), + ("::TestAttr%s::ul", "ul", "UnsignedLong%s", False), + ("::TestAttr%s::rul", "rul", "UnsignedLong%s", True), + ("::TestAttr%s::ll", "ll", "LongLong%s", False), + ("::TestAttr%s::rll", "rll", "LongLong%s", True), + ("::TestAttr%s::ull", "ull", "UnsignedLongLong%s", False), + ("::TestAttr%s::rull", "rull", "UnsignedLongLong%s", True), + ("::TestAttr%s::str", "str", "String%s", False), + ("::TestAttr%s::rstr", "rstr", "String%s", True), + ("::TestAttr%s::obj", "obj", "Object%s", False), + ("::TestAttr%s::robj", "robj", "Object%s", True), + ("::TestAttr%s::object", "object", "Object%s", False), + ("::TestAttr%s::f", "f", "Float%s", False), + ("::TestAttr%s::rf", "rf", "Float%s", True), + ] + + parser.parse( + """ + interface TestAttr { + attribute byte b; + readonly attribute byte rb; + attribute octet o; + readonly attribute octet ro; + attribute short s; + readonly attribute short rs; + attribute unsigned short us; + readonly attribute unsigned short rus; + attribute long l; + readonly attribute long rl; + attribute unsigned long ul; + readonly attribute unsigned long rul; + attribute long long ll; + readonly attribute long long rll; + attribute unsigned long long ull; + readonly attribute unsigned long long rull; + attribute DOMString str; + readonly attribute DOMString rstr; + attribute object obj; + readonly attribute object robj; + attribute object _object; + attribute float f; + readonly attribute float rf; + }; + + interface TestAttrNullable { + attribute byte? b; + readonly attribute byte? rb; + attribute octet? o; + readonly attribute octet? ro; + attribute short? s; + readonly attribute short? rs; + attribute unsigned short? us; + readonly attribute unsigned short? rus; + attribute long? l; + readonly attribute long? rl; + attribute unsigned long? ul; + readonly attribute unsigned long? rul; + attribute long long? ll; + readonly attribute long long? rll; + attribute unsigned long long? ull; + readonly attribute unsigned long long? rull; + attribute DOMString? str; + readonly attribute DOMString? rstr; + attribute object? obj; + readonly attribute object? robj; + attribute object? _object; + attribute float? f; + readonly attribute float? rf; + }; + """ + ) + + results = parser.finish() + + def checkAttr(attr, QName, name, type, readonly): + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.ok(attr.isAttr(), "Attr is an Attr") + harness.ok(not attr.isMethod(), "Attr is not an method") + harness.ok(not attr.isConst(), "Attr is not a const") + harness.check(attr.identifier.QName(), QName, "Attr has the right QName") + harness.check(attr.identifier.name, name, "Attr has the right name") + harness.check(str(attr.type), type, "Attr has the right type") + harness.check(attr.readonly, readonly, "Attr's readonly state is correct") + + harness.ok(True, "TestAttr interface parsed without error.") + harness.check(len(results), 2, "Should be two productions.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), "::TestAttr", "Interface has the right QName" + ) + harness.check(iface.identifier.name, "TestAttr", "Interface has the right name") + harness.check( + len(iface.members), len(testData), "Expect %s members" % len(testData) + ) + + attrs = iface.members + + for i in range(len(attrs)): + data = testData[i] + attr = attrs[i] + (QName, name, type, readonly) = data + checkAttr(attr, QName % "", name, type % "", readonly) + + iface = results[1] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), "::TestAttrNullable", "Interface has the right QName" + ) + harness.check( + iface.identifier.name, "TestAttrNullable", "Interface has the right name" + ) + harness.check( + len(iface.members), len(testData), "Expect %s members" % len(testData) + ) + + attrs = iface.members + + for i in range(len(attrs)): + data = testData[i] + attr = attrs[i] + (QName, name, type, readonly) = data + checkAttr(attr, QName % "Nullable", name, type % "OrNull", readonly) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [SetterThrows] readonly attribute boolean foo; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [SetterThrows] on readonly attributes") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Throw] readonly attribute boolean foo; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should spell [Throws] correctly") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [SameObject] readonly attribute boolean foo; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow [SameObject] on attributes not of interface type" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [SameObject] readonly attribute A foo; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow [SameObject] on attributes of interface type") diff --git a/dom/bindings/parser/tests/test_attr_sequence_type.py b/dom/bindings/parser/tests/test_attr_sequence_type.py new file mode 100644 index 0000000000..afbc732974 --- /dev/null +++ b/dom/bindings/parser/tests/test_attr_sequence_type.py @@ -0,0 +1,77 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface AttrSequenceType { + attribute sequence<object> foo; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Attribute type must not be a sequence type") + + parser.reset() + + threw = False + try: + parser.parse( + """ + interface AttrUnionWithSequenceType { + attribute (sequence<object> or DOMString) foo; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Attribute type must not be a union with a sequence member type") + + parser.reset() + + threw = False + try: + parser.parse( + """ + interface AttrNullableUnionWithSequenceType { + attribute (sequence<object>? or DOMString) foo; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Attribute type must not be a union with a nullable sequence " "member type", + ) + + parser.reset() + + threw = False + try: + parser.parse( + "\n" + " interface AttrUnionWithUnionWithSequenceType {\n" + " attribute ((sequence<object> or DOMString) or " + "AttrUnionWithUnionWithSequenceType) foo;\n" + " };\n" + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Attribute type must not be a union type with a union member " + "type that has a sequence member type", + ) diff --git a/dom/bindings/parser/tests/test_attributes_on_types.py b/dom/bindings/parser/tests/test_attributes_on_types.py new file mode 100644 index 0000000000..d0a0c7a72c --- /dev/null +++ b/dom/bindings/parser/tests/test_attributes_on_types.py @@ -0,0 +1,567 @@ +def WebIDLTest(parser, harness): + # Basic functionality + threw = False + try: + parser.parse( + """ + typedef [EnforceRange] long Foo; + typedef [Clamp] long Bar; + typedef [LegacyNullToEmptyString] DOMString Baz; + dictionary A { + required [EnforceRange] long a; + required [Clamp] long b; + [ChromeOnly, EnforceRange] long c; + Foo d; + }; + interface B { + attribute Foo typedefFoo; + attribute [EnforceRange] long foo; + attribute [Clamp] long bar; + attribute [LegacyNullToEmptyString] DOMString baz; + undefined method([EnforceRange] long foo, [Clamp] long bar, + [LegacyNullToEmptyString] DOMString baz); + undefined method2(optional [EnforceRange] long foo, optional [Clamp] long bar, + optional [LegacyNullToEmptyString] DOMString baz); + undefined method3(optional [LegacyNullToEmptyString] UTF8String foo = ""); + }; + interface C { + attribute [EnforceRange] long? foo; + attribute [Clamp] long? bar; + undefined method([EnforceRange] long? foo, [Clamp] long? bar); + undefined method2(optional [EnforceRange] long? foo, optional [Clamp] long? bar); + }; + interface Setlike { + setlike<[Clamp] long>; + }; + interface Maplike { + maplike<[Clamp] long, [EnforceRange] long>; + }; + interface Iterable { + iterable<[Clamp] long, [EnforceRange] long>; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(not threw, "Should not have thrown on parsing normal") + if not threw: + harness.check( + results[0].innerType.hasEnforceRange(), True, "Foo is [EnforceRange]" + ) + harness.check(results[1].innerType.hasClamp(), True, "Bar is [Clamp]") + harness.check( + results[2].innerType.legacyNullToEmptyString, + True, + "Baz is [LegacyNullToEmptyString]", + ) + A = results[3] + harness.check( + A.members[0].type.hasEnforceRange(), True, "A.a is [EnforceRange]" + ) + harness.check(A.members[1].type.hasClamp(), True, "A.b is [Clamp]") + harness.check( + A.members[2].type.hasEnforceRange(), True, "A.c is [EnforceRange]" + ) + harness.check( + A.members[3].type.hasEnforceRange(), True, "A.d is [EnforceRange]" + ) + B = results[4] + harness.check( + B.members[0].type.hasEnforceRange(), True, "B.typedefFoo is [EnforceRange]" + ) + harness.check( + B.members[1].type.hasEnforceRange(), True, "B.foo is [EnforceRange]" + ) + harness.check(B.members[2].type.hasClamp(), True, "B.bar is [Clamp]") + harness.check( + B.members[3].type.legacyNullToEmptyString, + True, + "B.baz is [LegacyNullToEmptyString]", + ) + method = B.members[4].signatures()[0][1] + harness.check( + method[0].type.hasEnforceRange(), + True, + "foo argument of method is [EnforceRange]", + ) + harness.check( + method[1].type.hasClamp(), True, "bar argument of method is [Clamp]" + ) + harness.check( + method[2].type.legacyNullToEmptyString, + True, + "baz argument of method is [LegacyNullToEmptyString]", + ) + method2 = B.members[5].signatures()[0][1] + harness.check( + method2[0].type.hasEnforceRange(), + True, + "foo argument of method2 is [EnforceRange]", + ) + harness.check( + method2[1].type.hasClamp(), True, "bar argument of method2 is [Clamp]" + ) + harness.check( + method2[2].type.legacyNullToEmptyString, + True, + "baz argument of method2 is [LegacyNullToEmptyString]", + ) + + method3 = B.members[6].signatures()[0][1] + harness.check( + method3[0].type.legacyNullToEmptyString, + True, + "bar argument of method2 is [LegacyNullToEmptyString]", + ) + harness.check( + method3[0].defaultValue.type.isUTF8String(), + True, + "default value of bar argument of method2 is correctly coerced to UTF8String", + ) + + C = results[5] + harness.ok(C.members[0].type.nullable(), "C.foo is nullable") + harness.ok(C.members[0].type.hasEnforceRange(), "C.foo has [EnforceRange]") + harness.ok(C.members[1].type.nullable(), "C.bar is nullable") + harness.ok(C.members[1].type.hasClamp(), "C.bar has [Clamp]") + method = C.members[2].signatures()[0][1] + harness.ok(method[0].type.nullable(), "foo argument of method is nullable") + harness.ok( + method[0].type.hasEnforceRange(), + "foo argument of method has [EnforceRange]", + ) + harness.ok(method[1].type.nullable(), "bar argument of method is nullable") + harness.ok(method[1].type.hasClamp(), "bar argument of method has [Clamp]") + method2 = C.members[3].signatures()[0][1] + harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable") + harness.ok( + method2[0].type.hasEnforceRange(), + "foo argument of method2 has [EnforceRange]", + ) + harness.ok(method2[1].type.nullable(), "bar argument of method2 is nullable") + harness.ok(method2[1].type.hasClamp(), "bar argument of method2 has [Clamp]") + + # Test [AllowShared] + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [AllowShared] ArrayBufferView Foo; + dictionary A { + required [AllowShared] ArrayBufferView a; + [ChromeOnly, AllowShared] ArrayBufferView b; + Foo c; + }; + interface B { + attribute Foo typedefFoo; + attribute [AllowShared] ArrayBufferView foo; + undefined method([AllowShared] ArrayBufferView foo); + undefined method2(optional [AllowShared] ArrayBufferView foo); + }; + interface C { + attribute [AllowShared] ArrayBufferView? foo; + undefined method([AllowShared] ArrayBufferView? foo); + undefined method2(optional [AllowShared] ArrayBufferView? foo); + }; + interface Setlike { + setlike<[AllowShared] ArrayBufferView>; + }; + interface Maplike { + maplike<[Clamp] long, [AllowShared] ArrayBufferView>; + }; + interface Iterable { + iterable<[Clamp] long, [AllowShared] ArrayBufferView>; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(not threw, "Should not have thrown on parsing normal") + if not threw: + harness.ok(results[0].innerType.hasAllowShared(), "Foo is [AllowShared]") + A = results[1] + harness.ok(A.members[0].type.hasAllowShared(), "A.a is [AllowShared]") + harness.ok(A.members[1].type.hasAllowShared(), "A.b is [AllowShared]") + harness.ok(A.members[2].type.hasAllowShared(), "A.c is [AllowShared]") + B = results[2] + harness.ok(B.members[0].type.hasAllowShared(), "B.typedefFoo is [AllowShared]") + harness.ok(B.members[1].type.hasAllowShared(), "B.foo is [AllowShared]") + method = B.members[2].signatures()[0][1] + harness.ok( + method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]" + ) + method2 = B.members[3].signatures()[0][1] + harness.ok( + method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]" + ) + C = results[3] + harness.ok(C.members[0].type.nullable(), "C.foo is nullable") + harness.ok(C.members[0].type.hasAllowShared(), "C.foo is [AllowShared]") + method = C.members[1].signatures()[0][1] + harness.ok(method[0].type.nullable(), "foo argument of method is nullable") + harness.ok( + method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]" + ) + method2 = C.members[2].signatures()[0][1] + harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable") + harness.ok( + method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]" + ) + + ATTRIBUTES = [ + ("[Clamp]", "long"), + ("[EnforceRange]", "long"), + ("[LegacyNullToEmptyString]", "DOMString"), + ("[AllowShared]", "ArrayBufferView"), + ] + TEMPLATES = [ + ( + "required dictionary members", + """ + dictionary Foo { + %s required %s foo; + }; + """, + ), + ( + "optional arguments", + """ + interface Foo { + undefined foo(%s optional %s foo); + }; + """, + ), + ( + "typedefs", + """ + %s typedef %s foo; + """, + ), + ( + "attributes", + """ + interface Foo { + %s attribute %s foo; + }; + """, + ), + ( + "readonly attributes", + """ + interface Foo { + readonly attribute %s %s foo; + }; + """, + ), + ( + "readonly unresolved attributes", + """ + interface Foo { + readonly attribute Bar baz; + }; + typedef %s %s Bar; + """, + ), + ( + "method", + """ + interface Foo { + %s %s foo(); + }; + """, + ), + ( + "interface", + """ + %s + interface Foo { + attribute %s foo; + }; + """, + ), + ( + "partial interface", + """ + interface Foo { + undefined foo(); + }; + %s + partial interface Foo { + attribute %s bar; + }; + """, + ), + ( + "interface mixin", + """ + %s + interface mixin Foo { + attribute %s foo; + }; + """, + ), + ( + "namespace", + """ + %s + namespace Foo { + attribute %s foo; + }; + """, + ), + ( + "partial namespace", + """ + namespace Foo { + undefined foo(); + }; + %s + partial namespace Foo { + attribute %s bar; + }; + """, + ), + ( + "dictionary", + """ + %s + dictionary Foo { + %s foo; + }; + """, + ), + ] + + for (name, template) in TEMPLATES: + parser = parser.reset() + threw = False + try: + parser.parse(template % ("", "long")) + parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Template for %s parses without attributes" % name) + for (attribute, type) in ATTRIBUTES: + parser = parser.reset() + threw = False + try: + parser.parse(template % (attribute, type)) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow %s on %s" % (attribute, name)) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [Clamp, EnforceRange] long Foo; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [EnforceRange, Clamp] long Foo; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [Clamp] long Foo; + typedef [EnforceRange] Foo bar; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [EnforceRange] long Foo; + typedef [Clamp] Foo bar; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs") + + TYPES = [ + "DOMString", + "unrestricted float", + "float", + "unrestricted double", + "double", + ] + + for type in TYPES: + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [Clamp] %s Foo; + """ + % type + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow [Clamp] on %s" % type) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [EnforceRange] %s Foo; + """ + % type + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow [EnforceRange] on %s" % type) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [LegacyNullToEmptyString] long Foo; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow [LegacyNullToEmptyString] on long") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [LegacyNullToEmptyString] JSString Foo; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow [LegacyNullToEmptyString] on JSString") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [LegacyNullToEmptyString] DOMString? Foo; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should not allow [LegacyNullToEmptyString] on nullable DOMString" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [AllowShared] DOMString Foo; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "[AllowShared] only allowed on buffer source types") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef [AllowShared=something] ArrayBufferView Foo; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "[AllowShared] must take no arguments") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + undefined foo([Clamp] Bar arg); + }; + typedef long Bar; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow type attributes on unresolved types") + harness.check( + results[0].members[0].signatures()[0][1][0].type.hasClamp(), + True, + "Unresolved types with type attributes should correctly resolve with attributes", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + undefined foo(Bar arg); + }; + typedef [Clamp] long Bar; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow type attributes on typedefs") + harness.check( + results[0].members[0].signatures()[0][1][0].type.hasClamp(), + True, + "Unresolved types that resolve to typedefs with attributes should correctly resolve with " + "attributes", + ) diff --git a/dom/bindings/parser/tests/test_builtin_filename.py b/dom/bindings/parser/tests/test_builtin_filename.py new file mode 100644 index 0000000000..25ed32befc --- /dev/null +++ b/dom/bindings/parser/tests/test_builtin_filename.py @@ -0,0 +1,11 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface Test { + attribute long b; + }; + """ + ) + + attr = parser.finish()[0].members[0] + harness.check(attr.type.filename(), "<builtin>", "Filename on builtin type") diff --git a/dom/bindings/parser/tests/test_builtins.py b/dom/bindings/parser/tests/test_builtins.py new file mode 100644 index 0000000000..a75a12e814 --- /dev/null +++ b/dom/bindings/parser/tests/test_builtins.py @@ -0,0 +1,59 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestBuiltins { + attribute boolean b; + attribute byte s8; + attribute octet u8; + attribute short s16; + attribute unsigned short u16; + attribute long s32; + attribute unsigned long u32; + attribute long long s64; + attribute unsigned long long u64; + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestBuiltins interface parsed without error.") + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + iface = results[0] + harness.check( + iface.identifier.QName(), "::TestBuiltins", "Interface has the right QName" + ) + harness.check(iface.identifier.name, "TestBuiltins", "Interface has the right name") + harness.check(iface.parent, None, "Interface has no parent") + + members = iface.members + harness.check(len(members), 9, "Should be one production") + + names = ["b", "s8", "u8", "s16", "u16", "s32", "u32", "s64", "u64", "ts"] + types = [ + "Boolean", + "Byte", + "Octet", + "Short", + "UnsignedShort", + "Long", + "UnsignedLong", + "LongLong", + "UnsignedLongLong", + "UnsignedLongLong", + ] + for i in range(9): + attr = members[i] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.check( + attr.identifier.QName(), + "::TestBuiltins::" + names[i], + "Attr has correct QName", + ) + harness.check(attr.identifier.name, names[i], "Attr has correct name") + harness.check(str(attr.type), types[i], "Attr type is the correct name") + harness.ok(attr.type.isPrimitive(), "Should be a primitive type") diff --git a/dom/bindings/parser/tests/test_bytestring.py b/dom/bindings/parser/tests/test_bytestring.py new file mode 100644 index 0000000000..248d2716f3 --- /dev/null +++ b/dom/bindings/parser/tests/test_bytestring.py @@ -0,0 +1,125 @@ +# -*- coding: UTF-8 -*- + +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestByteString { + attribute ByteString bs; + attribute DOMString ds; + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestByteString interface parsed without error.") + + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + iface = results[0] + harness.check( + iface.identifier.QName(), "::TestByteString", "Interface has the right QName" + ) + harness.check( + iface.identifier.name, "TestByteString", "Interface has the right name" + ) + harness.check(iface.parent, None, "Interface has no parent") + + members = iface.members + harness.check(len(members), 2, "Should be two productions") + + attr = members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.check( + attr.identifier.QName(), "::TestByteString::bs", "Attr has correct QName" + ) + harness.check(attr.identifier.name, "bs", "Attr has correct name") + harness.check(str(attr.type), "ByteString", "Attr type is the correct name") + harness.ok(attr.type.isByteString(), "Should be ByteString type") + harness.ok(attr.type.isString(), "Should be String collective type") + harness.ok(not attr.type.isDOMString(), "Should be not be DOMString type") + + # now check we haven't broken DOMStrings in the process. + attr = members[1] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.check( + attr.identifier.QName(), "::TestByteString::ds", "Attr has correct QName" + ) + harness.check(attr.identifier.name, "ds", "Attr has correct name") + harness.check(str(attr.type), "String", "Attr type is the correct name") + harness.ok(attr.type.isDOMString(), "Should be DOMString type") + harness.ok(attr.type.isString(), "Should be String collective type") + harness.ok(not attr.type.isByteString(), "Should be not be ByteString type") + + # Cannot represent constant ByteString in IDL. + threw = False + try: + parser.parse( + """ + interface ConstByteString { + const ByteString foo = "hello" + }; + """ + ) + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, "Should have thrown a WebIDL error for ByteString default in interface" + ) + + # Can have optional ByteStrings with default values + try: + parser.parse( + """ + interface OptionalByteString { + undefined passByteString(optional ByteString arg = "hello"); + }; + """ + ) + parser.finish() + except WebIDL.WebIDLError as e: + harness.ok( + False, + "Should not have thrown a WebIDL error for ByteString " + "default in dictionary. " + str(e), + ) + + # Can have a default ByteString value in a dictionary + try: + parser.parse( + """ + dictionary OptionalByteStringDict { + ByteString item = "some string"; + }; + """ + ) + parser.finish() + except WebIDL.WebIDLError as e: + harness.ok( + False, + "Should not have thrown a WebIDL error for ByteString " + "default in dictionary. " + str(e), + ) + + # Don't allow control characters in ByteString literals + threw = False + try: + parser.parse( + """ + dictionary OptionalByteStringDict2 { + ByteString item = "\x03"; + }; + """ + ) + parser.finish() + except WebIDL.WebIDLError: + threw = True + + harness.ok( + threw, + "Should have thrown a WebIDL error for invalid ByteString " + "default in dictionary", + ) diff --git a/dom/bindings/parser/tests/test_callback.py b/dom/bindings/parser/tests/test_callback.py new file mode 100644 index 0000000000..407644a6a8 --- /dev/null +++ b/dom/bindings/parser/tests/test_callback.py @@ -0,0 +1,42 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestCallback { + attribute CallbackType? listener; + }; + + callback CallbackType = boolean (unsigned long arg); + """ + ) + + results = parser.finish() + + harness.ok(True, "TestCallback interface parsed without error.") + harness.check(len(results), 2, "Should be two productions.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), "::TestCallback", "Interface has the right QName" + ) + harness.check(iface.identifier.name, "TestCallback", "Interface has the right name") + harness.check(len(iface.members), 1, "Expect %s members" % 1) + + attr = iface.members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.ok(attr.isAttr(), "Should be an attribute") + harness.ok(not attr.isMethod(), "Attr is not an method") + harness.ok(not attr.isConst(), "Attr is not a const") + harness.check( + attr.identifier.QName(), "::TestCallback::listener", "Attr has the right QName" + ) + harness.check(attr.identifier.name, "listener", "Attr has the right name") + t = attr.type + harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type") + harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type") + harness.ok(t.isCallback(), "Attr has the right type") + + callback = results[1] + harness.ok(not callback.isConstructor(), "callback is not constructor") diff --git a/dom/bindings/parser/tests/test_callback_constructor.py b/dom/bindings/parser/tests/test_callback_constructor.py new file mode 100644 index 0000000000..ea2f46c436 --- /dev/null +++ b/dom/bindings/parser/tests/test_callback_constructor.py @@ -0,0 +1,84 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestCallbackConstructor { + attribute CallbackConstructorType? constructorAttribute; + }; + + callback constructor CallbackConstructorType = TestCallbackConstructor (unsigned long arg); + """ + ) + + results = parser.finish() + + harness.ok(True, "TestCallbackConstructor interface parsed without error.") + harness.check(len(results), 2, "Should be two productions.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), + "::TestCallbackConstructor", + "Interface has the right QName", + ) + harness.check( + iface.identifier.name, "TestCallbackConstructor", "Interface has the right name" + ) + harness.check(len(iface.members), 1, "Expect %s members" % 1) + + attr = iface.members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.ok(attr.isAttr(), "Should be an attribute") + harness.ok(not attr.isMethod(), "Attr is not an method") + harness.ok(not attr.isConst(), "Attr is not a const") + harness.check( + attr.identifier.QName(), + "::TestCallbackConstructor::constructorAttribute", + "Attr has the right QName", + ) + harness.check( + attr.identifier.name, "constructorAttribute", "Attr has the right name" + ) + t = attr.type + harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type") + harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type") + harness.ok(t.isCallback(), "Attr has the right type") + + callback = results[1] + harness.ok(callback.isConstructor(), "Callback is constructor") + + parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyTreatNonObjectAsNull] + callback constructor CallbackConstructorType = object (); + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should throw on LegacyTreatNonObjectAsNull callback constructors" + ) + + parser.reset() + threw = False + try: + parser.parse( + """ + [MOZ_CAN_RUN_SCRIPT_BOUNDARY] + callback constructor CallbackConstructorType = object (); + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should not permit MOZ_CAN_RUN_SCRIPT_BOUNDARY callback constructors" + ) diff --git a/dom/bindings/parser/tests/test_callback_interface.py b/dom/bindings/parser/tests/test_callback_interface.py new file mode 100644 index 0000000000..1396eb8a24 --- /dev/null +++ b/dom/bindings/parser/tests/test_callback_interface.py @@ -0,0 +1,103 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + callback interface TestCallbackInterface { + attribute boolean bool; + }; + """ + ) + + results = parser.finish() + + iface = results[0] + + harness.ok(iface.isCallback(), "Interface should be a callback") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestInterface { + }; + callback interface TestCallbackInterface : TestInterface { + attribute boolean bool; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow non-callback parent of callback interface") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestInterface : TestCallbackInterface { + }; + callback interface TestCallbackInterface { + attribute boolean bool; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow callback parent of non-callback interface") + + parser = parser.reset() + parser.parse( + """ + callback interface TestCallbackInterface1 { + undefined foo(); + }; + callback interface TestCallbackInterface2 { + undefined foo(DOMString arg); + undefined foo(TestCallbackInterface1 arg); + }; + callback interface TestCallbackInterface3 { + undefined foo(DOMString arg); + undefined foo(TestCallbackInterface1 arg); + static undefined bar(); + }; + callback interface TestCallbackInterface4 { + undefined foo(DOMString arg); + undefined foo(TestCallbackInterface1 arg); + static undefined bar(); + const long baz = 5; + }; + callback interface TestCallbackInterface5 { + static attribute boolean bool; + undefined foo(); + }; + callback interface TestCallbackInterface6 { + undefined foo(DOMString arg); + undefined foo(TestCallbackInterface1 arg); + undefined bar(); + }; + callback interface TestCallbackInterface7 { + static attribute boolean bool; + }; + callback interface TestCallbackInterface8 { + attribute boolean bool; + }; + callback interface TestCallbackInterface9 : TestCallbackInterface1 { + undefined foo(); + }; + callback interface TestCallbackInterface10 : TestCallbackInterface1 { + undefined bar(); + }; + """ + ) + results = parser.finish() + for (i, iface) in enumerate(results): + harness.check( + iface.isSingleOperationInterface(), + i < 4, + "Interface %s should be a single operation interface" + % iface.identifier.name, + ) diff --git a/dom/bindings/parser/tests/test_cereactions.py b/dom/bindings/parser/tests/test_cereactions.py new file mode 100644 index 0000000000..376a32b4c4 --- /dev/null +++ b/dom/bindings/parser/tests/test_cereactions.py @@ -0,0 +1,157 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions(DOMString a)] undefined foo(boolean arg2); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] with an argument") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions(DOMString b)] readonly attribute boolean bar; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] with an argument") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions] attribute boolean bar; + }; + """ + ) + + parser.finish() + except Exception as e: + harness.ok( + False, + "Shouldn't have thrown for [CEReactions] used on writable attribute. %s" + % e, + ) + threw = True + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions] undefined foo(boolean arg2); + }; + """ + ) + + parser.finish() + except Exception as e: + harness.ok( + False, + "Shouldn't have thrown for [CEReactions] used on regular operations. %s" + % e, + ) + threw = True + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions] readonly attribute boolean A; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should have thrown for [CEReactions] used on a readonly attribute" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [CEReactions] + interface Foo { + } + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] used on a interface") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions] getter any(DOMString name); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] used on a named getter") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions] legacycaller double compute(double x); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] used on a legacycaller") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + [CEReactions] stringifier DOMString (); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] used on a stringifier") diff --git a/dom/bindings/parser/tests/test_conditional_dictionary_member.py b/dom/bindings/parser/tests/test_conditional_dictionary_member.py new file mode 100644 index 0000000000..2aef8ebe8f --- /dev/null +++ b/dom/bindings/parser/tests/test_conditional_dictionary_member.py @@ -0,0 +1,128 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + dictionary Dict { + any foo; + [ChromeOnly] any bar; + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should have a dictionary") + members = results[0].members + harness.check(len(members), 2, "Should have two members") + # Note that members are ordered lexicographically, so "bar" comes + # before "foo". + harness.ok( + members[0].getExtendedAttribute("ChromeOnly"), "First member is not ChromeOnly" + ) + harness.ok( + not members[1].getExtendedAttribute("ChromeOnly"), "Second member is ChromeOnly" + ) + + parser = parser.reset() + parser.parse( + """ + dictionary Dict { + any foo; + any bar; + }; + + interface Iface { + [Constant, Cached] readonly attribute Dict dict; + }; + """ + ) + results = parser.finish() + harness.check(len(results), 2, "Should have a dictionary and an interface") + + parser = parser.reset() + exception = None + try: + parser.parse( + """ + dictionary Dict { + any foo; + [ChromeOnly] any bar; + }; + + interface Iface { + [Constant, Cached] readonly attribute Dict dict; + }; + """ + ) + results = parser.finish() + except Exception as e: + exception = e + + harness.ok(exception, "Should have thrown.") + harness.check( + exception.message, + "[Cached] and [StoreInSlot] must not be used on an attribute " + "whose type contains a [ChromeOnly] dictionary member", + "Should have thrown the right exception", + ) + + parser = parser.reset() + exception = None + try: + parser.parse( + """ + dictionary ParentDict { + [ChromeOnly] any bar; + }; + + dictionary Dict : ParentDict { + any foo; + }; + + interface Iface { + [Constant, Cached] readonly attribute Dict dict; + }; + """ + ) + results = parser.finish() + except Exception as e: + exception = e + + harness.ok(exception, "Should have thrown (2).") + harness.check( + exception.message, + "[Cached] and [StoreInSlot] must not be used on an attribute " + "whose type contains a [ChromeOnly] dictionary member", + "Should have thrown the right exception (2)", + ) + + parser = parser.reset() + exception = None + try: + parser.parse( + """ + dictionary GrandParentDict { + [ChromeOnly] any baz; + }; + + dictionary ParentDict : GrandParentDict { + any bar; + }; + + dictionary Dict : ParentDict { + any foo; + }; + + interface Iface { + [Constant, Cached] readonly attribute Dict dict; + }; + """ + ) + results = parser.finish() + except Exception as e: + exception = e + + harness.ok(exception, "Should have thrown (3).") + harness.check( + exception.message, + "[Cached] and [StoreInSlot] must not be used on an attribute " + "whose type contains a [ChromeOnly] dictionary member", + "Should have thrown the right exception (3)", + ) diff --git a/dom/bindings/parser/tests/test_const.py b/dom/bindings/parser/tests/test_const.py new file mode 100644 index 0000000000..ea597d1ed2 --- /dev/null +++ b/dom/bindings/parser/tests/test_const.py @@ -0,0 +1,96 @@ +import WebIDL + +expected = [ + ("::TestConsts::zero", "zero", "Byte", 0), + ("::TestConsts::b", "b", "Byte", -1), + ("::TestConsts::o", "o", "Octet", 2), + ("::TestConsts::s", "s", "Short", -3), + ("::TestConsts::us", "us", "UnsignedShort", 4), + ("::TestConsts::l", "l", "Long", -5), + ("::TestConsts::ul", "ul", "UnsignedLong", 6), + ("::TestConsts::ull", "ull", "UnsignedLongLong", 7), + ("::TestConsts::ll", "ll", "LongLong", -8), + ("::TestConsts::t", "t", "Boolean", True), + ("::TestConsts::f", "f", "Boolean", False), + ("::TestConsts::fl", "fl", "Float", 0.2), + ("::TestConsts::db", "db", "Double", 0.2), + ("::TestConsts::ufl", "ufl", "UnrestrictedFloat", 0.2), + ("::TestConsts::udb", "udb", "UnrestrictedDouble", 0.2), + ("::TestConsts::fli", "fli", "Float", 2), + ("::TestConsts::dbi", "dbi", "Double", 2), + ("::TestConsts::ufli", "ufli", "UnrestrictedFloat", 2), + ("::TestConsts::udbi", "udbi", "UnrestrictedDouble", 2), +] + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestConsts { + const byte zero = 0; + const byte b = -1; + const octet o = 2; + const short s = -3; + const unsigned short us = 0x4; + const long l = -0X5; + const unsigned long ul = 6; + const unsigned long long ull = 7; + const long long ll = -010; + const boolean t = true; + const boolean f = false; + const float fl = 0.2; + const double db = 0.2; + const unrestricted float ufl = 0.2; + const unrestricted double udb = 0.2; + const float fli = 2; + const double dbi = 2; + const unrestricted float ufli = 2; + const unrestricted double udbi = 2; + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestConsts interface parsed without error.") + harness.check(len(results), 1, "Should be one production.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), "::TestConsts", "Interface has the right QName" + ) + harness.check(iface.identifier.name, "TestConsts", "Interface has the right name") + harness.check( + len(iface.members), len(expected), "Expect %s members" % len(expected) + ) + + for (const, (QName, name, type, value)) in zip(iface.members, expected): + harness.ok(isinstance(const, WebIDL.IDLConst), "Should be an IDLConst") + harness.ok(const.isConst(), "Const is a const") + harness.ok(not const.isAttr(), "Const is not an attr") + harness.ok(not const.isMethod(), "Const is not a method") + harness.check(const.identifier.QName(), QName, "Const has the right QName") + harness.check(const.identifier.name, name, "Const has the right name") + harness.check(str(const.type), type, "Const has the right type") + harness.ok(const.type.isPrimitive(), "All consts should be primitive") + harness.check( + str(const.value.type), + str(const.type), + "Const's value has the same type as the type", + ) + harness.check(const.value.value, value, "Const value has the right value.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestConsts { + const boolean? zero = 0; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Nullable types are not allowed for consts.") diff --git a/dom/bindings/parser/tests/test_constructor.py b/dom/bindings/parser/tests/test_constructor.py new file mode 100644 index 0000000000..1e1382336c --- /dev/null +++ b/dom/bindings/parser/tests/test_constructor.py @@ -0,0 +1,596 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + def checkArgument(argument, QName, name, type, optional, variadic): + harness.ok(isinstance(argument, WebIDL.IDLArgument), "Should be an IDLArgument") + harness.check( + argument.identifier.QName(), QName, "Argument has the right QName" + ) + harness.check(argument.identifier.name, name, "Argument has the right name") + harness.check(str(argument.type), type, "Argument has the right return type") + harness.check( + argument.optional, optional, "Argument has the right optional value" + ) + harness.check( + argument.variadic, variadic, "Argument has the right variadic value" + ) + + def checkMethod( + method, + QName, + name, + signatures, + static=True, + getter=False, + setter=False, + deleter=False, + legacycaller=False, + stringifier=False, + chromeOnly=False, + htmlConstructor=False, + secureContext=False, + pref=None, + func=None, + ): + harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") + harness.ok(method.isMethod(), "Method is a method") + harness.ok(not method.isAttr(), "Method is not an attr") + harness.ok(not method.isConst(), "Method is not a const") + harness.check(method.identifier.QName(), QName, "Method has the right QName") + harness.check(method.identifier.name, name, "Method has the right name") + harness.check(method.isStatic(), static, "Method has the correct static value") + harness.check(method.isGetter(), getter, "Method has the correct getter value") + harness.check(method.isSetter(), setter, "Method has the correct setter value") + harness.check( + method.isDeleter(), deleter, "Method has the correct deleter value" + ) + harness.check( + method.isLegacycaller(), + legacycaller, + "Method has the correct legacycaller value", + ) + harness.check( + method.isStringifier(), + stringifier, + "Method has the correct stringifier value", + ) + harness.check( + method.getExtendedAttribute("ChromeOnly") is not None, + chromeOnly, + "Method has the correct value for ChromeOnly", + ) + harness.check( + method.isHTMLConstructor(), + htmlConstructor, + "Method has the correct htmlConstructor value", + ) + harness.check( + len(method.signatures()), + len(signatures), + "Method has the correct number of signatures", + ) + harness.check( + method.getExtendedAttribute("Pref"), + pref, + "Method has the correct pref value", + ) + harness.check( + method.getExtendedAttribute("Func"), + func, + "Method has the correct func value", + ) + harness.check( + method.getExtendedAttribute("SecureContext") is not None, + secureContext, + "Method has the correct SecureContext value", + ) + + sigpairs = zip(method.signatures(), signatures) + for (gotSignature, expectedSignature) in sigpairs: + (gotRetType, gotArgs) = gotSignature + (expectedRetType, expectedArgs) = expectedSignature + + harness.check( + str(gotRetType), expectedRetType, "Method has the expected return type." + ) + + for i in range(0, len(gotArgs)): + (QName, name, type, optional, variadic) = expectedArgs[i] + checkArgument(gotArgs[i], QName, name, type, optional, variadic) + + def checkResults(results): + harness.check(len(results), 3, "Should be three productions") + harness.ok( + isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface" + ) + harness.ok( + isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface" + ) + harness.ok( + isinstance(results[2], WebIDL.IDLInterface), "Should be an IDLInterface" + ) + + checkMethod( + results[0].ctor(), + "::TestConstructorNoArgs::constructor", + "constructor", + [("TestConstructorNoArgs (Wrapper)", [])], + ) + harness.check( + len(results[0].members), 0, "TestConstructorNoArgs should not have members" + ) + checkMethod( + results[1].ctor(), + "::TestConstructorWithArgs::constructor", + "constructor", + [ + ( + "TestConstructorWithArgs (Wrapper)", + [ + ( + "::TestConstructorWithArgs::constructor::name", + "name", + "String", + False, + False, + ) + ], + ) + ], + ) + harness.check( + len(results[1].members), + 0, + "TestConstructorWithArgs should not have members", + ) + checkMethod( + results[2].ctor(), + "::TestConstructorOverloads::constructor", + "constructor", + [ + ( + "TestConstructorOverloads (Wrapper)", + [ + ( + "::TestConstructorOverloads::constructor::foo", + "foo", + "Object", + False, + False, + ) + ], + ), + ( + "TestConstructorOverloads (Wrapper)", + [ + ( + "::TestConstructorOverloads::constructor::bar", + "bar", + "Boolean", + False, + False, + ) + ], + ), + ], + ) + harness.check( + len(results[2].members), + 0, + "TestConstructorOverloads should not have members", + ) + + parser.parse( + """ + interface TestConstructorNoArgs { + constructor(); + }; + + interface TestConstructorWithArgs { + constructor(DOMString name); + }; + + interface TestConstructorOverloads { + constructor(object foo); + constructor(boolean bar); + }; + """ + ) + results = parser.finish() + checkResults(results) + + parser = parser.reset() + parser.parse( + """ + interface TestPrefConstructor { + [Pref="dom.webidl.test1"] constructor(); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + + checkMethod( + results[0].ctor(), + "::TestPrefConstructor::constructor", + "constructor", + [("TestPrefConstructor (Wrapper)", [])], + pref=["dom.webidl.test1"], + ) + + parser = parser.reset() + parser.parse( + """ + interface TestChromeOnlyConstructor { + [ChromeOnly] constructor(); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + + checkMethod( + results[0].ctor(), + "::TestChromeOnlyConstructor::constructor", + "constructor", + [("TestChromeOnlyConstructor (Wrapper)", [])], + chromeOnly=True, + ) + + parser = parser.reset() + parser.parse( + """ + interface TestSCConstructor { + [SecureContext] constructor(); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + + checkMethod( + results[0].ctor(), + "::TestSCConstructor::constructor", + "constructor", + [("TestSCConstructor (Wrapper)", [])], + secureContext=True, + ) + + parser = parser.reset() + parser.parse( + """ + interface TestFuncConstructor { + [Func="Document::IsWebAnimationsEnabled"] constructor(); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + + checkMethod( + results[0].ctor(), + "::TestFuncConstructor::constructor", + "constructor", + [("TestFuncConstructor (Wrapper)", [])], + func=["Document::IsWebAnimationsEnabled"], + ) + + parser = parser.reset() + parser.parse( + ( + "\n" + " interface TestPrefChromeOnlySCFuncConstructor {\n" + ' [ChromeOnly, Pref="dom.webidl.test1", SecureContext, ' + 'Func="Document::IsWebAnimationsEnabled"]\n' + " constructor();\n" + " };\n" + ) + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + + checkMethod( + results[0].ctor(), + "::TestPrefChromeOnlySCFuncConstructor::constructor", + "constructor", + [("TestPrefChromeOnlySCFuncConstructor (Wrapper)", [])], + func=["Document::IsWebAnimationsEnabled"], + pref=["dom.webidl.test1"], + chromeOnly=True, + secureContext=True, + ) + + parser = parser.reset() + parser.parse( + """ + interface TestHTMLConstructor { + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + + checkMethod( + results[0].ctor(), + "::TestHTMLConstructor::constructor", + "constructor", + [("TestHTMLConstructor (Wrapper)", [])], + htmlConstructor=True, + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestChromeOnlyConstructor { + constructor() + [ChromeOnly] constructor(DOMString a); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have both a constructor and a ChromeOnly constructor") + + # Test HTMLConstructor with argument + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorWithArgs { + [HTMLConstructor] constructor(DOMString a); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "HTMLConstructor should take no argument") + + # Test HTMLConstructor on a callback interface + parser = parser.reset() + threw = False + try: + parser.parse( + """ + callback interface TestHTMLConstructorOnCallbackInterface { + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "HTMLConstructor can't be used on a callback interface") + + # Test HTMLConstructor and constructor operation + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + constructor(); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have both a constructor and a HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + [Throws] + constructor(); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have both a throwing constructor and a HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have both a HTMLConstructor and a constructor operation") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + [Throws] + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Can't have both a HTMLConstructor and a throwing constructor " "operation", + ) + + # Test HTMLConstructor and [ChromeOnly] constructor operation + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + [ChromeOnly] + constructor(); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have both a ChromeOnly constructor and a HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + [Throws, ChromeOnly] + constructor(); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Can't have both a throwing chromeonly constructor and a " "HTMLConstructor", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + [ChromeOnly] + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Can't have both a HTMLConstructor and a chromeonly constructor " "operation", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestHTMLConstructorAndConstructor { + [Throws, ChromeOnly] + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Can't have both a HTMLConstructor and a throwing chromeonly " + "constructor operation", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyNoInterfaceObject] + interface InterfaceWithoutInterfaceObject { + constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Can't have a constructor operation on a [LegacyNoInterfaceObject] " + "interface", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface InterfaceWithPartial { + }; + + partial interface InterfaceWithPartial { + constructor(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have a constructor operation on a partial interface") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface InterfaceWithMixin { + }; + + interface mixin Mixin { + constructor(); + }; + + InterfaceWithMixin includes Mixin + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Can't have a constructor operation on a mixin") diff --git a/dom/bindings/parser/tests/test_constructor_global.py b/dom/bindings/parser/tests/test_constructor_global.py new file mode 100644 index 0000000000..74c81699e3 --- /dev/null +++ b/dom/bindings/parser/tests/test_constructor_global.py @@ -0,0 +1,69 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + [Global, Exposed=TestConstructorGlobal] + interface TestConstructorGlobal { + constructor(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=TestLegacyFactoryFunctionGlobal, + LegacyFactoryFunction=FooBar] + interface TestLegacyFactoryFunctionGlobal { + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyFactoryFunction=FooBar, Global, + Exposed=TestLegacyFactoryFunctionGlobal] + interface TestLegacyFactoryFunctionGlobal { + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=TestHTMLConstructorGlobal] + interface TestHTMLConstructorGlobal { + [HTMLConstructor] constructor(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_constructor_no_interface_object.py b/dom/bindings/parser/tests/test_constructor_no_interface_object.py new file mode 100644 index 0000000000..513a9ab3f8 --- /dev/null +++ b/dom/bindings/parser/tests/test_constructor_no_interface_object.py @@ -0,0 +1,47 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + [LegacyNoInterfaceObject] + interface TestConstructorLegacyNoInterfaceObject { + constructor(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + + parser.parse( + """ + [LegacyNoInterfaceObject, LegacyFactoryFunction=FooBar] + interface TestLegacyFactoryFunctionLegacyNoInterfaceObject { + }; + """ + ) + + # Test HTMLConstructor and LegacyNoInterfaceObject + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + [LegacyNoInterfaceObject] + interface TestHTMLConstructorLegacyNoInterfaceObject { + [HTMLConstructor] constructor(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_deduplicate.py b/dom/bindings/parser/tests/test_deduplicate.py new file mode 100644 index 0000000000..2308f6201e --- /dev/null +++ b/dom/bindings/parser/tests/test_deduplicate.py @@ -0,0 +1,17 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface Foo; + interface Bar; + interface Foo; + """ + ) + + results = parser.finish() + + # There should be no duplicate interfaces in the result. + expectedNames = sorted(["Foo", "Bar"]) + actualNames = sorted(map(lambda iface: iface.identifier.name, results)) + harness.check( + actualNames, expectedNames, "Parser shouldn't output duplicate names." + ) diff --git a/dom/bindings/parser/tests/test_dictionary.py b/dom/bindings/parser/tests/test_dictionary.py new file mode 100644 index 0000000000..26411b96d7 --- /dev/null +++ b/dom/bindings/parser/tests/test_dictionary.py @@ -0,0 +1,875 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + dictionary Dict2 : Dict1 { + long child = 5; + Dict1 aaandAnother; + }; + dictionary Dict1 { + long parent; + double otherParent; + }; + """ + ) + results = parser.finish() + + dict1 = results[1] + dict2 = results[0] + + harness.check(len(dict1.members), 2, "Dict1 has two members") + harness.check(len(dict2.members), 2, "Dict2 has four members") + + harness.check( + dict1.members[0].identifier.name, "otherParent", "'o' comes before 'p'" + ) + harness.check( + dict1.members[1].identifier.name, "parent", "'o' really comes before 'p'" + ) + harness.check( + dict2.members[0].identifier.name, "aaandAnother", "'a' comes before 'c'" + ) + harness.check( + dict2.members[1].identifier.name, "child", "'a' really comes before 'c'" + ) + + # Test partial dictionary. + parser = parser.reset() + parser.parse( + """ + dictionary A { + long c; + long g; + }; + partial dictionary A { + long h; + long d; + }; + """ + ) + results = parser.finish() + + dict1 = results[0] + harness.check(len(dict1.members), 4, "Dict1 has four members") + harness.check(dict1.members[0].identifier.name, "c", "c should be first") + harness.check(dict1.members[1].identifier.name, "d", "d should come after c") + harness.check(dict1.members[2].identifier.name, "g", "g should come after d") + harness.check(dict1.members[3].identifier.name, "h", "h should be last") + + # Now reset our parser + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Dict { + long prop = 5; + long prop; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow name duplication in a dictionary") + + # Test no name duplication across normal and partial dictionary. + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + long prop = 5; + }; + partial dictionary A { + long prop; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should not allow name duplication across normal and partial dictionary" + ) + + # Now reset our parser again + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Dict1 : Dict2 { + long prop = 5; + }; + dictionary Dict2 : Dict3 { + long prop2; + }; + dictionary Dict3 { + double prop; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should not allow name duplication in a dictionary and " "its ancestor" + ) + + # More reset + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface {}; + dictionary Dict : Iface { + long prop; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow non-dictionary parents for dictionaries") + + # Even more reset + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A : B {}; + dictionary B : A {}; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow cycles in dictionary inheritance chains") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + [LegacyNullToEmptyString] DOMString foo; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should not allow [LegacyNullToEmptyString] on dictionary members" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(A arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Trailing dictionary arg must be optional") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional A arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Trailing dictionary arg must have a default value") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo((A or DOMString) arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Trailing union arg containing a dictionary must be optional") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (A or DOMString) arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Trailing union arg containing a dictionary must have a default value" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(A arg1, optional long arg2); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Dictionary arg followed by optional arg must be optional") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional A arg1, optional long arg2); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Dictionary arg followed by optional arg must have default value") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(A arg1, optional long arg2, long arg3); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + not threw, + "Dictionary arg followed by non-optional arg doesn't have to be optional", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo((A or DOMString) arg1, optional long arg2); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Union arg containing dictionary followed by optional arg must " "be optional", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (A or DOMString) arg1, optional long arg2); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Union arg containing dictionary followed by optional arg must " + "have a default value", + ) + + parser = parser.reset() + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(A arg1, long arg2); + }; + """ + ) + parser.finish() + harness.ok(True, "Dictionary arg followed by required arg can be required") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional A? arg1 = {}); + }; + """ + ) + parser.finish() + except Exception as x: + threw = x + + harness.ok(threw, "Optional dictionary arg must not be nullable") + harness.ok( + "nullable" in str(threw), + "Must have the expected exception for optional nullable dictionary arg", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + required long x; + }; + interface X { + undefined doFoo(A? arg1); + }; + """ + ) + parser.finish() + except Exception as x: + threw = x + + harness.ok(threw, "Required dictionary arg must not be nullable") + harness.ok( + "nullable" in str(threw), + "Must have the expected exception for required nullable " "dictionary arg", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (A or long)? arg1 = {}); + }; + """ + ) + parser.finish() + except Exception as x: + threw = x + + harness.ok(threw, "Dictionary arg must not be in an optional nullable union") + harness.ok( + "nullable" in str(threw), + "Must have the expected exception for optional nullable union " + "arg containing dictionary", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + required long x; + }; + interface X { + undefined doFoo((A or long)? arg1); + }; + """ + ) + parser.finish() + except Exception as x: + threw = x + + harness.ok(threw, "Dictionary arg must not be in a required nullable union") + harness.ok( + "nullable" in str(threw), + "Must have the expected exception for required nullable union " + "arg containing dictionary", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(sequence<A?> arg1); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(not threw, "Nullable union should be allowed in a sequence argument") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (A or long?) arg1); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Dictionary must not be in a union with a nullable type") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (long? or A) arg1); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "A nullable type must not be in a union with a dictionary") + + parser = parser.reset() + parser.parse( + """ + dictionary A { + }; + interface X { + A? doFoo(); + }; + """ + ) + parser.finish() + harness.ok(True, "Dictionary return value can be nullable") + + parser = parser.reset() + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional A arg = {}); + }; + """ + ) + parser.finish() + harness.ok(True, "Dictionary arg should actually parse") + + parser = parser.reset() + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (A or DOMString) arg = {}); + }; + """ + ) + parser.finish() + harness.ok(True, "Union arg containing a dictionary should actually parse") + + parser = parser.reset() + parser.parse( + """ + dictionary A { + }; + interface X { + undefined doFoo(optional (A or DOMString) arg = "abc"); + }; + """ + ) + parser.finish() + harness.ok( + True, + "Union arg containing a dictionary with string default should actually parse", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + Foo foo; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Member type must not be its Dictionary.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo3 : Foo { + short d; + }; + + dictionary Foo2 : Foo3 { + boolean c; + }; + + dictionary Foo1 : Foo2 { + long a; + }; + + dictionary Foo { + Foo1 b; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Member type must not be a Dictionary that " "inherits from its Dictionary.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + (Foo or DOMString)[]? b; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Member type must not be a Nullable type " + "whose inner type includes its Dictionary.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + (DOMString or Foo) b; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Member type must not be a Union type, one of " + "whose member types includes its Dictionary.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + sequence<sequence<sequence<Foo>>> c; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Member type must not be a Sequence type " + "whose element type includes its Dictionary.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + (DOMString or Foo)[] d; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Member type must not be an Array type " + "whose element type includes its Dictionary.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + Foo1 b; + }; + + dictionary Foo3 { + Foo d; + }; + + dictionary Foo2 : Foo3 { + short c; + }; + + dictionary Foo1 : Foo2 { + long a; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Member type must not be a Dictionary, one of whose " + "members or inherited members has a type that includes " + "its Dictionary.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + }; + + dictionary Bar { + Foo? d; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Member type must not be a nullable dictionary") + + parser = parser.reset() + parser.parse( + """ + dictionary Foo { + unrestricted float urFloat = 0; + unrestricted float urFloat2 = 1.1; + unrestricted float urFloat3 = -1.1; + unrestricted float? urFloat4 = null; + unrestricted float infUrFloat = Infinity; + unrestricted float negativeInfUrFloat = -Infinity; + unrestricted float nanUrFloat = NaN; + + unrestricted double urDouble = 0; + unrestricted double urDouble2 = 1.1; + unrestricted double urDouble3 = -1.1; + unrestricted double? urDouble4 = null; + unrestricted double infUrDouble = Infinity; + unrestricted double negativeInfUrDouble = -Infinity; + unrestricted double nanUrDouble = NaN; + }; + """ + ) + parser.finish() + harness.ok(True, "Parsing default values for unrestricted types succeeded.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + double f = Infinity; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to Infinity") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + double f = -Infinity; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to -Infinity") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + double f = NaN; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to NaN") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + float f = Infinity; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to Infinity") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + float f = -Infinity; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to -Infinity") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + float f = NaN; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to NaN") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Foo { + long module; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(not threw, "Should be able to use 'module' as a dictionary member name") diff --git a/dom/bindings/parser/tests/test_distinguishability.py b/dom/bindings/parser/tests/test_distinguishability.py new file mode 100644 index 0000000000..8c938d88a3 --- /dev/null +++ b/dom/bindings/parser/tests/test_distinguishability.py @@ -0,0 +1,421 @@ +def firstArgType(method): + return method.signatures()[0][1][0].type + + +def WebIDLTest(parser, harness): + parser.parse( + """ + // Give our dictionary a required member so we don't need to + // mess with optional and default values. + dictionary Dict { + required long member; + }; + callback interface Foo { + }; + interface Bar { + // Bit of a pain to get things that have dictionary types + undefined passDict(Dict arg); + undefined passFoo(Foo arg); + undefined passNullableUnion((object? or DOMString) arg); + undefined passNullable(Foo? arg); + }; + """ + ) + results = parser.finish() + + iface = results[2] + harness.ok(iface.isInterface(), "Should have interface") + dictMethod = iface.members[0] + ifaceMethod = iface.members[1] + nullableUnionMethod = iface.members[2] + nullableIfaceMethod = iface.members[3] + + dictType = firstArgType(dictMethod) + ifaceType = firstArgType(ifaceMethod) + + harness.ok(dictType.isDictionary(), "Should have dictionary type") + harness.ok(ifaceType.isInterface(), "Should have interface type") + harness.ok(ifaceType.isCallbackInterface(), "Should have callback interface type") + + harness.ok( + not dictType.isDistinguishableFrom(ifaceType), + "Dictionary not distinguishable from callback interface", + ) + harness.ok( + not ifaceType.isDistinguishableFrom(dictType), + "Callback interface not distinguishable from dictionary", + ) + + nullableUnionType = firstArgType(nullableUnionMethod) + nullableIfaceType = firstArgType(nullableIfaceMethod) + + harness.ok(nullableUnionType.isUnion(), "Should have union type") + harness.ok(nullableIfaceType.isInterface(), "Should have interface type") + harness.ok(nullableIfaceType.nullable(), "Should have nullable type") + + harness.ok( + not nullableUnionType.isDistinguishableFrom(nullableIfaceType), + "Nullable type not distinguishable from union with nullable " "member type", + ) + harness.ok( + not nullableIfaceType.isDistinguishableFrom(nullableUnionType), + "Union with nullable member type not distinguishable from " "nullable type", + ) + + parser = parser.reset() + parser.parse( + """ + interface TestIface { + undefined passKid(Kid arg); + undefined passParent(Parent arg); + undefined passGrandparent(Grandparent arg); + undefined passUnrelated1(Unrelated1 arg); + undefined passUnrelated2(Unrelated2 arg); + undefined passArrayBuffer(ArrayBuffer arg); + undefined passArrayBuffer(ArrayBufferView arg); + }; + + interface Kid : Parent {}; + interface Parent : Grandparent {}; + interface Grandparent {}; + interface Unrelated1 {}; + interface Unrelated2 {}; + """ + ) + results = parser.finish() + + iface = results[0] + harness.ok(iface.isInterface(), "Should have interface") + argTypes = [firstArgType(method) for method in iface.members] + unrelatedTypes = [firstArgType(method) for method in iface.members[-3:]] + + for type1 in argTypes: + for type2 in argTypes: + distinguishable = type1 is not type2 and ( + type1 in unrelatedTypes or type2 in unrelatedTypes + ) + + harness.check( + type1.isDistinguishableFrom(type2), + distinguishable, + "Type %s should %sbe distinguishable from type %s" + % (type1, "" if distinguishable else "not ", type2), + ) + harness.check( + type2.isDistinguishableFrom(type1), + distinguishable, + "Type %s should %sbe distinguishable from type %s" + % (type2, "" if distinguishable else "not ", type1), + ) + + parser = parser.reset() + parser.parse( + """ + interface Dummy {}; + interface TestIface { + undefined method(long arg1, TestIface arg2); + undefined method(long arg1, long arg2); + undefined method(long arg1, Dummy arg2); + undefined method(DOMString arg1, DOMString arg2, DOMString arg3); + }; + """ + ) + results = parser.finish() + harness.check(len(results[1].members), 1, "Should look like we have one method") + harness.check( + len(results[1].members[0].signatures()), 4, "Should have four signatures" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Dummy {}; + interface TestIface { + undefined method(long arg1, TestIface arg2); + undefined method(long arg1, long arg2); + undefined method(any arg1, Dummy arg2); + undefined method(DOMString arg1, DOMString arg2, DOMString arg3); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should throw when args before the distinguishing arg are not " + "all the same type", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Dummy {}; + interface TestIface { + undefined method(long arg1, TestIface arg2); + undefined method(long arg1, long arg2); + undefined method(any arg1, DOMString arg2); + undefined method(DOMString arg1, DOMString arg2, DOMString arg3); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should throw when there is no distinguishing index") + + # Now let's test our whole distinguishability table + argTypes = [ + "long", + "short", + "long?", + "short?", + "boolean", + "boolean?", + "undefined", + "undefined?", + "DOMString", + "ByteString", + "UTF8String", + "Enum", + "Enum2", + "Interface", + "Interface?", + "AncestorInterface", + "UnrelatedInterface", + "CallbackInterface", + "CallbackInterface?", + "CallbackInterface2", + "object", + "Callback", + "Callback2", + "Dict", + "Dict2", + "sequence<long>", + "sequence<short>", + "record<DOMString, object>", + "record<USVString, Dict>", + "record<ByteString, long>", + "record<UTF8String, long>", + "any", + "Promise<any>", + "Promise<any>?", + "USVString", + "JSString", + "ArrayBuffer", + "ArrayBufferView", + "Uint8Array", + "Uint16Array", + "(long or Callback)", + "(long or Dict)", + ] + + # Try to categorize things a bit to keep list lengths down + def allBut(list1, list2): + return [ + a + for a in list1 + if a not in list2 + and (a != "any" and a != "Promise<any>" and a != "Promise<any>?") + ] + + unions = ["(long or Callback)", "(long or Dict)"] + numerics = ["long", "short", "long?", "short?"] + booleans = ["boolean", "boolean?"] + undefineds = ["undefined", "undefined?"] + primitives = numerics + booleans + nonNumerics = allBut(argTypes, numerics + unions) + nonBooleans = allBut(argTypes, booleans) + strings = [ + "DOMString", + "ByteString", + "Enum", + "Enum2", + "USVString", + "JSString", + "UTF8String", + ] + nonStrings = allBut(argTypes, strings) + nonObjects = undefineds + primitives + strings + bufferSourceTypes = ["ArrayBuffer", "ArrayBufferView", "Uint8Array", "Uint16Array"] + interfaces = [ + "Interface", + "Interface?", + "AncestorInterface", + "UnrelatedInterface", + ] + bufferSourceTypes + nullables = [ + "long?", + "short?", + "boolean?", + "undefined?", + "Interface?", + "CallbackInterface?", + "Dict", + "Dict2", + "Date?", + "any", + "Promise<any>?", + ] + allBut(unions, ["(long or Callback)"]) + sequences = ["sequence<long>", "sequence<short>"] + nonUserObjects = nonObjects + interfaces + sequences + otherObjects = allBut(argTypes, nonUserObjects + ["object"]) + notRelatedInterfaces = ( + nonObjects + + ["UnrelatedInterface"] + + otherObjects + + sequences + + bufferSourceTypes + ) + records = [ + "record<DOMString, object>", + "record<USVString, Dict>", + "record<ByteString, long>", + "record<UTF8String, long>", + ] # JSString not supported in records + dictionaryLike = ( + [ + "Dict", + "Dict2", + "CallbackInterface", + "CallbackInterface?", + "CallbackInterface2", + ] + + records + + allBut(unions, ["(long or Callback)"]) + ) + + # Build a representation of the distinguishability table as a dict + # of dicts, holding True values where needed, holes elsewhere. + data = dict() + for type in argTypes: + data[type] = dict() + + def setDistinguishable(type, types): + for other in types: + data[type][other] = True + + setDistinguishable("long", nonNumerics) + setDistinguishable("short", nonNumerics) + setDistinguishable("long?", allBut(nonNumerics, nullables)) + setDistinguishable("short?", allBut(nonNumerics, nullables)) + setDistinguishable("boolean", nonBooleans) + setDistinguishable("boolean?", allBut(nonBooleans, nullables)) + setDistinguishable("undefined", allBut(argTypes, undefineds + dictionaryLike)) + setDistinguishable( + "undefined?", allBut(argTypes, undefineds + dictionaryLike + nullables) + ) + setDistinguishable("DOMString", nonStrings) + setDistinguishable("ByteString", nonStrings) + setDistinguishable("UTF8String", nonStrings) + setDistinguishable("USVString", nonStrings) + setDistinguishable("JSString", nonStrings) + setDistinguishable("Enum", nonStrings) + setDistinguishable("Enum2", nonStrings) + setDistinguishable("Interface", notRelatedInterfaces) + setDistinguishable("Interface?", allBut(notRelatedInterfaces, nullables)) + setDistinguishable("AncestorInterface", notRelatedInterfaces) + setDistinguishable( + "UnrelatedInterface", allBut(argTypes, ["object", "UnrelatedInterface"]) + ) + setDistinguishable("CallbackInterface", allBut(nonUserObjects, undefineds)) + setDistinguishable( + "CallbackInterface?", allBut(nonUserObjects, nullables + undefineds) + ) + setDistinguishable("CallbackInterface2", allBut(nonUserObjects, undefineds)) + setDistinguishable("object", nonObjects) + setDistinguishable("Callback", nonUserObjects) + setDistinguishable("Callback2", nonUserObjects) + setDistinguishable("Dict", allBut(nonUserObjects, nullables + undefineds)) + setDistinguishable("Dict2", allBut(nonUserObjects, nullables + undefineds)) + setDistinguishable("sequence<long>", allBut(argTypes, sequences + ["object"])) + setDistinguishable("sequence<short>", allBut(argTypes, sequences + ["object"])) + setDistinguishable("record<DOMString, object>", allBut(nonUserObjects, undefineds)) + setDistinguishable("record<USVString, Dict>", allBut(nonUserObjects, undefineds)) + # JSString not supported in records + setDistinguishable("record<ByteString, long>", allBut(nonUserObjects, undefineds)) + setDistinguishable("record<UTF8String, long>", allBut(nonUserObjects, undefineds)) + setDistinguishable("any", []) + setDistinguishable("Promise<any>", []) + setDistinguishable("Promise<any>?", []) + setDistinguishable("ArrayBuffer", allBut(argTypes, ["ArrayBuffer", "object"])) + setDistinguishable( + "ArrayBufferView", + allBut(argTypes, ["ArrayBufferView", "Uint8Array", "Uint16Array", "object"]), + ) + setDistinguishable( + "Uint8Array", allBut(argTypes, ["ArrayBufferView", "Uint8Array", "object"]) + ) + setDistinguishable( + "Uint16Array", allBut(argTypes, ["ArrayBufferView", "Uint16Array", "object"]) + ) + setDistinguishable("(long or Callback)", allBut(nonUserObjects, numerics)) + setDistinguishable( + "(long or Dict)", allBut(nonUserObjects, numerics + nullables + undefineds) + ) + + def areDistinguishable(type1, type2): + return data[type1].get(type2, False) + + def checkDistinguishability(parser, type1, type2): + idlTemplate = """ + enum Enum { "a", "b" }; + enum Enum2 { "c", "d" }; + interface Interface : AncestorInterface {}; + interface AncestorInterface {}; + interface UnrelatedInterface {}; + callback interface CallbackInterface {}; + callback interface CallbackInterface2 {}; + callback Callback = any(); + callback Callback2 = long(short arg); + // Give our dictionaries required members so we don't need to + // mess with optional and default values. + dictionary Dict { required long member; }; + dictionary Dict2 { required long member; }; + interface TestInterface {%s + }; + """ + if type1 in undefineds or type2 in undefineds: + methods = """ + (%s or %s) myMethod();""" % ( + type1, + type2, + ) + else: + methodTemplate = """ + undefined myMethod(%s arg);""" + methods = (methodTemplate % type1) + (methodTemplate % type2) + idl = idlTemplate % methods + + parser = parser.reset() + threw = False + try: + parser.parse(idl) + parser.finish() + except Exception: + threw = True + + if areDistinguishable(type1, type2): + harness.ok( + not threw, + "Should not throw for '%s' and '%s' because they are distinguishable" + % (type1, type2), + ) + else: + harness.ok( + threw, + "Should throw for '%s' and '%s' because they are not distinguishable" + % (type1, type2), + ) + + # Enumerate over everything in both orders, since order matters in + # terms of our implementation of distinguishability checks + for type1 in argTypes: + for type2 in argTypes: + checkDistinguishability(parser, type1, type2) diff --git a/dom/bindings/parser/tests/test_double_null.py b/dom/bindings/parser/tests/test_double_null.py new file mode 100644 index 0000000000..5160a53727 --- /dev/null +++ b/dom/bindings/parser/tests/test_double_null.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface DoubleNull { + attribute byte?? foo; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_duplicate_qualifiers.py b/dom/bindings/parser/tests/test_duplicate_qualifiers.py new file mode 100644 index 0000000000..2b37cdc0fb --- /dev/null +++ b/dom/bindings/parser/tests/test_duplicate_qualifiers.py @@ -0,0 +1,64 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface DuplicateQualifiers1 { + getter getter byte foo(unsigned long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface DuplicateQualifiers2 { + setter setter byte foo(unsigned long index, byte value); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface DuplicateQualifiers4 { + deleter deleter byte foo(unsigned long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface DuplicateQualifiers5 { + getter deleter getter byte foo(unsigned long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_empty_enum.py b/dom/bindings/parser/tests/test_empty_enum.py new file mode 100644 index 0000000000..cd6b985dc9 --- /dev/null +++ b/dom/bindings/parser/tests/test_empty_enum.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + try: + parser.parse( + """ + enum TestEmptyEnum { + }; + """ + ) + + harness.ok(False, "Should have thrown!") + except Exception: + harness.ok(True, "Parsing TestEmptyEnum enum should fail") + + parser.finish() diff --git a/dom/bindings/parser/tests/test_empty_sequence_default_value.py b/dom/bindings/parser/tests/test_empty_sequence_default_value.py new file mode 100644 index 0000000000..da0b3add82 --- /dev/null +++ b/dom/bindings/parser/tests/test_empty_sequence_default_value.py @@ -0,0 +1,54 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface X { + const sequence<long> foo = []; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Constant cannot have [] as a default value") + + parser = parser.reset() + + parser.parse( + """ + interface X { + undefined foo(optional sequence<long> arg = []); + }; + """ + ) + results = parser.finish() + + harness.ok( + isinstance( + results[0].members[0].signatures()[0][1][0].defaultValue, + WebIDL.IDLEmptySequenceValue, + ), + "Should have IDLEmptySequenceValue as default value of argument", + ) + + parser = parser.reset() + + parser.parse( + """ + dictionary X { + sequence<long> foo = []; + }; + """ + ) + results = parser.finish() + + harness.ok( + isinstance(results[0].members[0].defaultValue, WebIDL.IDLEmptySequenceValue), + "Should have IDLEmptySequenceValue as default value of " "dictionary member", + ) diff --git a/dom/bindings/parser/tests/test_enum.py b/dom/bindings/parser/tests/test_enum.py new file mode 100644 index 0000000000..d7305a708f --- /dev/null +++ b/dom/bindings/parser/tests/test_enum.py @@ -0,0 +1,107 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + enum TestEnum { + "", + "foo", + "bar" + }; + + interface TestEnumInterface { + TestEnum doFoo(boolean arg); + readonly attribute TestEnum foo; + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestEnumInterfaces interface parsed without error.") + harness.check(len(results), 2, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLEnum), "Should be an IDLEnum") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface") + + enum = results[0] + harness.check(enum.identifier.QName(), "::TestEnum", "Enum has the right QName") + harness.check(enum.identifier.name, "TestEnum", "Enum has the right name") + harness.check(enum.values(), ["", "foo", "bar"], "Enum has the right values") + + iface = results[1] + + harness.check( + iface.identifier.QName(), "::TestEnumInterface", "Interface has the right QName" + ) + harness.check( + iface.identifier.name, "TestEnumInterface", "Interface has the right name" + ) + harness.check(iface.parent, None, "Interface has no parent") + + members = iface.members + harness.check(len(members), 2, "Should be one production") + harness.ok(isinstance(members[0], WebIDL.IDLMethod), "Should be an IDLMethod") + method = members[0] + harness.check( + method.identifier.QName(), + "::TestEnumInterface::doFoo", + "Method has correct QName", + ) + harness.check(method.identifier.name, "doFoo", "Method has correct name") + + signatures = method.signatures() + harness.check(len(signatures), 1, "Expect one signature") + + (returnType, arguments) = signatures[0] + harness.check( + str(returnType), "TestEnum (Wrapper)", "Method type is the correct name" + ) + harness.check(len(arguments), 1, "Method has the right number of arguments") + arg = arguments[0] + harness.ok(isinstance(arg, WebIDL.IDLArgument), "Should be an IDLArgument") + harness.check(str(arg.type), "Boolean", "Argument has the right type") + + attr = members[1] + harness.check( + attr.identifier.QName(), "::TestEnumInterface::foo", "Attr has correct QName" + ) + harness.check(attr.identifier.name, "foo", "Attr has correct name") + + harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name") + + # Now reset our parser + parser = parser.reset() + threw = False + try: + parser.parse( + """ + enum Enum { + "a", + "b", + "c" + }; + interface TestInterface { + undefined foo(optional Enum e = "d"); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow a bogus default value for an enum") + + # Now reset our parser + parser = parser.reset() + parser.parse( + """ + enum Enum { + "a", + "b", + "c", + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should allow trailing comma in enum") diff --git a/dom/bindings/parser/tests/test_enum_duplicate_values.py b/dom/bindings/parser/tests/test_enum_duplicate_values.py new file mode 100644 index 0000000000..aec28c49e8 --- /dev/null +++ b/dom/bindings/parser/tests/test_enum_duplicate_values.py @@ -0,0 +1,13 @@ +def WebIDLTest(parser, harness): + try: + parser.parse( + """ + enum TestEnumDuplicateValue { + "", + "" + }; + """ + ) + harness.ok(False, "Should have thrown!") + except Exception: + harness.ok(True, "Enum TestEnumDuplicateValue should throw") diff --git a/dom/bindings/parser/tests/test_error_colno.py b/dom/bindings/parser/tests/test_error_colno.py new file mode 100644 index 0000000000..b08ba7cc17 --- /dev/null +++ b/dom/bindings/parser/tests/test_error_colno.py @@ -0,0 +1,24 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + # Check that error messages put the '^' in the right place. + + threw = False + input = "interface ?" + try: + parser.parse(input) + parser.finish() + except WebIDL.WebIDLError as e: + threw = True + lines = str(e).split("\n") + + harness.check(len(lines), 3, "Expected number of lines in error message") + harness.check(lines[1], input, "Second line shows error") + harness.check( + lines[2], + " " * (len(input) - 1) + "^", + "Correct column pointer in error message", + ) + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_error_lineno.py b/dom/bindings/parser/tests/test_error_lineno.py new file mode 100644 index 0000000000..30629be30c --- /dev/null +++ b/dom/bindings/parser/tests/test_error_lineno.py @@ -0,0 +1,38 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + # Check that error messages put the '^' in the right place. + + threw = False + input = """\ +// This is a comment. +interface Foo { +}; + +/* This is also a comment. */ +interface ?""" + try: + parser.parse(input) + parser.finish() + except WebIDL.WebIDLError as e: + threw = True + lines = str(e).split("\n") + + harness.check(len(lines), 3, "Expected number of lines in error message") + harness.ok( + lines[0].endswith("line 6:10"), + 'First line of error should end with "line 6:10", but was "%s".' % lines[0], + ) + harness.check( + lines[1], + "interface ?", + "Second line of error message is the line which caused the error.", + ) + harness.check( + lines[2], + " " * (len("interface ?") - 1) + "^", + "Correct column pointer in error message.", + ) + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_exposed_extended_attribute.py b/dom/bindings/parser/tests/test_exposed_extended_attribute.py new file mode 100644 index 0000000000..0bee74a0db --- /dev/null +++ b/dom/bindings/parser/tests/test_exposed_extended_attribute.py @@ -0,0 +1,383 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global=(Bar, Bar1,Bar2), Exposed=Bar] interface Bar {}; + [Global=(Baz, Baz2), Exposed=Baz] interface Baz {}; + + [Exposed=(Foo,Bar1)] + interface Iface { + undefined method1(); + + [Exposed=Bar1] + readonly attribute any attr; + }; + + [Exposed=Foo] + partial interface Iface { + undefined method2(); + }; + """ + ) + + results = parser.finish() + + harness.check(len(results), 5, "Should know about five things") + iface = results[3] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here") + members = iface.members + harness.check(len(members), 3, "Should have three members") + + harness.ok( + members[0].exposureSet == set(["Foo", "Bar"]), + "method1 should have the right exposure set", + ) + harness.ok( + members[0]._exposureGlobalNames == set(["Foo", "Bar1"]), + "method1 should have the right exposure global names", + ) + + harness.ok( + members[1].exposureSet == set(["Bar"]), + "attr should have the right exposure set", + ) + harness.ok( + members[1]._exposureGlobalNames == set(["Bar1"]), + "attr should have the right exposure global names", + ) + + harness.ok( + members[2].exposureSet == set(["Foo"]), + "method2 should have the right exposure set", + ) + harness.ok( + members[2]._exposureGlobalNames == set(["Foo"]), + "method2 should have the right exposure global names", + ) + + harness.ok( + iface.exposureSet == set(["Foo", "Bar"]), + "Iface should have the right exposure set", + ) + harness.ok( + iface._exposureGlobalNames == set(["Foo", "Bar1"]), + "Iface should have the right exposure global names", + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global=(Bar, Bar1, Bar2), Exposed=Bar] interface Bar {}; + [Global=(Baz, Baz2), Exposed=Baz] interface Baz {}; + + [Exposed=Foo] + interface Iface2 { + undefined method3(); + }; + """ + ) + results = parser.finish() + + harness.check(len(results), 4, "Should know about four things") + iface = results[3] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here") + members = iface.members + harness.check(len(members), 1, "Should have one member") + + harness.ok( + members[0].exposureSet == set(["Foo"]), + "method3 should have the right exposure set", + ) + harness.ok( + members[0]._exposureGlobalNames == set(["Foo"]), + "method3 should have the right exposure global names", + ) + + harness.ok( + iface.exposureSet == set(["Foo"]), "Iface2 should have the right exposure set" + ) + harness.ok( + iface._exposureGlobalNames == set(["Foo"]), + "Iface2 should have the right exposure global names", + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global=(Bar, Bar1, Bar2), Exposed=Bar] interface Bar {}; + [Global=(Baz, Baz2), Exposed=Baz] interface Baz {}; + + [Exposed=Foo] + interface Iface3 { + undefined method4(); + }; + + [Exposed=(Foo,Bar1)] + interface mixin Mixin { + undefined method5(); + }; + + Iface3 includes Mixin; + """ + ) + results = parser.finish() + harness.check(len(results), 6, "Should know about six things") + iface = results[3] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here") + members = iface.members + harness.check(len(members), 2, "Should have two members") + + harness.ok( + members[0].exposureSet == set(["Foo"]), + "method4 should have the right exposure set", + ) + harness.ok( + members[0]._exposureGlobalNames == set(["Foo"]), + "method4 should have the right exposure global names", + ) + + harness.ok( + members[1].exposureSet == set(["Foo", "Bar"]), + "method5 should have the right exposure set", + ) + harness.ok( + members[1]._exposureGlobalNames == set(["Foo", "Bar1"]), + "method5 should have the right exposure global names", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Exposed=Foo] + interface Bar { + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on invalid Exposed value on interface.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Bar { + [Exposed=Foo] + readonly attribute bool attr; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on invalid Exposed value on attribute.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Bar { + [Exposed=Foo] + undefined operation(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on invalid Exposed value on operation.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Bar { + [Exposed=Foo] + const long constant = 5; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on invalid Exposed value on constant.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; + + [Exposed=Foo] + interface Baz { + [Exposed=Bar] + undefined method(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should have thrown on member exposed where its interface is not." + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; + + [Exposed=Foo] + interface Baz { + undefined method(); + }; + + [Exposed=Bar] + interface mixin Mixin { + undefined otherMethod(); + }; + + Baz includes Mixin; + """ + ) + + results = parser.finish() + + harness.check(len(results), 5, "Should know about five things") + iface = results[2] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here") + members = iface.members + harness.check(len(members), 2, "Should have two members") + + harness.ok( + members[0].exposureSet == set(["Foo"]), + "method should have the right exposure set", + ) + harness.ok( + members[0]._exposureGlobalNames == set(["Foo"]), + "method should have the right exposure global names", + ) + + harness.ok( + members[1].exposureSet == set(["Bar"]), + "otherMethod should have the right exposure set", + ) + harness.ok( + members[1]._exposureGlobalNames == set(["Bar"]), + "otherMethod should have the right exposure global names", + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; + + [Exposed=*] + interface Baz { + undefined methodWild(); + }; + + [Exposed=Bar] + interface mixin Mixin { + undefined methodNotWild(); + }; + + Baz includes Mixin; + """ + ) + + results = parser.finish() + + harness.check(len(results), 5, "Should know about five things") + iface = results[2] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here") + members = iface.members + harness.check(len(members), 2, "Should have two members") + + harness.ok( + members[0].exposureSet == set(["Foo", "Bar"]), + "methodWild should have the right exposure set", + ) + harness.ok( + members[0]._exposureGlobalNames == set(["Foo", "Bar"]), + "methodWild should have the right exposure global names", + ) + + harness.ok( + members[1].exposureSet == set(["Bar"]), + "methodNotWild should have the right exposure set", + ) + harness.ok( + members[1]._exposureGlobalNames == set(["Bar"]), + "methodNotWild should have the right exposure global names", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; + + [Exposed=Foo] + interface Baz { + [Exposed=*] + undefined method(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should have thrown on member exposed where its interface is not." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; + + [Exposed=(Foo,*)] + interface Baz { + undefined method(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on a wildcard in an identifier list.") diff --git a/dom/bindings/parser/tests/test_extended_attributes.py b/dom/bindings/parser/tests/test_extended_attributes.py new file mode 100644 index 0000000000..b39ebd1029 --- /dev/null +++ b/dom/bindings/parser/tests/test_extended_attributes.py @@ -0,0 +1,128 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + [LegacyNoInterfaceObject] + interface TestExtendedAttr { + [LegacyUnforgeable] readonly attribute byte b; + }; + """ + ) + + parser.finish() + + parser = parser.reset() + parser.parse( + """ + [Pref="foo.bar",Pref=flop] + interface TestExtendedAttr { + [Pref="foo.bar"] attribute byte b; + }; + """ + ) + + parser.finish() + + parser = parser.reset() + parser.parse( + """ + interface TestLegacyLenientThis { + [LegacyLenientThis] attribute byte b; + }; + """ + ) + + results = parser.finish() + harness.ok( + results[0].members[0].hasLegacyLenientThis(), "Should have a lenient this" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestLegacyLenientThis2 { + [LegacyLenientThis=something] attribute byte b; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "[LegacyLenientThis] must take no arguments") + + parser = parser.reset() + parser.parse( + """ + interface TestClamp { + undefined testClamp([Clamp] long foo); + undefined testNotClamp(long foo); + }; + """ + ) + + results = parser.finish() + # Pull out the first argument out of the arglist of the first (and + # only) signature. + harness.ok( + results[0].members[0].signatures()[0][1][0].type.hasClamp(), "Should be clamped" + ) + harness.ok( + not results[0].members[1].signatures()[0][1][0].type.hasClamp(), + "Should not be clamped", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestClamp2 { + undefined testClamp([Clamp=something] long foo); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "[Clamp] must take no arguments") + + parser = parser.reset() + parser.parse( + """ + interface TestEnforceRange { + undefined testEnforceRange([EnforceRange] long foo); + undefined testNotEnforceRange(long foo); + }; + """ + ) + + results = parser.finish() + # Pull out the first argument out of the arglist of the first (and + # only) signature. + harness.ok( + results[0].members[0].signatures()[0][1][0].type.hasEnforceRange(), + "Should be enforceRange", + ) + harness.ok( + not results[0].members[1].signatures()[0][1][0].type.hasEnforceRange(), + "Should not be enforceRange", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestEnforceRange2 { + undefined testEnforceRange([EnforceRange=something] long foo); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "[EnforceRange] must take no arguments") diff --git a/dom/bindings/parser/tests/test_float_types.py b/dom/bindings/parser/tests/test_float_types.py new file mode 100644 index 0000000000..5cb9404699 --- /dev/null +++ b/dom/bindings/parser/tests/test_float_types.py @@ -0,0 +1,145 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + typedef float myFloat; + typedef unrestricted float myUnrestrictedFloat; + interface FloatTypes { + attribute float f; + attribute unrestricted float uf; + attribute double d; + attribute unrestricted double ud; + [LenientFloat] + attribute float lf; + [LenientFloat] + attribute double ld; + + undefined m1(float arg1, double arg2, float? arg3, double? arg4, + myFloat arg5, unrestricted float arg6, + unrestricted double arg7, unrestricted float? arg8, + unrestricted double? arg9, myUnrestrictedFloat arg10); + [LenientFloat] + undefined m2(float arg1, double arg2, float? arg3, double? arg4, + myFloat arg5, unrestricted float arg6, + unrestricted double arg7, unrestricted float? arg8, + unrestricted double? arg9, myUnrestrictedFloat arg10); + [LenientFloat] + undefined m3(float arg); + [LenientFloat] + undefined m4(double arg); + [LenientFloat] + undefined m5((float or FloatTypes) arg); + [LenientFloat] + undefined m6(sequence<float> arg); + }; + """ + ) + + results = parser.finish() + + harness.check(len(results), 3, "Should be two typedefs and one interface.") + iface = results[2] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + types = [a.type for a in iface.members if a.isAttr()] + harness.ok(types[0].isFloat(), "'float' is a float") + harness.ok(not types[0].isUnrestricted(), "'float' is not unrestricted") + harness.ok(types[1].isFloat(), "'unrestricted float' is a float") + harness.ok(types[1].isUnrestricted(), "'unrestricted float' is unrestricted") + harness.ok(types[2].isFloat(), "'double' is a float") + harness.ok(not types[2].isUnrestricted(), "'double' is not unrestricted") + harness.ok(types[3].isFloat(), "'unrestricted double' is a float") + harness.ok(types[3].isUnrestricted(), "'unrestricted double' is unrestricted") + + method = iface.members[6] + harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") + argtypes = [a.type for a in method.signatures()[0][1]] + for (idx, type) in enumerate(argtypes): + harness.ok(type.isFloat(), "Type %d should be float" % idx) + harness.check( + type.isUnrestricted(), + idx >= 5, + "Type %d should %sbe unrestricted" % (idx, "" if idx >= 4 else "not "), + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface FloatTypes { + [LenientFloat] + long m(float arg); + }; + """ + ) + except Exception: + threw = True + harness.ok(threw, "[LenientFloat] only allowed on methods returning undefined") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface FloatTypes { + [LenientFloat] + undefined m(unrestricted float arg); + }; + """ + ) + except Exception: + threw = True + harness.ok( + threw, "[LenientFloat] only allowed on methods with unrestricted float args" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface FloatTypes { + [LenientFloat] + undefined m(sequence<unrestricted float> arg); + }; + """ + ) + except Exception: + threw = True + harness.ok( + threw, "[LenientFloat] only allowed on methods with unrestricted float args (2)" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface FloatTypes { + [LenientFloat] + undefined m((unrestricted float or FloatTypes) arg); + }; + """ + ) + except Exception: + threw = True + harness.ok( + threw, "[LenientFloat] only allowed on methods with unrestricted float args (3)" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface FloatTypes { + [LenientFloat] + readonly attribute float foo; + }; + """ + ) + except Exception: + threw = True + harness.ok(threw, "[LenientFloat] only allowed on writable attributes") diff --git a/dom/bindings/parser/tests/test_forward_decl.py b/dom/bindings/parser/tests/test_forward_decl.py new file mode 100644 index 0000000000..1ba19e42fc --- /dev/null +++ b/dom/bindings/parser/tests/test_forward_decl.py @@ -0,0 +1,15 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface ForwardDeclared; + interface ForwardDeclared; + + interface TestForwardDecl { + attribute ForwardDeclared foo; + }; + """ + ) + + parser.finish() + + harness.ok(True, "TestForwardDeclared interface parsed without error.") diff --git a/dom/bindings/parser/tests/test_global_extended_attr.py b/dom/bindings/parser/tests/test_global_extended_attr.py new file mode 100644 index 0000000000..2de4aa68bd --- /dev/null +++ b/dom/bindings/parser/tests/test_global_extended_attr.py @@ -0,0 +1,129 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + [Global, Exposed=Foo] + interface Foo : Bar { + getter any(DOMString name); + }; + [Exposed=Foo] + interface Bar {}; + """ + ) + + results = parser.finish() + + harness.ok( + results[0].isOnGlobalProtoChain(), + "[Global] interface should be on global's proto chain", + ) + harness.ok( + results[1].isOnGlobalProtoChain(), + "[Global] interface should be on global's proto chain", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] + interface Foo { + getter any(DOMString name); + setter undefined(DOMString name, any arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown for [Global] used on an interface with a " "named setter", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] + interface Foo { + getter any(DOMString name); + deleter undefined(DOMString name); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown for [Global] used on an interface with a " "named deleter", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, LegacyOverrideBuiltIns, Exposed=Foo] + interface Foo { + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown for [Global] used on an interface with a " + "[LegacyOverrideBuiltIns]", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] + interface Foo : Bar { + }; + [LegacyOverrideBuiltIns, Exposed=Foo] + interface Bar { + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown for [Global] used on an interface with an " + "[LegacyOverrideBuiltIns] ancestor", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Foo] + interface Foo { + }; + [Exposed=Foo] + interface Bar : Foo { + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown for [Global] used on an interface with a " "descendant", + ) diff --git a/dom/bindings/parser/tests/test_identifier_conflict.py b/dom/bindings/parser/tests/test_identifier_conflict.py new file mode 100644 index 0000000000..424f4d6285 --- /dev/null +++ b/dom/bindings/parser/tests/test_identifier_conflict.py @@ -0,0 +1,45 @@ +def WebIDLTest(parser, harness): + try: + parser.parse( + """ + enum Foo { "a" }; + interface Foo; + """ + ) + parser.finish() + harness.ok(False, "Should fail to parse") + except Exception as e: + harness.ok( + "Name collision" in str(e), "Should have name collision for interface" + ) + + parser = parser.reset() + try: + parser.parse( + """ + dictionary Foo { long x; }; + enum Foo { "a" }; + """ + ) + parser.finish() + harness.ok(False, "Should fail to parse") + except Exception as e: + harness.ok( + "Name collision" in str(e), "Should have name collision for dictionary" + ) + + parser = parser.reset() + try: + parser.parse( + """ + enum Foo { "a" }; + enum Foo { "b" }; + """ + ) + parser.finish() + harness.ok(False, "Should fail to parse") + except Exception as e: + harness.ok( + "Multiple unresolvable definitions" in str(e), + "Should have name collision for dictionary", + ) diff --git a/dom/bindings/parser/tests/test_incomplete_parent.py b/dom/bindings/parser/tests/test_incomplete_parent.py new file mode 100644 index 0000000000..80662a7848 --- /dev/null +++ b/dom/bindings/parser/tests/test_incomplete_parent.py @@ -0,0 +1,18 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestIncompleteParent : NotYetDefined { + undefined foo(); + }; + + interface NotYetDefined : EvenHigherOnTheChain { + }; + + interface EvenHigherOnTheChain { + }; + """ + ) + + parser.finish() + + harness.ok(True, "TestIncompleteParent interface parsed without error.") diff --git a/dom/bindings/parser/tests/test_incomplete_types.py b/dom/bindings/parser/tests/test_incomplete_types.py new file mode 100644 index 0000000000..0d54f708bb --- /dev/null +++ b/dom/bindings/parser/tests/test_incomplete_types.py @@ -0,0 +1,61 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestIncompleteTypes { + attribute FooInterface attr1; + + FooInterface method1(FooInterface arg); + }; + + interface FooInterface { + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestIncompleteTypes interface parsed without error.") + harness.check(len(results), 2, "Should be two productions.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), + "::TestIncompleteTypes", + "Interface has the right QName", + ) + harness.check( + iface.identifier.name, "TestIncompleteTypes", "Interface has the right name" + ) + harness.check(len(iface.members), 2, "Expect 2 members") + + attr = iface.members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + method = iface.members[1] + harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") + + harness.check( + attr.identifier.QName(), + "::TestIncompleteTypes::attr1", + "Attribute has the right QName", + ) + harness.check( + attr.type.name, "FooInterface", "Previously unresolved type has the right name" + ) + + harness.check( + method.identifier.QName(), + "::TestIncompleteTypes::method1", + "Attribute has the right QName", + ) + (returnType, args) = method.signatures()[0] + harness.check( + returnType.name, "FooInterface", "Previously unresolved type has the right name" + ) + harness.check( + args[0].type.name, + "FooInterface", + "Previously unresolved type has the right name", + ) diff --git a/dom/bindings/parser/tests/test_interface.py b/dom/bindings/parser/tests/test_interface.py new file mode 100644 index 0000000000..c078a593bb --- /dev/null +++ b/dom/bindings/parser/tests/test_interface.py @@ -0,0 +1,459 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse("interface Foo { };") + results = parser.finish() + harness.ok(True, "Empty interface parsed without error.") + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + iface = results[0] + harness.check(iface.identifier.QName(), "::Foo", "Interface has the right QName") + harness.check(iface.identifier.name, "Foo", "Interface has the right name") + harness.check(iface.parent, None, "Interface has no parent") + + parser.parse("interface Bar : Foo { };") + results = parser.finish() + harness.ok(True, "Empty interface parsed without error.") + harness.check(len(results), 2, "Should be two productions") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface") + iface = results[1] + harness.check(iface.identifier.QName(), "::Bar", "Interface has the right QName") + harness.check(iface.identifier.name, "Bar", "Interface has the right name") + harness.ok(isinstance(iface.parent, WebIDL.IDLInterface), "Interface has a parent") + + parser = parser.reset() + parser.parse( + """ + interface QNameBase { + attribute long foo; + }; + + interface QNameDerived : QNameBase { + attribute long long foo; + attribute byte bar; + }; + """ + ) + results = parser.finish() + harness.check(len(results), 2, "Should be two productions") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check(results[1].parent, results[0], "Inheritance chain is right") + harness.check(len(results[0].members), 1, "Expect 1 productions") + harness.check(len(results[1].members), 2, "Expect 2 productions") + base = results[0] + derived = results[1] + harness.check( + base.members[0].identifier.QName(), + "::QNameBase::foo", + "Member has the right QName", + ) + harness.check( + derived.members[0].identifier.QName(), + "::QNameDerived::foo", + "Member has the right QName", + ) + harness.check( + derived.members[1].identifier.QName(), + "::QNameDerived::bar", + "Member has the right QName", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A : B {}; + interface B : A {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow cycles in interface inheritance chains") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A : C {}; + interface C : B {}; + interface B : A {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, "Should not allow indirect cycles in interface inheritance chains" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A; + interface B : A {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should not allow inheriting from an interface that is only forward declared", + ) + + parser = parser.reset() + parser.parse( + """ + interface A { + constructor(); + constructor(long arg); + readonly attribute boolean x; + undefined foo(); + }; + partial interface A { + readonly attribute boolean y; + undefined foo(long arg); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 2, "Should have two results with partial interface") + iface = results[0] + harness.check( + len(iface.members), 3, "Should have three members with partial interface" + ) + harness.check( + iface.members[0].identifier.name, + "x", + "First member should be x with partial interface", + ) + harness.check( + iface.members[1].identifier.name, + "foo", + "Second member should be foo with partial interface", + ) + harness.check( + len(iface.members[1].signatures()), + 2, + "Should have two foo signatures with partial interface", + ) + harness.check( + iface.members[2].identifier.name, + "y", + "Third member should be y with partial interface", + ) + harness.check( + len(iface.ctor().signatures()), + 2, + "Should have two constructors with partial interface", + ) + + parser = parser.reset() + parser.parse( + """ + partial interface A { + readonly attribute boolean y; + undefined foo(long arg); + }; + interface A { + constructor(); + constructor(long arg); + readonly attribute boolean x; + undefined foo(); + }; + """ + ) + results = parser.finish() + harness.check( + len(results), 2, "Should have two results with reversed partial interface" + ) + iface = results[1] + harness.check( + len(iface.members), + 3, + "Should have three members with reversed partial interface", + ) + harness.check( + iface.members[0].identifier.name, + "x", + "First member should be x with reversed partial interface", + ) + harness.check( + iface.members[1].identifier.name, + "foo", + "Second member should be foo with reversed partial interface", + ) + harness.check( + len(iface.members[1].signatures()), + 2, + "Should have two foo signatures with reversed partial interface", + ) + harness.check( + iface.members[2].identifier.name, + "y", + "Third member should be y with reversed partial interface", + ) + harness.check( + len(iface.ctor().signatures()), + 2, + "Should have two constructors with reversed partial interface", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + readonly attribute boolean x; + }; + interface A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow two non-partial interfaces with the same name") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + partial interface A { + readonly attribute boolean x; + }; + partial interface A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Must have a non-partial interface for a given name") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + boolean x; + }; + partial interface A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a name collision between partial interface " + "and other object", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + boolean x; + }; + interface A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow a name collision between interface " "and other object" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + boolean x; + }; + interface A; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a name collision between external interface " + "and other object", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + readonly attribute boolean x; + }; + interface A; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a name collision between external interface " "and interface", + ) + + parser = parser.reset() + parser.parse( + """ + interface A; + interface A; + """ + ) + results = parser.finish() + harness.ok( + len(results) == 1 and isinstance(results[0], WebIDL.IDLExternalInterface), + "Should allow name collisions between external interface " "declarations", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [SomeRandomAnnotation] + interface A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow unknown extended attributes on interfaces") + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + [Exposed=Window, LegacyWindowAlias=(C, D)] + interface E {}; + """ + ) + results = parser.finish() + harness.check( + results[1].legacyWindowAliases, ["A"], "Should support a single identifier" + ) + harness.check( + results[2].legacyWindowAliases, ["C", "D"], "Should support an identifier list" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyWindowAlias] + interface A {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [LegacyWindowAlias] with no value") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Exposed=Worker, LegacyWindowAlias=B] + interface A {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [LegacyWindowAlias] without Window exposure") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Exposed=Window] + interface A {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow [LegacyWindowAlias] to conflict with other identifiers" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + [Exposed=Window] + interface A {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow [LegacyWindowAlias] to conflict with other identifiers" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + [Exposed=Window, LegacyWindowAlias=A] + interface C {}; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow [LegacyWindowAlias] to conflict with other identifiers" + ) diff --git a/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py b/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py new file mode 100644 index 0000000000..6388af2139 --- /dev/null +++ b/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py @@ -0,0 +1,17 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface IdentifierConflict { + const byte thing1 = 1; + const unsigned long thing1 = 1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py new file mode 100644 index 0000000000..d8398d46ba --- /dev/null +++ b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py @@ -0,0 +1,168 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers1 { + const byte thing1 = 1; + readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers1.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers2 { + readonly attribute long thing1; + const byte thing1 = 1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers2.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers3 { + getter boolean thing1(DOMString name); + readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers3.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers4 { + const byte thing1 = 1; + long thing1(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers4.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers5 { + static long thing1(); + undefined thing1(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + not threw, "Should not have thrown for IdentifierConflictAcrossMembers5." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin IdentifierConflictAcrossMembers6Mixin { + undefined thing1(); + }; + interface IdentifierConflictAcrossMembers6 { + static long thing1(); + }; + IdentifierConflictAcrossMembers6 includes IdentifierConflictAcrossMembers6Mixin; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + not threw, "Should not have thrown for IdentifierConflictAcrossMembers6." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers7 { + const byte thing1 = 1; + static readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers7.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers8 { + readonly attribute long thing1 = 1; + static readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers8.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers9 { + void thing1(); + static readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers9.") diff --git a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py new file mode 100644 index 0000000000..fdd9c00965 --- /dev/null +++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py @@ -0,0 +1,935 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + def shouldPass(prefix, iface, expectedMembers, numProductions=1): + p = parser.reset() + p.parse(iface) + results = p.finish() + harness.check( + len(results), + numProductions, + "%s - Should have production count %d" % (prefix, numProductions), + ) + harness.ok( + isinstance(results[0], WebIDL.IDLInterface), + "%s - Should be an IDLInterface" % (prefix), + ) + # Make a copy, since we plan to modify it + expectedMembers = list(expectedMembers) + for m in results[0].members: + name = m.identifier.name + if m.isMethod() and m.isStatic(): + # None of the expected members are static methods, so ignore those. + harness.ok(True, "%s - %s - Should be a %s" % (prefix, name, type(m))) + elif (name, type(m)) in expectedMembers: + harness.ok(True, "%s - %s - Should be a %s" % (prefix, name, type(m))) + expectedMembers.remove((name, type(m))) + else: + harness.ok( + False, + "%s - %s - Unknown symbol of type %s" % (prefix, name, type(m)), + ) + # A bit of a hoop because we can't generate the error string if we pass + if len(expectedMembers) == 0: + harness.ok(True, "Found all the members") + else: + harness.ok( + False, + "Expected member not found: %s of type %s" + % (expectedMembers[0][0], expectedMembers[0][1]), + ) + return results + + def shouldFail(prefix, iface): + try: + p = parser.reset() + p.parse(iface) + p.finish() + harness.ok(False, prefix + " - Interface passed when should've failed") + except WebIDL.WebIDLError: + harness.ok(True, prefix + " - Interface failed as expected") + except Exception as e: + harness.ok( + False, + prefix + + " - Interface failed but not as a WebIDLError exception: %s" % e, + ) + + iterableMembers = [ + (x, WebIDL.IDLMethod) for x in ["entries", "keys", "values", "forEach"] + ] + setROMembers = ( + [(x, WebIDL.IDLMethod) for x in ["has"]] + + [("__setlike", WebIDL.IDLMaplikeOrSetlike)] + + iterableMembers + ) + setROMembers.extend([("size", WebIDL.IDLAttribute)]) + setRWMembers = [ + (x, WebIDL.IDLMethod) for x in ["add", "clear", "delete"] + ] + setROMembers + mapROMembers = ( + [(x, WebIDL.IDLMethod) for x in ["get", "has"]] + + [("__maplike", WebIDL.IDLMaplikeOrSetlike)] + + iterableMembers + ) + mapROMembers.extend([("size", WebIDL.IDLAttribute)]) + mapRWMembers = [ + (x, WebIDL.IDLMethod) for x in ["set", "clear", "delete"] + ] + mapROMembers + + # OK, now that we've used iterableMembers to set up the above, append + # __iterable to it for the iterable<> case. + iterableMembers.append(("__iterable", WebIDL.IDLIterable)) + + asyncIterableMembers = [ + (x, WebIDL.IDLMethod) for x in ["entries", "keys", "values"] + ] + asyncIterableMembers.append(("__iterable", WebIDL.IDLAsyncIterable)) + + valueIterableMembers = [("__iterable", WebIDL.IDLIterable)] + valueIterableMembers.append(("__indexedgetter", WebIDL.IDLMethod)) + valueIterableMembers.append(("length", WebIDL.IDLAttribute)) + + valueAsyncIterableMembers = [("__iterable", WebIDL.IDLAsyncIterable)] + valueAsyncIterableMembers.append(("values", WebIDL.IDLMethod)) + + disallowedIterableNames = [ + ("keys", WebIDL.IDLMethod), + ("entries", WebIDL.IDLMethod), + ("values", WebIDL.IDLMethod), + ] + disallowedMemberNames = [ + ("forEach", WebIDL.IDLMethod), + ("has", WebIDL.IDLMethod), + ("size", WebIDL.IDLAttribute), + ] + disallowedIterableNames + mapDisallowedMemberNames = [("get", WebIDL.IDLMethod)] + disallowedMemberNames + disallowedNonMethodNames = [ + ("clear", WebIDL.IDLMethod), + ("delete", WebIDL.IDLMethod), + ] + mapDisallowedNonMethodNames = [("set", WebIDL.IDLMethod)] + disallowedNonMethodNames + setDisallowedNonMethodNames = [("add", WebIDL.IDLMethod)] + disallowedNonMethodNames + unrelatedMembers = [ + ("unrelatedAttribute", WebIDL.IDLAttribute), + ("unrelatedMethod", WebIDL.IDLMethod), + ] + + # + # Simple Usage Tests + # + + shouldPass( + "Iterable (key only)", + """ + interface Foo1 { + iterable<long>; + readonly attribute unsigned long length; + getter long(unsigned long index); + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + valueIterableMembers + unrelatedMembers, + ) + + shouldPass( + "Iterable (key only) inheriting from parent", + """ + interface Foo1 : Foo2 { + iterable<long>; + readonly attribute unsigned long length; + getter long(unsigned long index); + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + valueIterableMembers, + numProductions=2, + ) + + shouldPass( + "Iterable (key and value)", + """ + interface Foo1 { + iterable<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + iterableMembers + unrelatedMembers, + # numProductions == 2 because of the generated iterator iface, + numProductions=2, + ) + + shouldPass( + "Iterable (key and value) inheriting from parent", + """ + interface Foo1 : Foo2 { + iterable<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + iterableMembers, + # numProductions == 3 because of the generated iterator iface, + numProductions=3, + ) + + shouldPass( + "Async iterable (key only)", + """ + interface Foo1 { + async iterable<long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + valueAsyncIterableMembers + unrelatedMembers, + # numProductions == 2 because of the generated iterator iface, + numProductions=2, + ) + + shouldPass( + "Async iterable (key only) inheriting from parent", + """ + interface Foo1 : Foo2 { + async iterable<long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + valueAsyncIterableMembers, + # numProductions == 3 because of the generated iterator iface, + numProductions=3, + ) + + shouldPass( + "Async iterable with argument (key only)", + """ + interface Foo1 { + async iterable<long>(optional long foo); + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + valueAsyncIterableMembers + unrelatedMembers, + # numProductions == 2 because of the generated iterator iface, + numProductions=2, + ) + + shouldPass( + "Async iterable (key and value)", + """ + interface Foo1 { + async iterable<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + asyncIterableMembers + unrelatedMembers, + # numProductions == 2 because of the generated iterator iface, + numProductions=2, + ) + + shouldPass( + "Async iterable (key and value) inheriting from parent", + """ + interface Foo1 : Foo2 { + async iterable<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + asyncIterableMembers, + # numProductions == 3 because of the generated iterator iface, + numProductions=3, + ) + + shouldPass( + "Async iterable with argument (key and value)", + """ + interface Foo1 { + async iterable<long, long>(optional long foo); + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + asyncIterableMembers + unrelatedMembers, + # numProductions == 2 because of the generated iterator iface, + numProductions=2, + ) + + shouldPass( + "Maplike (readwrite)", + """ + interface Foo1 { + maplike<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + mapRWMembers + unrelatedMembers, + ) + + shouldPass( + "Maplike (readwrite) inheriting from parent", + """ + interface Foo1 : Foo2 { + maplike<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + mapRWMembers, + numProductions=2, + ) + + shouldPass( + "Maplike (readwrite)", + """ + interface Foo1 { + maplike<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + mapRWMembers + unrelatedMembers, + ) + + shouldPass( + "Maplike (readwrite) inheriting from parent", + """ + interface Foo1 : Foo2 { + maplike<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + mapRWMembers, + numProductions=2, + ) + + shouldPass( + "Maplike (readonly)", + """ + interface Foo1 { + readonly maplike<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + mapROMembers + unrelatedMembers, + ) + + shouldPass( + "Maplike (readonly) inheriting from parent", + """ + interface Foo1 : Foo2 { + readonly maplike<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + mapROMembers, + numProductions=2, + ) + + shouldPass( + "Setlike (readwrite)", + """ + interface Foo1 { + setlike<long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + setRWMembers + unrelatedMembers, + ) + + shouldPass( + "Setlike (readwrite) inheriting from parent", + """ + interface Foo1 : Foo2 { + setlike<long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + setRWMembers, + numProductions=2, + ) + + shouldPass( + "Setlike (readonly)", + """ + interface Foo1 { + readonly setlike<long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + setROMembers + unrelatedMembers, + ) + + shouldPass( + "Setlike (readonly) inheriting from parent", + """ + interface Foo1 : Foo2 { + readonly setlike<long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, + setROMembers, + numProductions=2, + ) + + shouldPass( + "Inheritance of maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + }; + """, + mapRWMembers, + numProductions=2, + ) + + shouldFail( + "JS Implemented maplike interface", + """ + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"] + interface Foo1 { + constructor(); + setlike<long>; + }; + """, + ) + + shouldFail( + "JS Implemented maplike interface", + """ + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"] + interface Foo1 { + constructor(); + maplike<long, long>; + }; + """, + ) + + # + # Multiple maplike/setlike tests + # + + shouldFail( + "Two maplike/setlikes on same interface", + """ + interface Foo1 { + setlike<long>; + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Two iterable/setlikes on same interface", + """ + interface Foo1 { + iterable<long>; + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Two iterables on same interface", + """ + interface Foo1 { + iterable<long>; + iterable<long, long>; + }; + """, + ) + + shouldFail( + "Two iterables on same interface", + """ + interface Foo1 { + iterable<long>; + async iterable<long>; + }; + """, + ) + + shouldFail( + "Two iterables on same interface", + """ + interface Foo1 { + async iterable<long>; + async iterable<long, long>; + }; + """, + ) + + shouldFail( + "Async iterable with non-optional arguments", + """ + interface Foo1 { + async iterable<long>(long foo); + }; + """, + ) + + shouldFail( + "Async iterable with non-optional arguments", + """ + interface Foo1 { + async iterable<long>(optional long foo, long bar); + }; + """, + ) + + shouldFail( + "Async iterable with non-optional arguments", + """ + interface Foo1 { + async iterable<long, long>(long foo); + }; + """, + ) + + shouldFail( + "Two maplike/setlikes in partials", + """ + interface Foo1 { + maplike<long, long>; + }; + partial interface Foo1 { + setlike<long>; + }; + """, + ) + + shouldFail( + "Conflicting maplike/setlikes across inheritance", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + setlike<long>; + }; + """, + ) + + shouldFail( + "Conflicting maplike/iterable across inheritance", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + iterable<long>; + }; + """, + ) + + shouldFail( + "Conflicting maplike/setlikes across multistep inheritance", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + setlike<long>; + }; + """, + ) + + # + # Member name collision tests + # + + def testConflictingMembers( + likeMember, conflict, expectedMembers, methodPasses, numProductions=1 + ): + """ + Tests for maplike/setlike member generation against conflicting member + names. If methodPasses is True, this means we expect the interface to + pass in the case of method shadowing, and expectedMembers should be the + list of interface members to check against on the passing interface. + + """ + (conflictName, conflictType) = conflict + if methodPasses: + shouldPass( + "Conflicting method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + [Throws] + undefined %s(long test1, double test2, double test3); + }; + """ + % (likeMember, conflictName), + expectedMembers, + ) + else: + shouldFail( + "Conflicting method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + [Throws] + undefined %s(long test1, double test2, double test3); + }; + """ + % (likeMember, conflictName), + ) + # Inherited conflicting methods should ALWAYS fail + shouldFail( + "Conflicting inherited method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + undefined %s(long test1, double test2, double test3); + }; + interface Foo2 : Foo1 { + %s; + }; + """ + % (conflictName, likeMember), + ) + if conflictType == WebIDL.IDLAttribute: + shouldFail( + "Conflicting static method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + static undefined %s(long test1, double test2, double test3); + }; + """ + % (likeMember, conflictName), + ) + else: + shouldPass( + "Conflicting static method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + static undefined %s(long test1, double test2, double test3); + }; + """ + % (likeMember, conflictName), + expectedMembers, + numProductions=numProductions, + ) + shouldFail( + "Conflicting attribute: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s + attribute double %s; + }; + """ + % (likeMember, conflictName), + ) + shouldFail( + "Conflicting const: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + const double %s = 0; + }; + """ + % (likeMember, conflictName), + ) + shouldFail( + "Conflicting static attribute: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + static attribute long %s; + }; + """ + % (likeMember, conflictName), + ) + + for member in disallowedIterableNames: + testConflictingMembers( + "iterable<long, long>", member, iterableMembers, False, numProductions=2 + ) + for member in mapDisallowedMemberNames: + testConflictingMembers("maplike<long, long>", member, mapRWMembers, False) + for member in disallowedMemberNames: + testConflictingMembers("setlike<long>", member, setRWMembers, False) + for member in mapDisallowedNonMethodNames: + testConflictingMembers("maplike<long, long>", member, mapRWMembers, True) + for member in setDisallowedNonMethodNames: + testConflictingMembers("setlike<long>", member, setRWMembers, True) + + shouldPass( + "Inheritance of maplike/setlike with child member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + undefined entries(); + }; + """, + mapRWMembers, + numProductions=2, + ) + + shouldPass( + "Inheritance of multi-level maplike/setlike with child member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + undefined entries(); + }; + """, + mapRWMembers, + numProductions=3, + ) + + shouldFail( + "Maplike interface with mixin member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface mixin Foo2 { + undefined entries(); + }; + Foo1 includes Foo2; + """, + ) + + shouldPass( + "Inherited Maplike interface with consequential interface member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface mixin Foo2 { + undefined entries(); + }; + interface Foo3 : Foo1 { + }; + Foo3 includes Foo2; + """, + mapRWMembers, + numProductions=4, + ) + + shouldFail( + "Inheritance of name collision with child maplike/setlike", + """ + interface Foo1 { + undefined entries(); + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Inheritance of multi-level name collision with child maplike/setlike", + """ + interface Foo1 { + undefined entries(); + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + maplike<long, long>; + }; + """, + ) + + shouldPass( + "Inheritance of attribute collision with parent maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + attribute double size; + }; + """, + mapRWMembers, + numProductions=2, + ) + + shouldPass( + "Inheritance of multi-level attribute collision with parent maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + attribute double size; + }; + """, + mapRWMembers, + numProductions=3, + ) + + shouldFail( + "Inheritance of attribute collision with child maplike/setlike", + """ + interface Foo1 { + attribute double size; + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Inheritance of multi-level attribute collision with child maplike/setlike", + """ + interface Foo1 { + attribute double size; + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Inheritance of attribute/rw function collision with child maplike/setlike", + """ + interface Foo1 { + attribute double set; + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Inheritance of const/rw function collision with child maplike/setlike", + """ + interface Foo1 { + const double set = 0; + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """, + ) + + shouldPass( + "Inheritance of rw function with same name in child maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 : Foo1 { + undefined clear(); + }; + """, + mapRWMembers, + numProductions=2, + ) + + shouldFail( + "Inheritance of unforgeable attribute collision with child maplike/setlike", + """ + interface Foo1 { + [LegacyUnforgeable] + attribute double size; + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """, + ) + + shouldFail( + "Inheritance of multi-level unforgeable attribute collision with child maplike/setlike", + """ + interface Foo1 { + [LegacyUnforgeable] + attribute double size; + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + maplike<long, long>; + }; + """, + ) + + shouldPass( + "Interface with readonly allowable overrides", + """ + interface Foo1 { + readonly setlike<long>; + readonly attribute boolean clear; + }; + """, + setROMembers + [("clear", WebIDL.IDLAttribute)], + ) + + r = shouldPass( + "Check proper override of clear/delete/set", + """ + interface Foo1 { + maplike<long, long>; + long clear(long a, long b, double c, double d); + long set(long a, long b, double c, double d); + long delete(long a, long b, double c, double d); + }; + """, + mapRWMembers, + ) + + for m in r[0].members: + if m.identifier.name in ["clear", "set", "delete"]: + harness.ok(m.isMethod(), "%s should be a method" % m.identifier.name) + harness.check( + m.maxArgCount, 4, "%s should have 4 arguments" % m.identifier.name + ) + harness.ok( + not m.isMaplikeOrSetlikeOrIterableMethod(), + "%s should not be a maplike/setlike function" % m.identifier.name, + ) diff --git a/dom/bindings/parser/tests/test_interfacemixin.py b/dom/bindings/parser/tests/test_interfacemixin.py new file mode 100644 index 0000000000..4723991937 --- /dev/null +++ b/dom/bindings/parser/tests/test_interfacemixin.py @@ -0,0 +1,534 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse("interface mixin Foo { };") + results = parser.finish() + harness.ok(True, "Empty interface mixin parsed without error.") + harness.check(len(results), 1, "Should be one production") + harness.ok( + isinstance(results[0], WebIDL.IDLInterfaceMixin), + "Should be an IDLInterfaceMixin", + ) + mixin = results[0] + harness.check( + mixin.identifier.QName(), "::Foo", "Interface mixin has the right QName" + ) + harness.check(mixin.identifier.name, "Foo", "Interface mixin has the right name") + + parser = parser.reset() + parser.parse( + """ + interface mixin QNameBase { + const long foo = 3; + }; + """ + ) + results = parser.finish() + harness.check(len(results), 1, "Should be one productions") + harness.ok( + isinstance(results[0], WebIDL.IDLInterfaceMixin), + "Should be an IDLInterfaceMixin", + ) + harness.check(len(results[0].members), 1, "Expect 1 productions") + mixin = results[0] + harness.check( + mixin.members[0].identifier.QName(), + "::QNameBase::foo", + "Member has the right QName", + ) + + parser = parser.reset() + parser.parse( + """ + interface mixin A { + readonly attribute boolean x; + undefined foo(); + }; + partial interface mixin A { + readonly attribute boolean y; + undefined foo(long arg); + }; + """ + ) + results = parser.finish() + harness.check( + len(results), 2, "Should have two results with partial interface mixin" + ) + mixin = results[0] + harness.check( + len(mixin.members), 3, "Should have three members with partial interface mixin" + ) + harness.check( + mixin.members[0].identifier.name, + "x", + "First member should be x with partial interface mixin", + ) + harness.check( + mixin.members[1].identifier.name, + "foo", + "Second member should be foo with partial interface mixin", + ) + harness.check( + len(mixin.members[1].signatures()), + 2, + "Should have two foo signatures with partial interface mixin", + ) + harness.check( + mixin.members[2].identifier.name, + "y", + "Third member should be y with partial interface mixin", + ) + + parser = parser.reset() + parser.parse( + """ + partial interface mixin A { + readonly attribute boolean y; + undefined foo(long arg); + }; + interface mixin A { + readonly attribute boolean x; + undefined foo(); + }; + """ + ) + results = parser.finish() + harness.check( + len(results), 2, "Should have two results with reversed partial interface mixin" + ) + mixin = results[1] + harness.check( + len(mixin.members), + 3, + "Should have three members with reversed partial interface mixin", + ) + harness.check( + mixin.members[0].identifier.name, + "x", + "First member should be x with reversed partial interface mixin", + ) + harness.check( + mixin.members[1].identifier.name, + "foo", + "Second member should be foo with reversed partial interface mixin", + ) + harness.check( + len(mixin.members[1].signatures()), + 2, + "Should have two foo signatures with reversed partial interface mixin", + ) + harness.check( + mixin.members[2].identifier.name, + "y", + "Third member should be y with reversed partial interface mixin", + ) + + parser = parser.reset() + parser.parse( + """ + interface Interface {}; + interface mixin Mixin { + attribute short x; + }; + Interface includes Mixin; + """ + ) + results = parser.finish() + iface = results[0] + harness.check(len(iface.members), 1, "Should merge members from mixins") + harness.check( + iface.members[0].identifier.name, "x", "Should merge members from mixins" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + readonly attribute boolean x; + }; + interface mixin A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow two non-partial interface mixins with the same name" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + partial interface mixin A { + readonly attribute boolean x; + }; + partial interface mixin A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Must have a non-partial interface mixin for a given name") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + boolean x; + }; + partial interface mixin A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a name collision between partial interface " + "mixin and other object", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary A { + boolean x; + }; + interface mixin A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a name collision between interface mixin " "and other object", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + readonly attribute boolean x; + }; + interface A; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a name collision between external interface " + "and interface mixin", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [SomeRandomAnnotation] + interface mixin A { + readonly attribute boolean y; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow unknown extended attributes on interface mixins" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + getter double (DOMString propertyName); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow getters on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + setter undefined (DOMString propertyName, double propertyValue); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow setters on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + deleter undefined (DOMString propertyName); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow deleters on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + legacycaller double compute(double x); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow legacycallers on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin A { + inherit attribute x; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow inherited attribute on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Interface {}; + interface NotMixin { + attribute short x; + }; + Interface includes NotMixin; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should fail if the right side does not point an interface mixin") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin NotInterface {}; + interface mixin Mixin { + attribute short x; + }; + NotInterface includes Mixin; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should fail if the left side does not point an interface") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin Mixin { + iterable<DOMString>; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should fail if an interface mixin includes iterable") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin Mixin { + setlike<DOMString>; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should fail if an interface mixin includes setlike") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin Mixin { + maplike<DOMString, DOMString>; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should fail if an interface mixin includes maplike") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Interface { + attribute short attr; + }; + interface mixin Mixin { + attribute short attr; + }; + Interface includes Mixin; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should fail if the included mixin interface has duplicated member" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Interface {}; + interface mixin Mixin1 { + attribute short attr; + }; + interface mixin Mixin2 { + attribute short attr; + }; + Interface includes Mixin1; + Interface includes Mixin2; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should fail if the included mixin interfaces have duplicated member" + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Global, Exposed=Worker] interface Worker {}; + [Exposed=Window] + interface Base {}; + interface mixin Mixin { + Base returnSelf(); + }; + Base includes Mixin; + """ + ) + results = parser.finish() + base = results[2] + attr = base.members[0] + harness.check( + attr.exposureSet, + set(["Window"]), + "Should expose on globals where the base interfaces are exposed", + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Global, Exposed=Worker] interface Worker {}; + [Exposed=Window] + interface Base {}; + [Exposed=Window] + interface mixin Mixin { + attribute short a; + }; + Base includes Mixin; + """ + ) + results = parser.finish() + base = results[2] + attr = base.members[0] + harness.check( + attr.exposureSet, set(["Window"]), "Should follow [Exposed] on interface mixin" + ) + + parser = parser.reset() + parser.parse( + """ + [Global, Exposed=Window] interface Window {}; + [Global, Exposed=Worker] interface Worker {}; + [Exposed=Window] + interface Base1 {}; + [Exposed=Worker] + interface Base2 {}; + interface mixin Mixin { + attribute short a; + }; + Base1 includes Mixin; + Base2 includes Mixin; + """ + ) + results = parser.finish() + base = results[2] + attr = base.members[0] + harness.check( + attr.exposureSet, + set(["Window", "Worker"]), + "Should expose on all globals where including interfaces are " "exposed", + ) + base = results[3] + attr = base.members[0] + harness.check( + attr.exposureSet, + set(["Window", "Worker"]), + "Should expose on all globals where including interfaces are " "exposed", + ) diff --git a/dom/bindings/parser/tests/test_lenientSetter.py b/dom/bindings/parser/tests/test_lenientSetter.py new file mode 100644 index 0000000000..1d2a9f06ce --- /dev/null +++ b/dom/bindings/parser/tests/test_lenientSetter.py @@ -0,0 +1,84 @@ +# 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/. + + +def should_throw(parser, harness, message, code): + parser = parser.reset() + threw = False + try: + parser.parse(code) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown: %s" % message) + + +def WebIDLTest(parser, harness): + # The [LegacyLenientSetter] extended attribute MUST take no arguments. + should_throw( + parser, + harness, + "no arguments", + """ + interface I { + [LegacyLenientSetter=X] readonly attribute long A; + }; + """, + ) + + # An attribute with the [LegacyLenientSetter] extended attribute MUST NOT + # also be declared with the [PutForwards] extended attribute. + should_throw( + parser, + harness, + "PutForwards", + """ + interface I { + [PutForwards=B, LegacyLenientSetter] readonly attribute J A; + }; + interface J { + attribute long B; + }; + """, + ) + + # An attribute with the [LegacyLenientSetter] extended attribute MUST NOT + # also be declared with the [Replaceable] extended attribute. + should_throw( + parser, + harness, + "Replaceable", + """ + interface I { + [Replaceable, LegacyLenientSetter] readonly attribute J A; + }; + """, + ) + + # The [LegacyLenientSetter] extended attribute MUST NOT be used on an + # attribute that is not read only. + should_throw( + parser, + harness, + "writable attribute", + """ + interface I { + [LegacyLenientSetter] attribute long A; + }; + """, + ) + + # The [LegacyLenientSetter] extended attribute MUST NOT be used on a + # static attribute. + should_throw( + parser, + harness, + "static attribute", + """ + interface I { + [LegacyLenientSetter] static readonly attribute long A; + }; + """, + ) diff --git a/dom/bindings/parser/tests/test_method.py b/dom/bindings/parser/tests/test_method.py new file mode 100644 index 0000000000..e11044b742 --- /dev/null +++ b/dom/bindings/parser/tests/test_method.py @@ -0,0 +1,430 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestMethods { + undefined basic(); + static undefined basicStatic(); + undefined basicWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3); + boolean basicBoolean(); + static boolean basicStaticBoolean(); + boolean basicBooleanWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3); + undefined optionalArg(optional byte? arg1, optional sequence<byte> arg2); + undefined variadicArg(byte?... arg1); + object getObject(); + undefined setObject(object arg1); + undefined setAny(any arg1); + float doFloats(float arg1); + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestMethods interface parsed without error.") + harness.check(len(results), 1, "Should be one production.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), "::TestMethods", "Interface has the right QName" + ) + harness.check(iface.identifier.name, "TestMethods", "Interface has the right name") + harness.check(len(iface.members), 12, "Expect 12 members") + + methods = iface.members + + def checkArgument(argument, QName, name, type, optional, variadic): + harness.ok(isinstance(argument, WebIDL.IDLArgument), "Should be an IDLArgument") + harness.check( + argument.identifier.QName(), QName, "Argument has the right QName" + ) + harness.check(argument.identifier.name, name, "Argument has the right name") + harness.check(str(argument.type), type, "Argument has the right return type") + harness.check( + argument.optional, optional, "Argument has the right optional value" + ) + harness.check( + argument.variadic, variadic, "Argument has the right variadic value" + ) + + def checkMethod( + method, + QName, + name, + signatures, + static=False, + getter=False, + setter=False, + deleter=False, + legacycaller=False, + stringifier=False, + ): + harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") + harness.ok(method.isMethod(), "Method is a method") + harness.ok(not method.isAttr(), "Method is not an attr") + harness.ok(not method.isConst(), "Method is not a const") + harness.check(method.identifier.QName(), QName, "Method has the right QName") + harness.check(method.identifier.name, name, "Method has the right name") + harness.check(method.isStatic(), static, "Method has the correct static value") + harness.check(method.isGetter(), getter, "Method has the correct getter value") + harness.check(method.isSetter(), setter, "Method has the correct setter value") + harness.check( + method.isDeleter(), deleter, "Method has the correct deleter value" + ) + harness.check( + method.isLegacycaller(), + legacycaller, + "Method has the correct legacycaller value", + ) + harness.check( + method.isStringifier(), + stringifier, + "Method has the correct stringifier value", + ) + harness.check( + len(method.signatures()), + len(signatures), + "Method has the correct number of signatures", + ) + + sigpairs = zip(method.signatures(), signatures) + for (gotSignature, expectedSignature) in sigpairs: + (gotRetType, gotArgs) = gotSignature + (expectedRetType, expectedArgs) = expectedSignature + + harness.check( + str(gotRetType), expectedRetType, "Method has the expected return type." + ) + + for i in range(0, len(gotArgs)): + (QName, name, type, optional, variadic) = expectedArgs[i] + checkArgument(gotArgs[i], QName, name, type, optional, variadic) + + checkMethod(methods[0], "::TestMethods::basic", "basic", [("Undefined", [])]) + checkMethod( + methods[1], + "::TestMethods::basicStatic", + "basicStatic", + [("Undefined", [])], + static=True, + ) + checkMethod( + methods[2], + "::TestMethods::basicWithSimpleArgs", + "basicWithSimpleArgs", + [ + ( + "Undefined", + [ + ( + "::TestMethods::basicWithSimpleArgs::arg1", + "arg1", + "Boolean", + False, + False, + ), + ( + "::TestMethods::basicWithSimpleArgs::arg2", + "arg2", + "Byte", + False, + False, + ), + ( + "::TestMethods::basicWithSimpleArgs::arg3", + "arg3", + "UnsignedLong", + False, + False, + ), + ], + ) + ], + ) + checkMethod( + methods[3], "::TestMethods::basicBoolean", "basicBoolean", [("Boolean", [])] + ) + checkMethod( + methods[4], + "::TestMethods::basicStaticBoolean", + "basicStaticBoolean", + [("Boolean", [])], + static=True, + ) + checkMethod( + methods[5], + "::TestMethods::basicBooleanWithSimpleArgs", + "basicBooleanWithSimpleArgs", + [ + ( + "Boolean", + [ + ( + "::TestMethods::basicBooleanWithSimpleArgs::arg1", + "arg1", + "Boolean", + False, + False, + ), + ( + "::TestMethods::basicBooleanWithSimpleArgs::arg2", + "arg2", + "Byte", + False, + False, + ), + ( + "::TestMethods::basicBooleanWithSimpleArgs::arg3", + "arg3", + "UnsignedLong", + False, + False, + ), + ], + ) + ], + ) + checkMethod( + methods[6], + "::TestMethods::optionalArg", + "optionalArg", + [ + ( + "Undefined", + [ + ( + "::TestMethods::optionalArg::arg1", + "arg1", + "ByteOrNull", + True, + False, + ), + ( + "::TestMethods::optionalArg::arg2", + "arg2", + "ByteSequence", + True, + False, + ), + ], + ) + ], + ) + checkMethod( + methods[7], + "::TestMethods::variadicArg", + "variadicArg", + [ + ( + "Undefined", + [ + ( + "::TestMethods::variadicArg::arg1", + "arg1", + "ByteOrNull", + True, + True, + ) + ], + ) + ], + ) + checkMethod(methods[8], "::TestMethods::getObject", "getObject", [("Object", [])]) + checkMethod( + methods[9], + "::TestMethods::setObject", + "setObject", + [ + ( + "Undefined", + [("::TestMethods::setObject::arg1", "arg1", "Object", False, False)], + ) + ], + ) + checkMethod( + methods[10], + "::TestMethods::setAny", + "setAny", + [("Undefined", [("::TestMethods::setAny::arg1", "arg1", "Any", False, False)])], + ) + checkMethod( + methods[11], + "::TestMethods::doFloats", + "doFloats", + [("Float", [("::TestMethods::doFloats::arg1", "arg1", "Float", False, False)])], + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + undefined foo(optional float bar = 1); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow integer to float type corecion") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [GetterThrows] undefined foo(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [GetterThrows] on methods") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [SetterThrows] undefined foo(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [SetterThrows] on methods") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Throw] undefined foo(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should spell [Throws] correctly on methods") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + undefined __noSuchMethod__(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow __noSuchMethod__ methods") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Throws, LenientFloat] + undefined foo(float myFloat); + [Throws] + undefined foo(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow LenientFloat to be only in a specific overload") + + parser = parser.reset() + parser.parse( + """ + interface A { + [Throws] + undefined foo(); + [Throws, LenientFloat] + undefined foo(float myFloat); + }; + """ + ) + results = parser.finish() + iface = results[0] + methods = iface.members + lenientFloat = methods[0].getExtendedAttribute("LenientFloat") + harness.ok( + lenientFloat is not None, + "LenientFloat in overloads must be added to the method", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Throws, LenientFloat] + undefined foo(float myFloat); + [Throws] + undefined foo(float myFloat, float yourFloat); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should prevent overloads from getting different restricted float behavior", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Throws] + undefined foo(float myFloat, float yourFloat); + [Throws, LenientFloat] + undefined foo(float myFloat); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should prevent overloads from getting different restricted float behavior (2)", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Throws, LenientFloat] + undefined foo(float myFloat); + [Throws, LenientFloat] + undefined foo(short myShort); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should prevent overloads from getting redundant [LenientFloat]") diff --git a/dom/bindings/parser/tests/test_namespace.py b/dom/bindings/parser/tests/test_namespace.py new file mode 100644 index 0000000000..cf315800ae --- /dev/null +++ b/dom/bindings/parser/tests/test_namespace.py @@ -0,0 +1,232 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + namespace MyNamespace { + attribute any foo; + any bar(); + }; + """ + ) + + results = parser.finish() + harness.check(len(results), 1, "Should have a thing.") + harness.ok(results[0].isNamespace(), "Our thing should be a namespace") + harness.check(len(results[0].members), 2, "Should have two things in our namespace") + harness.ok(results[0].members[0].isAttr(), "First member is attribute") + harness.ok(results[0].members[0].isStatic(), "Attribute should be static") + harness.ok(results[0].members[1].isMethod(), "Second member is method") + harness.ok(results[0].members[1].isStatic(), "Operation should be static") + + parser = parser.reset() + parser.parse( + """ + namespace MyNamespace { + attribute any foo; + }; + partial namespace MyNamespace { + any bar(); + }; + """ + ) + + results = parser.finish() + harness.check(len(results), 2, "Should have things.") + harness.ok(results[0].isNamespace(), "Our thing should be a namespace") + harness.check(len(results[0].members), 2, "Should have two things in our namespace") + harness.ok(results[0].members[0].isAttr(), "First member is attribute") + harness.ok(results[0].members[0].isStatic(), "Attribute should be static") + harness.ok(results[0].members[1].isMethod(), "Second member is method") + harness.ok(results[0].members[1].isStatic(), "Operation should be static") + + parser = parser.reset() + parser.parse( + """ + partial namespace MyNamespace { + any bar(); + }; + namespace MyNamespace { + attribute any foo; + }; + """ + ) + + results = parser.finish() + harness.check(len(results), 2, "Should have things.") + harness.ok(results[1].isNamespace(), "Our thing should be a namespace") + harness.check(len(results[1].members), 2, "Should have two things in our namespace") + harness.ok(results[1].members[0].isAttr(), "First member is attribute") + harness.ok(results[1].members[0].isStatic(), "Attribute should be static") + harness.ok(results[1].members[1].isMethod(), "Second member is method") + harness.ok(results[1].members[1].isStatic(), "Operation should be static") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + namespace MyNamespace { + static attribute any foo; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + namespace MyNamespace { + static any bar(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + namespace MyNamespace { + any bar(); + }; + + interface MyNamespace { + any baz(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface MyNamespace { + any baz(); + }; + + namespace MyNamespace { + any bar(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + namespace MyNamespace { + any baz(); + }; + + namespace MyNamespace { + any bar(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + partial namespace MyNamespace { + any baz(); + }; + + interface MyNamespace { + any bar(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + namespace MyNamespace { + any bar(); + }; + + partial interface MyNamespace { + any baz(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + partial interface MyNamespace { + any baz(); + }; + + namespace MyNamespace { + any bar(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface MyNamespace { + any bar(); + }; + + partial namespace MyNamespace { + any baz(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_newobject.py b/dom/bindings/parser/tests/test_newobject.py new file mode 100644 index 0000000000..efa0385542 --- /dev/null +++ b/dom/bindings/parser/tests/test_newobject.py @@ -0,0 +1,73 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +def WebIDLTest(parser, harness): + # Basic functionality + parser.parse( + """ + interface Iface { + [NewObject] readonly attribute Iface attr; + [NewObject] Iface method(); + }; + """ + ) + results = parser.finish() + harness.ok(results, "Should not have thrown on basic [NewObject] usage") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [Pure, NewObject] readonly attribute Iface attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "[NewObject] attributes must depend on something") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [Pure, NewObject] Iface method(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "[NewObject] methods must depend on something") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [Cached, NewObject, Affects=Nothing] readonly attribute Iface attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "[NewObject] attributes must not be [Cached]") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [StoreInSlot, NewObject, Affects=Nothing] readonly attribute Iface attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "[NewObject] attributes must not be [StoreInSlot]") diff --git a/dom/bindings/parser/tests/test_nullable_equivalency.py b/dom/bindings/parser/tests/test_nullable_equivalency.py new file mode 100644 index 0000000000..278794f719 --- /dev/null +++ b/dom/bindings/parser/tests/test_nullable_equivalency.py @@ -0,0 +1,138 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestNullableEquivalency1 { + attribute long a; + attribute long? b; + }; + + interface TestNullableEquivalency2 { + attribute ArrayBuffer a; + attribute ArrayBuffer? b; + }; + + /* Can't have dictionary-valued attributes, so can't test that here */ + + enum TestNullableEquivalency4Enum { + "Foo", + "Bar" + }; + + interface TestNullableEquivalency4 { + attribute TestNullableEquivalency4Enum a; + attribute TestNullableEquivalency4Enum? b; + }; + + interface TestNullableEquivalency5 { + attribute TestNullableEquivalency4 a; + attribute TestNullableEquivalency4? b; + }; + + interface TestNullableEquivalency6 { + attribute boolean a; + attribute boolean? b; + }; + + interface TestNullableEquivalency7 { + attribute DOMString a; + attribute DOMString? b; + }; + + interface TestNullableEquivalency8 { + attribute float a; + attribute float? b; + }; + + interface TestNullableEquivalency9 { + attribute double a; + attribute double? b; + }; + + interface TestNullableEquivalency10 { + attribute object a; + attribute object? b; + }; + """ + ) + + for decl in parser.finish(): + if decl.isInterface(): + checkEquivalent(decl, harness) + + +def checkEquivalent(iface, harness): + type1 = iface.members[0].type + type2 = iface.members[1].type + + harness.check(type1.nullable(), False, "attr1 should not be nullable") + harness.check(type2.nullable(), True, "attr2 should be nullable") + + # We don't know about type1, but type2, the nullable type, definitely + # shouldn't be builtin. + harness.check(type2.builtin, False, "attr2 should not be builtin") + + # Ensure that all attributes of type2 match those in type1, except for: + # - names on an ignore list, + # - names beginning with '_', + # - functions which throw when called with no args, and + # - class-level non-callables ("static variables"). + # + # Yes, this is an ugly, fragile hack. But it finds bugs... + for attr in dir(type1): + if ( + attr.startswith("_") + or attr + in [ + "nullable", + "builtin", + "filename", + "location", + "inner", + "QName", + "getDeps", + "name", + "prettyName", + ] + or (hasattr(type(type1), attr) and not callable(getattr(type1, attr))) + ): + continue + + a1 = getattr(type1, attr) + + if callable(a1): + try: + v1 = a1() + except Exception: + # Can't call a1 with no args, so skip this attriute. + continue + + try: + a2 = getattr(type2, attr) + except Exception: + harness.ok( + False, + "Missing %s attribute on type %s in %s" % (attr, type2, iface), + ) + continue + + if not callable(a2): + harness.ok( + False, + "%s attribute on type %s in %s wasn't callable" + % (attr, type2, iface), + ) + continue + + v2 = a2() + harness.check(v2, v1, "%s method return value" % attr) + else: + try: + a2 = getattr(type2, attr) + except Exception: + harness.ok( + False, + "Missing %s attribute on type %s in %s" % (attr, type2, iface), + ) + continue + + harness.check(a2, a1, "%s attribute should match" % attr) diff --git a/dom/bindings/parser/tests/test_nullable_void.py b/dom/bindings/parser/tests/test_nullable_void.py new file mode 100644 index 0000000000..5acba727e5 --- /dev/null +++ b/dom/bindings/parser/tests/test_nullable_void.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface NullableVoid { + void? foo(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_observableArray.py b/dom/bindings/parser/tests/test_observableArray.py new file mode 100644 index 0000000000..601f626bcf --- /dev/null +++ b/dom/bindings/parser/tests/test_observableArray.py @@ -0,0 +1,288 @@ +# 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/. + + +def WebIDLTest(parser, harness): + + # Test dictionary as inner type + harness.should_throw( + parser, + """ + dictionary A { + boolean member; + }; + interface B { + attribute ObservableArray<A> foo; + }; + """, + "use dictionary as inner type", + ) + + # Test sequence as inner type + harness.should_throw( + parser, + """ + interface A { + attribute ObservableArray<sequence<boolean>> foo; + }; + """, + "use sequence as inner type", + ) + + # Test sequence<dictionary> as inner type + harness.should_throw( + parser, + """ + dictionary A { + boolean member; + }; + interface B { + attribute ObservableArray<sequence<A>> foo; + }; + """, + "use sequence<dictionary> as inner type", + ) + + # Test record as inner type + harness.should_throw( + parser, + """ + interface A { + attribute ObservableArray<record<DOMString, boolean>> foo; + }; + """, + "use record as inner type", + ) + + # Test record<dictionary> as inner type + harness.should_throw( + parser, + """ + dictionary A { + boolean member; + }; + interface B { + attribute ObservableArray<record<DOMString, A>> foo; + }; + """, + "use record<dictionary> as inner type", + ) + + # Test observable array as inner type + harness.should_throw( + parser, + """ + interface A { + attribute ObservableArray<ObservableArray<boolean>> foo; + }; + """, + "use ObservableArray as inner type", + ) + + # Test nullable attribute + harness.should_throw( + parser, + """ + interface A { + attribute ObservableArray<boolean>? foo; + }; + """, + "nullable", + ) + + # Test sequence + harness.should_throw( + parser, + """ + interface A { + undefined foo(sequence<ObservableArray<boolean>> foo); + }; + """, + "used in sequence", + ) + + # Test record + harness.should_throw( + parser, + """ + interface A { + undefined foo(record<DOMString, ObservableArray<boolean>> foo); + }; + """, + "used in record", + ) + + # Test promise + harness.should_throw( + parser, + """ + interface A { + Promise<ObservableArray<boolean>> foo(); + }; + """, + "used in promise", + ) + + # Test union + harness.should_throw( + parser, + """ + interface A { + attribute (DOMString or ObservableArray<boolean>>) foo; + }; + """, + "used in union", + ) + + # Test dictionary member + harness.should_throw( + parser, + """ + dictionary A { + ObservableArray<boolean> foo; + }; + """, + "used on dictionary member type", + ) + + # Test argument + harness.should_throw( + parser, + """ + interface A { + undefined foo(ObservableArray<boolean> foo); + }; + """, + "used on argument", + ) + + # Test static attribute + harness.should_throw( + parser, + """ + interface A { + static attribute ObservableArray<boolean> foo; + }; + """, + "used on static attribute type", + ) + + # Test iterable + harness.should_throw( + parser, + """ + interface A { + iterable<ObservableArray<boolean>>; + }; + """, + "used in iterable", + ) + + # Test maplike + harness.should_throw( + parser, + """ + interface A { + maplike<long, ObservableArray<boolean>>; + }; + """, + "used in maplike", + ) + + # Test setlike + harness.should_throw( + parser, + """ + interface A { + setlike<ObservableArray<boolean>>; + }; + """, + "used in setlike", + ) + + # Test JS implemented interface + harness.should_throw( + parser, + """ + [JSImplementation="@mozilla.org/dom/test-interface-js;1"] + interface A { + readonly attribute ObservableArray<boolean> foo; + }; + """, + "used in JS implemented interface", + ) + + # Test namespace + harness.should_throw( + parser, + """ + namespace A { + readonly attribute ObservableArray<boolean> foo; + }; + """, + "used in namespaces", + ) + + # Test [Cached] extended attribute + harness.should_throw( + parser, + """ + interface A { + [Cached, Pure] + readonly attribute ObservableArray<boolean> foo; + }; + """, + "have Cached extended attribute", + ) + + # Test [StoreInSlot] extended attribute + harness.should_throw( + parser, + """ + interface A { + [StoreInSlot, Pure] + readonly attribute ObservableArray<boolean> foo; + }; + """, + "have StoreInSlot extended attribute", + ) + + # Test regular attribute + parser = parser.reset() + parser.parse( + """ + interface A { + readonly attribute ObservableArray<boolean> foo; + attribute ObservableArray<[Clamp] octet> bar; + attribute ObservableArray<long?> baz; + attribute ObservableArray<(boolean or long)> qux; + }; + """ + ) + results = parser.finish() + A = results[0] + foo = A.members[0] + harness.ok(foo.readonly, "A.foo is readonly attribute") + harness.ok(foo.type.isObservableArray(), "A.foo is ObservableArray type") + harness.check( + foo.slotIndices[A.identifier.name], 0, "A.foo should be stored in slot" + ) + bar = A.members[1] + harness.ok(bar.type.isObservableArray(), "A.bar is ObservableArray type") + harness.check( + bar.slotIndices[A.identifier.name], 1, "A.bar should be stored in slot" + ) + harness.ok(bar.type.inner.hasClamp(), "A.bar's inner type should be clamped") + baz = A.members[2] + harness.ok(baz.type.isObservableArray(), "A.baz is ObservableArray type") + harness.check( + baz.slotIndices[A.identifier.name], 2, "A.baz should be stored in slot" + ) + harness.ok(baz.type.inner.nullable(), "A.baz's inner type should be nullable") + qux = A.members[3] + harness.ok(qux.type.isObservableArray(), "A.qux is ObservableArray type") + harness.check( + qux.slotIndices[A.identifier.name], 3, "A.qux should be stored in slot" + ) + harness.ok(qux.type.inner.isUnion(), "A.qux's inner type should be union") diff --git a/dom/bindings/parser/tests/test_optional_constraints.py b/dom/bindings/parser/tests/test_optional_constraints.py new file mode 100644 index 0000000000..78e71df544 --- /dev/null +++ b/dom/bindings/parser/tests/test_optional_constraints.py @@ -0,0 +1,35 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface OptionalConstraints1 { + undefined foo(optional byte arg1, byte arg2); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + not threw, + "Should not have thrown on non-optional argument following " + "optional argument.", + ) + + parser = parser.reset() + parser.parse( + """ + interface OptionalConstraints2 { + undefined foo(optional byte arg1 = 1, optional byte arg2 = 2, + optional byte arg3, optional byte arg4 = 4, + optional byte arg5, optional byte arg6 = 9); + }; + """ + ) + results = parser.finish() + args = results[0].members[0].signatures()[0][1] + harness.check(len(args), 6, "Should have 6 arguments") + harness.check(args[5].defaultValue.value, 9, "Should have correct default value") diff --git a/dom/bindings/parser/tests/test_overload.py b/dom/bindings/parser/tests/test_overload.py new file mode 100644 index 0000000000..7816276aa6 --- /dev/null +++ b/dom/bindings/parser/tests/test_overload.py @@ -0,0 +1,74 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestOverloads { + undefined basic(); + undefined basic(long arg1); + boolean abitharder(TestOverloads foo); + boolean abitharder(boolean foo); + undefined abitharder(ArrayBuffer? foo); + undefined withVariadics(long... numbers); + undefined withVariadics(TestOverloads iface); + undefined withVariadics(long num, TestOverloads iface); + undefined optionalTest(); + undefined optionalTest(optional long num1, long num2); + }; + """ + ) + + results = parser.finish() + + harness.ok(True, "TestOverloads interface parsed without error.") + harness.check(len(results), 1, "Should be one production.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface") + harness.check( + iface.identifier.QName(), "::TestOverloads", "Interface has the right QName" + ) + harness.check( + iface.identifier.name, "TestOverloads", "Interface has the right name" + ) + harness.check(len(iface.members), 4, "Expect %s members" % 4) + + member = iface.members[0] + harness.check( + member.identifier.QName(), + "::TestOverloads::basic", + "Method has the right QName", + ) + harness.check(member.identifier.name, "basic", "Method has the right name") + harness.check(member.hasOverloads(), True, "Method has overloads") + + signatures = member.signatures() + harness.check(len(signatures), 2, "Method should have 2 signatures") + + (retval, argumentSet) = signatures[0] + + harness.check(str(retval), "Undefined", "Expect an undefined retval") + harness.check(len(argumentSet), 0, "Expect an empty argument set") + + (retval, argumentSet) = signatures[1] + harness.check(str(retval), "Undefined", "Expect an undefined retval") + harness.check(len(argumentSet), 1, "Expect an argument set with one argument") + + argument = argumentSet[0] + harness.ok(isinstance(argument, WebIDL.IDLArgument), "Should be an IDLArgument") + harness.check( + argument.identifier.QName(), + "::TestOverloads::basic::arg1", + "Argument has the right QName", + ) + harness.check(argument.identifier.name, "arg1", "Argument has the right name") + harness.check(str(argument.type), "Long", "Argument has the right type") + + member = iface.members[3] + harness.check( + len(member.overloadsForArgCount(0)), 1, "Only one overload for no args" + ) + harness.check(len(member.overloadsForArgCount(1)), 0, "No overloads for one arg") + harness.check( + len(member.overloadsForArgCount(2)), 1, "Only one overload for two args" + ) diff --git a/dom/bindings/parser/tests/test_promise.py b/dom/bindings/parser/tests/test_promise.py new file mode 100644 index 0000000000..e8051c36f0 --- /dev/null +++ b/dom/bindings/parser/tests/test_promise.py @@ -0,0 +1,177 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface A { + legacycaller Promise<any> foo(); + }; + """ + ) + parser.finish() + + except Exception: + threw = True + harness.ok(threw, "Should not allow Promise return values for legacycaller.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + Promise<any> foo(); + long foo(long arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow overloads which have both Promise and " + "non-Promise return types.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + long foo(long arg); + Promise<any> foo(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow overloads which have both Promise and " + "non-Promise return types.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + Promise<any>? foo(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow nullable Promise return values.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + undefined foo(Promise<any>? arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow nullable Promise arguments.") + + parser = parser.reset() + parser.parse( + """ + interface A { + Promise<any> foo(); + Promise<any> foo(long arg); + }; + """ + ) + parser.finish() + + harness.ok( + True, "Should allow overloads which only have Promise and return " "types." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + attribute Promise<any> attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow writable Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [LegacyLenientSetter] readonly attribute Promise<any> attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should not allow [LegacyLenientSetter] Promise-typed attributes." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [PutForwards=bar] readonly attribute Promise<any> attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [PutForwards] Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [Replaceable] readonly attribute Promise<any> attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [Replaceable] Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface A { + [SameObject] readonly attribute Promise<any> attr; + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow [SameObject] Promise-typed attributes.") diff --git a/dom/bindings/parser/tests/test_prototype_ident.py b/dom/bindings/parser/tests/test_prototype_ident.py new file mode 100644 index 0000000000..a47f42af9d --- /dev/null +++ b/dom/bindings/parser/tests/test_prototype_ident.py @@ -0,0 +1,107 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface TestIface { + static attribute boolean prototype; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "The identifier of a static attribute must not be 'prototype'") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestIface { + static boolean prototype(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "The identifier of a static operation must not be 'prototype'") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestIface { + const boolean prototype = true; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "The identifier of a constant must not be 'prototype'") + + # Make sure that we can parse non-static attributes with 'prototype' as identifier. + parser = parser.reset() + parser.parse( + """ + interface TestIface { + attribute boolean prototype; + }; + """ + ) + results = parser.finish() + + testIface = results[0] + harness.check( + testIface.members[0].isStatic(), False, "Attribute should not be static" + ) + harness.check( + testIface.members[0].identifier.name, + "prototype", + "Attribute identifier should be 'prototype'", + ) + + # Make sure that we can parse non-static operations with 'prototype' as identifier. + parser = parser.reset() + parser.parse( + """ + interface TestIface { + boolean prototype(); + }; + """ + ) + results = parser.finish() + + testIface = results[0] + harness.check( + testIface.members[0].isStatic(), False, "Operation should not be static" + ) + harness.check( + testIface.members[0].identifier.name, + "prototype", + "Operation identifier should be 'prototype'", + ) + + # Make sure that we can parse dictionary members with 'prototype' as identifier. + parser = parser.reset() + parser.parse( + """ + dictionary TestDict { + boolean prototype; + }; + """ + ) + results = parser.finish() + + testDict = results[0] + harness.check( + testDict.members[0].identifier.name, + "prototype", + "Dictionary member should be 'prototype'", + ) diff --git a/dom/bindings/parser/tests/test_putForwards.py b/dom/bindings/parser/tests/test_putForwards.py new file mode 100644 index 0000000000..4e8504f766 --- /dev/null +++ b/dom/bindings/parser/tests/test_putForwards.py @@ -0,0 +1,119 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface I { + [PutForwards=B] readonly attribute long A; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface I { + [PutForwards=B] readonly attribute J A; + }; + interface J { + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface I { + [PutForwards=B] attribute J A; + }; + interface J { + attribute long B; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface I { + [PutForwards=B] static readonly attribute J A; + }; + interface J { + attribute long B; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + callback interface I { + [PutForwards=B] readonly attribute J A; + }; + interface J { + attribute long B; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface I { + [PutForwards=C] readonly attribute J A; + [PutForwards=C] readonly attribute J B; + }; + interface J { + [PutForwards=D] readonly attribute K C; + }; + interface K { + [PutForwards=A] readonly attribute I D; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_record.py b/dom/bindings/parser/tests/test_record.py new file mode 100644 index 0000000000..930b0e1a90 --- /dev/null +++ b/dom/bindings/parser/tests/test_record.py @@ -0,0 +1,61 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + dictionary Dict {}; + interface RecordArg { + undefined foo(record<DOMString, Dict> arg); + }; + """ + ) + + results = parser.finish() + + harness.check(len(results), 2, "Should know about two things") + harness.ok( + isinstance(results[1], WebIDL.IDLInterface), "Should have an interface here" + ) + members = results[1].members + harness.check(len(members), 1, "Should have one member") + harness.ok(members[0].isMethod(), "Should have method") + signature = members[0].signatures()[0] + args = signature[1] + harness.check(len(args), 1, "Should have one arg") + harness.ok(args[0].type.isRecord(), "Should have a record type here") + harness.ok(args[0].type.inner.isDictionary(), "Should have a dictionary inner type") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface RecordUndefinedArg { + undefined foo(record<DOMString, undefined> arg); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "Should have thrown because record can't have undefined as value type." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + dictionary Dict { + record<DOMString, Dict> val; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown on dictionary containing itself via record.") diff --git a/dom/bindings/parser/tests/test_replaceable.py b/dom/bindings/parser/tests/test_replaceable.py new file mode 100644 index 0000000000..3b6df65c07 --- /dev/null +++ b/dom/bindings/parser/tests/test_replaceable.py @@ -0,0 +1,84 @@ +# 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/. + + +def should_throw(parser, harness, message, code): + parser = parser.reset() + threw = False + try: + parser.parse(code) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown: %s" % message) + + +def WebIDLTest(parser, harness): + # The [Replaceable] extended attribute MUST take no arguments. + should_throw( + parser, + harness, + "no arguments", + """ + interface I { + [Replaceable=X] readonly attribute long A; + }; + """, + ) + + # An attribute with the [Replaceable] extended attribute MUST NOT also be + # declared with the [PutForwards] extended attribute. + should_throw( + parser, + harness, + "PutForwards", + """ + interface I { + [PutForwards=B, Replaceable] readonly attribute J A; + }; + interface J { + attribute long B; + }; + """, + ) + + # The [Replaceable] extended attribute MUST NOT be used on an attribute + # that is not read only. + should_throw( + parser, + harness, + "writable attribute", + """ + interface I { + [Replaceable] attribute long A; + }; + """, + ) + + # The [Replaceable] extended attribute MUST NOT be used on a static + # attribute. + should_throw( + parser, + harness, + "static attribute", + """ + interface I { + [Replaceable] static readonly attribute long A; + }; + """, + ) + + # The [Replaceable] extended attribute MUST NOT be used on an attribute + # declared on a callback interface. + should_throw( + parser, + harness, + "callback interface", + """ + callback interface I { + [Replaceable] readonly attribute long A; + }; + """, + ) diff --git a/dom/bindings/parser/tests/test_sanity.py b/dom/bindings/parser/tests/test_sanity.py new file mode 100644 index 0000000000..d3184c0073 --- /dev/null +++ b/dom/bindings/parser/tests/test_sanity.py @@ -0,0 +1,7 @@ +def WebIDLTest(parser, harness): + parser.parse("") + parser.finish() + harness.ok(True, "Parsing nothing doesn't throw.") + parser.parse("interface Foo {};") + parser.finish() + harness.ok(True, "Parsing a silly interface doesn't throw.") diff --git a/dom/bindings/parser/tests/test_securecontext_extended_attribute.py b/dom/bindings/parser/tests/test_securecontext_extended_attribute.py new file mode 100644 index 0000000000..f7848bf092 --- /dev/null +++ b/dom/bindings/parser/tests/test_securecontext_extended_attribute.py @@ -0,0 +1,538 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + [SecureContext] + interface TestSecureContextOnInterface { + const octet TEST_CONSTANT = 0; + readonly attribute byte testAttribute; + undefined testMethod(byte foo); + }; + partial interface TestSecureContextOnInterface { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + undefined testMethod2(byte foo); + }; + """ + ) + results = parser.finish() + harness.check( + len(results[0].members), + 6, + "TestSecureContextOnInterface should have six members", + ) + harness.ok( + results[0].getExtendedAttribute("SecureContext"), + "Interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[0].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to constant members", + ) + harness.ok( + results[0].members[1].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to attribute members", + ) + harness.ok( + results[0].members[2].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to method members", + ) + harness.ok( + results[0].members[3].getExtendedAttribute("SecureContext"), + ( + "[SecureContext] should propagate from interface to " + "constant members from partial interface" + ), + ) + harness.ok( + results[0].members[4].getExtendedAttribute("SecureContext"), + ( + "[SecureContext] should propagate from interface to " + "attribute members from partial interface" + ), + ) + harness.ok( + results[0].members[5].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to method members from partial interface", + ) + + # Same thing, but with the partial interface specified first: + parser = parser.reset() + parser.parse( + """ + partial interface TestSecureContextOnInterfaceAfterPartialInterface { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + undefined testMethod2(byte foo); + }; + [SecureContext] + interface TestSecureContextOnInterfaceAfterPartialInterface { + const octet TEST_CONSTANT = 0; + readonly attribute byte testAttribute; + undefined testMethod(byte foo); + }; + """ + ) + results = parser.finish() + harness.check( + len(results[1].members), + 6, + "TestSecureContextOnInterfaceAfterPartialInterface should have six members", + ) + harness.ok( + results[1].getExtendedAttribute("SecureContext"), + "Interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[1].members[0].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to constant members", + ) + harness.ok( + results[1].members[1].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to attribute members", + ) + harness.ok( + results[1].members[2].getExtendedAttribute("SecureContext"), + "[SecureContext] should propagate from interface to method members", + ) + harness.ok( + results[1].members[3].getExtendedAttribute("SecureContext"), + ( + "[SecureContext] should propagate from interface to constant members from " + "partial interface" + ), + ) + harness.ok( + results[1].members[4].getExtendedAttribute("SecureContext"), + ( + "[SecureContext] should propagate from interface to attribute members from " + "partial interface" + ), + ) + harness.ok( + results[1].members[5].getExtendedAttribute("SecureContext"), + ( + "[SecureContext] should propagate from interface to method members from partial " + "interface" + ), + ) + + parser = parser.reset() + parser.parse( + """ + interface TestSecureContextOnPartialInterface { + const octet TEST_CONSTANT = 0; + readonly attribute byte testAttribute; + undefined testMethod(byte foo); + }; + [SecureContext] + partial interface TestSecureContextOnPartialInterface { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + undefined testMethod2(byte foo); + }; + """ + ) + results = parser.finish() + harness.check( + len(results[0].members), + 6, + "TestSecureContextOnPartialInterface should have six members", + ) + harness.ok( + results[0].getExtendedAttribute("SecureContext") is None, + "[SecureContext] should not propagate from a partial interface to the interface", + ) + harness.ok( + results[0].members[0].getExtendedAttribute("SecureContext") is None, + ( + "[SecureContext] should not propagate from a partial interface to the interface's " + "constant members" + ), + ) + harness.ok( + results[0].members[1].getExtendedAttribute("SecureContext") is None, + ( + "[SecureContext] should not propagate from a partial interface to the interface's " + "attribute members" + ), + ) + harness.ok( + results[0].members[2].getExtendedAttribute("SecureContext") is None, + ( + "[SecureContext] should not propagate from a partial interface to the interface's " + "method members" + ), + ) + harness.ok( + results[0].members[3].getExtendedAttribute("SecureContext"), + "Constant members from [SecureContext] partial interface should be [SecureContext]", + ) + harness.ok( + results[0].members[4].getExtendedAttribute("SecureContext"), + "Attribute members from [SecureContext] partial interface should be [SecureContext]", + ) + harness.ok( + results[0].members[5].getExtendedAttribute("SecureContext"), + "Method members from [SecureContext] partial interface should be [SecureContext]", + ) + + parser = parser.reset() + parser.parse( + """ + interface TestSecureContextOnInterfaceMembers { + const octet TEST_NON_SECURE_CONSTANT_1 = 0; + [SecureContext] + const octet TEST_SECURE_CONSTANT = 1; + const octet TEST_NON_SECURE_CONSTANT_2 = 2; + readonly attribute byte testNonSecureAttribute1; + [SecureContext] + readonly attribute byte testSecureAttribute; + readonly attribute byte testNonSecureAttribute2; + undefined testNonSecureMethod1(byte foo); + [SecureContext] + undefined testSecureMethod(byte foo); + undefined testNonSecureMethod2(byte foo); + }; + """ + ) + results = parser.finish() + harness.check( + len(results[0].members), + 9, + "TestSecureContextOnInterfaceMembers should have nine members", + ) + harness.ok( + results[0].getExtendedAttribute("SecureContext") is None, + "[SecureContext] on members should not propagate up to the interface", + ) + harness.ok( + results[0].members[0].getExtendedAttribute("SecureContext") is None, + "Constant should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[1].getExtendedAttribute("SecureContext"), + "Constant should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[2].getExtendedAttribute("SecureContext") is None, + "Constant should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[3].getExtendedAttribute("SecureContext") is None, + "Attribute should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[4].getExtendedAttribute("SecureContext"), + "Attribute should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[5].getExtendedAttribute("SecureContext") is None, + "Attribute should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[6].getExtendedAttribute("SecureContext") is None, + "Method should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[7].getExtendedAttribute("SecureContext"), + "Method should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[8].getExtendedAttribute("SecureContext") is None, + "Method should not have [SecureContext] extended attribute", + ) + + parser = parser.reset() + parser.parse( + """ + interface TestSecureContextOnPartialInterfaceMembers { + }; + partial interface TestSecureContextOnPartialInterfaceMembers { + const octet TEST_NON_SECURE_CONSTANT_1 = 0; + [SecureContext] + const octet TEST_SECURE_CONSTANT = 1; + const octet TEST_NON_SECURE_CONSTANT_2 = 2; + readonly attribute byte testNonSecureAttribute1; + [SecureContext] + readonly attribute byte testSecureAttribute; + readonly attribute byte testNonSecureAttribute2; + undefined testNonSecureMethod1(byte foo); + [SecureContext] + undefined testSecureMethod(byte foo); + undefined testNonSecureMethod2(byte foo); + }; + """ + ) + results = parser.finish() + harness.check( + len(results[0].members), + 9, + "TestSecureContextOnPartialInterfaceMembers should have nine members", + ) + harness.ok( + results[0].members[0].getExtendedAttribute("SecureContext") is None, + "Constant from partial interface should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[1].getExtendedAttribute("SecureContext"), + "Constant from partial interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[2].getExtendedAttribute("SecureContext") is None, + "Constant from partial interface should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[3].getExtendedAttribute("SecureContext") is None, + "Attribute from partial interface should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[4].getExtendedAttribute("SecureContext"), + "Attribute from partial interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[5].getExtendedAttribute("SecureContext") is None, + "Attribute from partial interface should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[6].getExtendedAttribute("SecureContext") is None, + "Method from partial interface should not have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[7].getExtendedAttribute("SecureContext"), + "Method from partial interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[8].getExtendedAttribute("SecureContext") is None, + "Method from partial interface should not have [SecureContext] extended attribute", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [SecureContext=something] + interface TestSecureContextTakesNoValue1 { + const octet TEST_SECURE_CONSTANT = 0; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "[SecureContext] must take no arguments (testing on interface)") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestSecureContextForOverloads1 { + [SecureContext] + undefined testSecureMethod(byte foo); + }; + partial interface TestSecureContextForOverloads1 { + undefined testSecureMethod(byte foo, byte bar); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + ( + "If [SecureContext] appears on an overloaded operation, then it MUST appear on all " + "overloads" + ), + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestSecureContextForOverloads2 { + [SecureContext] + undefined testSecureMethod(byte foo); + }; + partial interface TestSecureContextForOverloads2 { + [SecureContext] + undefined testSecureMethod(byte foo, byte bar); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + not threw, + "[SecureContext] can appear on an overloaded operation if it appears on all overloads", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [SecureContext] + interface TestSecureContextOnInterfaceAndMember { + [SecureContext] + undefined testSecureMethod(byte foo); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, "[SecureContext] must not appear on an interface and interface member" + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestSecureContextOnPartialInterfaceAndMember { + }; + [SecureContext] + partial interface TestSecureContextOnPartialInterfaceAndMember { + [SecureContext] + undefined testSecureMethod(byte foo); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + ( + "[SecureContext] must not appear on a partial interface and one of the partial " + "interface's member's" + ), + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [SecureContext] + interface TestSecureContextOnInterfaceAndPartialInterfaceMember { + }; + partial interface TestSecureContextOnInterfaceAndPartialInterfaceMember { + [SecureContext] + undefined testSecureMethod(byte foo); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + ( + "[SecureContext] must not appear on an interface and one of its partial interface's " + "member's" + ), + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [SecureContext] + interface TestSecureContextOnInheritedInterface { + }; + interface TestSecureContextNotOnInheritingInterface : TestSecureContextOnInheritedInterface { + undefined testSecureMethod(byte foo); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + ( + "[SecureContext] must appear on interfaces that inherit from another [SecureContext] " + "interface" + ), + ) + + # Test 'includes'. + parser = parser.reset() + parser.parse( + """ + [SecureContext] + interface TestSecureContextInterfaceThatIncludesNonSecureContextMixin { + const octet TEST_CONSTANT = 0; + }; + interface mixin TestNonSecureContextMixin { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + undefined testMethod2(byte foo); + }; + TestSecureContextInterfaceThatIncludesNonSecureContextMixin includes TestNonSecureContextMixin; + """ + ) + results = parser.finish() + harness.check( + len(results[0].members), + 4, + ( + "TestSecureContextInterfaceThatImplementsNonSecureContextInterface should have four " + "members" + ), + ) + harness.ok( + results[0].getExtendedAttribute("SecureContext"), + "Interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[0].getExtendedAttribute("SecureContext"), + ( + "[SecureContext] should propagate from interface to constant members even when other " + "members are copied from a non-[SecureContext] interface" + ), + ) + harness.ok( + results[0].members[1].getExtendedAttribute("SecureContext") is None, + "Constants copied from non-[SecureContext] mixin should not be [SecureContext]", + ) + harness.ok( + results[0].members[2].getExtendedAttribute("SecureContext") is None, + "Attributes copied from non-[SecureContext] mixin should not be [SecureContext]", + ) + harness.ok( + results[0].members[3].getExtendedAttribute("SecureContext") is None, + "Methods copied from non-[SecureContext] mixin should not be [SecureContext]", + ) + + # Test SecureContext and LegacyNoInterfaceObject + parser = parser.reset() + parser.parse( + """ + [LegacyNoInterfaceObject, SecureContext] + interface TestSecureContextLegacyNoInterfaceObject { + undefined testSecureMethod(byte foo); + }; + """ + ) + results = parser.finish() + harness.check( + len(results[0].members), + 1, + "TestSecureContextLegacyNoInterfaceObject should have only one member", + ) + harness.ok( + results[0].getExtendedAttribute("SecureContext"), + "Interface should have [SecureContext] extended attribute", + ) + harness.ok( + results[0].members[0].getExtendedAttribute("SecureContext"), + "Interface member should have [SecureContext] extended attribute", + ) diff --git a/dom/bindings/parser/tests/test_special_method_signature_mismatch.py b/dom/bindings/parser/tests/test_special_method_signature_mismatch.py new file mode 100644 index 0000000000..edbb396bd3 --- /dev/null +++ b/dom/bindings/parser/tests/test_special_method_signature_mismatch.py @@ -0,0 +1,256 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch1 { + getter long long foo(long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch2 { + getter undefined foo(unsigned long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch3 { + getter boolean foo(unsigned long index, boolean extraArg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch4 { + getter boolean foo(unsigned long... index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch5 { + getter boolean foo(optional unsigned long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch6 { + getter boolean foo(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch7 { + deleter long long foo(long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch9 { + deleter boolean foo(unsigned long index, boolean extraArg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch10 { + deleter boolean foo(unsigned long... index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch11 { + deleter boolean foo(optional unsigned long index); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch12 { + deleter boolean foo(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch13 { + setter long long foo(long index, long long value); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch15 { + setter boolean foo(unsigned long index, boolean value, long long extraArg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch16 { + setter boolean foo(unsigned long index, boolean... value); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch17 { + setter boolean foo(unsigned long index, optional boolean value); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodSignatureMismatch18 { + setter boolean foo(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_special_methods.py b/dom/bindings/parser/tests/test_special_methods.py new file mode 100644 index 0000000000..64b6cea9e0 --- /dev/null +++ b/dom/bindings/parser/tests/test_special_methods.py @@ -0,0 +1,117 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface SpecialMethods { + getter long long (unsigned long index); + setter long long (unsigned long index, long long value); + getter boolean (DOMString name); + setter boolean (DOMString name, boolean value); + deleter boolean (DOMString name); + readonly attribute unsigned long length; + }; + + interface SpecialMethodsCombination { + getter deleter boolean (DOMString name); + }; + """ + ) + + results = parser.finish() + + def checkMethod( + method, + QName, + name, + static=False, + getter=False, + setter=False, + deleter=False, + legacycaller=False, + stringifier=False, + ): + harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") + harness.check(method.identifier.QName(), QName, "Method has the right QName") + harness.check(method.identifier.name, name, "Method has the right name") + harness.check(method.isStatic(), static, "Method has the correct static value") + harness.check(method.isGetter(), getter, "Method has the correct getter value") + harness.check(method.isSetter(), setter, "Method has the correct setter value") + harness.check( + method.isDeleter(), deleter, "Method has the correct deleter value" + ) + harness.check( + method.isLegacycaller(), + legacycaller, + "Method has the correct legacycaller value", + ) + harness.check( + method.isStringifier(), + stringifier, + "Method has the correct stringifier value", + ) + + harness.check(len(results), 2, "Expect 2 interfaces") + + iface = results[0] + harness.check(len(iface.members), 6, "Expect 6 members") + + checkMethod( + iface.members[0], + "::SpecialMethods::__indexedgetter", + "__indexedgetter", + getter=True, + ) + checkMethod( + iface.members[1], + "::SpecialMethods::__indexedsetter", + "__indexedsetter", + setter=True, + ) + checkMethod( + iface.members[2], + "::SpecialMethods::__namedgetter", + "__namedgetter", + getter=True, + ) + checkMethod( + iface.members[3], + "::SpecialMethods::__namedsetter", + "__namedsetter", + setter=True, + ) + checkMethod( + iface.members[4], + "::SpecialMethods::__nameddeleter", + "__nameddeleter", + deleter=True, + ) + + iface = results[1] + harness.check(len(iface.members), 1, "Expect 1 member") + + checkMethod( + iface.members[0], + "::SpecialMethodsCombination::__namedgetterdeleter", + "__namedgetterdeleter", + getter=True, + deleter=True, + ) + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + interface IndexedDeleter { + deleter undefined(unsigned long index); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "There are no indexed deleters") diff --git a/dom/bindings/parser/tests/test_special_methods_uniqueness.py b/dom/bindings/parser/tests/test_special_methods_uniqueness.py new file mode 100644 index 0000000000..b7781207ee --- /dev/null +++ b/dom/bindings/parser/tests/test_special_methods_uniqueness.py @@ -0,0 +1,51 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface SpecialMethodUniqueness1 { + getter deleter boolean (DOMString name); + getter boolean (DOMString name); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodUniqueness1 { + deleter boolean (DOMString name); + getter deleter boolean (DOMString name); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse( + """ + interface SpecialMethodUniqueness1 { + setter boolean (DOMString name); + setter boolean (DOMString name); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_stringifier.py b/dom/bindings/parser/tests/test_stringifier.py new file mode 100644 index 0000000000..13c7ce4af8 --- /dev/null +++ b/dom/bindings/parser/tests/test_stringifier.py @@ -0,0 +1,196 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestStringifier { + stringifier; + }; + """ + ) + + results = parser.finish() + + harness.ok( + isinstance(results[0].members[0], WebIDL.IDLMethod), + "Stringifer should be method", + ) + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + interface TestStringifier { + stringifier; + stringifier; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow two 'stringifier;'") + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + interface TestStringifier { + stringifier; + stringifier DOMString foo(); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow a 'stringifier;' and a 'stringifier()'") + + parser = parser.reset() + parser.parse( + """ + interface TestStringifier { + stringifier attribute DOMString foo; + }; + """ + ) + results = parser.finish() + harness.ok( + isinstance(results[0].members[0], WebIDL.IDLAttribute), + "Stringifier attribute should be an attribute", + ) + stringifier = results[0].members[1] + harness.ok( + isinstance(stringifier, WebIDL.IDLMethod), + "Stringifier attribute should insert a method", + ) + harness.ok(stringifier.isStringifier(), "Inserted method should be a stringifier") + + parser = parser.reset() + parser.parse( + """ + interface TestStringifier {}; + interface mixin TestStringifierMixin { + stringifier attribute DOMString foo; + }; + TestStringifier includes TestStringifierMixin; + """ + ) + results = parser.finish() + harness.ok( + isinstance(results[0].members[0], WebIDL.IDLAttribute), + "Stringifier attribute should be an attribute", + ) + stringifier = results[0].members[1] + harness.ok( + isinstance(stringifier, WebIDL.IDLMethod), + "Stringifier attribute should insert a method", + ) + harness.ok(stringifier.isStringifier(), "Inserted method should be a stringifier") + + parser = parser.reset() + parser.parse( + """ + interface TestStringifier { + stringifier attribute USVString foo; + }; + """ + ) + results = parser.finish() + stringifier = results[0].members[1] + harness.ok( + stringifier.signatures()[0][0].isUSVString(), + "Stringifier attributes should allow USVString", + ) + + parser = parser.reset() + parser.parse( + """ + interface TestStringifier { + [Throws, NeedsSubjectPrincipal] + stringifier attribute USVString foo; + }; + """ + ) + results = parser.finish() + stringifier = results[0].members[1] + harness.ok( + stringifier.getExtendedAttribute("Throws"), + "Stringifier attributes should support [Throws]", + ) + harness.ok( + stringifier.getExtendedAttribute("NeedsSubjectPrincipal"), + "Stringifier attributes should support [NeedsSubjectPrincipal]", + ) + + parser = parser.reset() + parser.parse( + """ + interface TestStringifier { + stringifier attribute UTF8String foo; + }; + """ + ) + results = parser.finish() + stringifier = results[0].members[1] + harness.ok( + stringifier.signatures()[0][0].isUTF8String(), + "Stringifier attributes should allow UTF8String", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestStringifier { + stringifier attribute ByteString foo; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow ByteString") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestStringifier { + stringifier; + stringifier attribute DOMString foo; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow a 'stringifier;' and a stringifier attribute") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface TestStringifier { + stringifier attribute DOMString foo; + stringifier attribute DOMString bar; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should not allow multiple stringifier attributes") diff --git a/dom/bindings/parser/tests/test_toJSON.py b/dom/bindings/parser/tests/test_toJSON.py new file mode 100644 index 0000000000..42bad30e50 --- /dev/null +++ b/dom/bindings/parser/tests/test_toJSON.py @@ -0,0 +1,309 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface Test { + object toJSON(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow a toJSON method.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + object toJSON(object arg); + object toJSON(long arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow overloads of a toJSON method.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + object toJSON(object arg); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should not allow a toJSON method with arguments.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + long toJSON(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok(not threw, "Should allow a toJSON method with 'long' as return type.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + [Default] object toJSON(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok( + not threw, "Should allow a default toJSON method with 'object' as return type." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + [Default] long toJSON(); + }; + """ + ) + parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should not allow a default toJSON method with non-'object' as return type.", + ) + + JsonTypes = [ + "byte", + "octet", + "short", + "unsigned short", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "unrestricted float", + "double", + "unrestricted double", + "boolean", + "DOMString", + "ByteString", + "UTF8String", + "USVString", + "Enum", + "InterfaceWithToJSON", + "object", + ] + + nonJsonTypes = [ + "InterfaceWithoutToJSON", + "any", + "Int8Array", + "Int16Array", + "Int32Array", + "Uint8Array", + "Uint16Array", + "Uint32Array", + "Uint8ClampedArray", + "Float32Array", + "Float64Array", + "ArrayBuffer", + ] + + def doTest(testIDL, shouldThrow, description): + p = parser.reset() + threw = False + try: + p.parse( + testIDL + + """ + enum Enum { "a", "b", "c" }; + interface InterfaceWithToJSON { long toJSON(); }; + interface InterfaceWithoutToJSON {}; + """ + ) + p.finish() + except Exception as x: + threw = True + harness.ok(x.message == "toJSON method has non-JSON return type", x) + harness.check(threw, shouldThrow, description) + + for type in JsonTypes: + doTest( + "interface Test { %s toJSON(); };" % type, + False, + "%s should be a JSON type" % type, + ) + + doTest( + "interface Test { sequence<%s> toJSON(); };" % type, + False, + "sequence<%s> should be a JSON type" % type, + ) + + doTest( + "dictionary Foo { %s foo; }; " "interface Test { Foo toJSON(); }; " % type, + False, + "dictionary containing only JSON type (%s) should be a JSON type" % type, + ) + + doTest( + "dictionary Foo { %s foo; }; dictionary Bar : Foo { }; " + "interface Test { Bar toJSON(); }; " % type, + False, + "dictionary whose ancestors only contain JSON types should be a JSON type", + ) + + doTest( + "dictionary Foo { any foo; }; dictionary Bar : Foo { %s bar; };" + "interface Test { Bar toJSON(); };" % type, + True, + "dictionary whose ancestors contain non-JSON types should not be a JSON type", + ) + + doTest( + "interface Test { record<DOMString, %s> toJSON(); };" % type, + False, + "record<DOMString, %s> should be a JSON type" % type, + ) + + doTest( + "interface Test { record<ByteString, %s> toJSON(); };" % type, + False, + "record<ByteString, %s> should be a JSON type" % type, + ) + + doTest( + "interface Test { record<UTF8String, %s> toJSON(); };" % type, + False, + "record<UTF8String, %s> should be a JSON type" % type, + ) + + doTest( + "interface Test { record<USVString, %s> toJSON(); };" % type, + False, + "record<USVString, %s> should be a JSON type" % type, + ) + + otherUnionType = "Foo" if type != "object" else "long" + doTest( + "interface Foo { object toJSON(); };" + "interface Test { (%s or %s) toJSON(); };" % (otherUnionType, type), + False, + "union containing only JSON types (%s or %s) should be a JSON type" + % (otherUnionType, type), + ) + + doTest( + "interface test { %s? toJSON(); };" % type, + False, + "Nullable type (%s) should be a JSON type" % type, + ) + + doTest( + "interface Foo : InterfaceWithoutToJSON { %s toJSON(); };" + "interface Test { Foo toJSON(); };" % type, + False, + "interface with toJSON should be a JSON type", + ) + + doTest( + "interface Foo : InterfaceWithToJSON { };" "interface Test { Foo toJSON(); };", + False, + "inherited interface with toJSON should be a JSON type", + ) + + for type in nonJsonTypes: + doTest( + "interface Test { %s toJSON(); };" % type, + True, + "%s should not be a JSON type" % type, + ) + + doTest( + "interface Test { sequence<%s> toJSON(); };" % type, + True, + "sequence<%s> should not be a JSON type" % type, + ) + + doTest( + "dictionary Foo { %s foo; }; " "interface Test { Foo toJSON(); }; " % type, + True, + "Dictionary containing a non-JSON type (%s) should not be a JSON type" + % type, + ) + + doTest( + "dictionary Foo { %s foo; }; dictionary Bar : Foo { }; " + "interface Test { Bar toJSON(); }; " % type, + True, + "dictionary whose ancestors only contain non-JSON types should not be a JSON type", + ) + + doTest( + "interface Test { record<DOMString, %s> toJSON(); };" % type, + True, + "record<DOMString, %s> should not be a JSON type" % type, + ) + + doTest( + "interface Test { record<ByteString, %s> toJSON(); };" % type, + True, + "record<ByteString, %s> should not be a JSON type" % type, + ) + + doTest( + "interface Test { record<USVString, %s> toJSON(); };" % type, + True, + "record<USVString, %s> should not be a JSON type" % type, + ) + + if type != "any": + doTest( + "interface Foo { object toJSON(); }; " + "interface Test { (Foo or %s) toJSON(); };" % type, + True, + "union containing a non-JSON type (%s) should not be a JSON type" + % type, + ) + + doTest( + "interface test { %s? toJSON(); };" % type, + True, + "Nullable type (%s) should not be a JSON type" % type, + ) + + doTest( + "dictionary Foo { long foo; any bar; };" "interface Test { Foo toJSON(); };", + True, + "dictionary containing a non-JSON type should not be a JSON type", + ) + + doTest( + "interface Foo : InterfaceWithoutToJSON { }; " + "interface Test { Foo toJSON(); };", + True, + "interface without toJSON should not be a JSON type", + ) diff --git a/dom/bindings/parser/tests/test_treatNonCallableAsNull.py b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py new file mode 100644 index 0000000000..fb44cf7fdf --- /dev/null +++ b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py @@ -0,0 +1,77 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + [TreatNonCallableAsNull] callback Function = any(any... arguments); + + interface TestTreatNonCallableAsNull1 { + attribute Function? onfoo; + attribute Function onbar; + }; + """ + ) + + results = parser.finish() + + iface = results[1] + attr = iface.members[0] + harness.check(attr.type.treatNonCallableAsNull(), True, "Got the expected value") + attr = iface.members[1] + harness.check(attr.type.treatNonCallableAsNull(), False, "Got the expected value") + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + callback Function = any(any... arguments); + + interface TestTreatNonCallableAsNull2 { + [TreatNonCallableAsNull] attribute Function onfoo; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + callback Function = any(any... arguments); + + [TreatNonCallableAsNull] + interface TestTreatNonCallableAsNull3 { + attribute Function onfoo; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + + threw = False + try: + parser.parse( + """ + [TreatNonCallableAsNull, LegacyTreatNonObjectAsNull] + callback Function = any(any... arguments); + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_typedef.py b/dom/bindings/parser/tests/test_typedef.py new file mode 100644 index 0000000000..8179f90d5c --- /dev/null +++ b/dom/bindings/parser/tests/test_typedef.py @@ -0,0 +1,94 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + typedef long mylong; + typedef long? mynullablelong; + interface Foo { + const mylong X = 5; + undefined foo(optional mynullablelong arg = 7); + undefined bar(optional mynullablelong arg = null); + undefined baz(mylong arg); + }; + """ + ) + + results = parser.finish() + + harness.check( + results[2].members[1].signatures()[0][1][0].type.name, + "LongOrNull", + "Should expand typedefs", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef long? mynullablelong; + interface Foo { + undefined foo(mynullablelong? Y); + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on nullable inside nullable arg.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + typedef long? mynullablelong; + interface Foo { + const mynullablelong? X = 5; + }; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on nullable inside nullable const.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Foo { + const mynullablelong? X = 5; + }; + typedef long? mynullablelong; + """ + ) + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown on nullable inside nullable const typedef " + "after interface.", + ) + + parser = parser.reset() + parser.parse( + """ + interface Foo { + const mylong X = 5; + }; + typedef long mylong; + """ + ) + + results = parser.finish() + + harness.check( + results[0].members[0].type.name, + "Long", + "Should expand typedefs that come before interface", + ) diff --git a/dom/bindings/parser/tests/test_typedef_identifier_conflict.py b/dom/bindings/parser/tests/test_typedef_identifier_conflict.py new file mode 100644 index 0000000000..90e45ddb7d --- /dev/null +++ b/dom/bindings/parser/tests/test_typedef_identifier_conflict.py @@ -0,0 +1,19 @@ +def WebIDLTest(parser, harness): + exception = None + try: + parser.parse( + """ + typedef long foo; + typedef long foo; + """ + ) + + parser.finish() + except Exception as e: + exception = e + + harness.ok(exception, "Should have thrown.") + harness.ok( + "Multiple unresolvable definitions of identifier 'foo'" in str(exception), + "Should have a sane exception message", + ) diff --git a/dom/bindings/parser/tests/test_undefined.py b/dom/bindings/parser/tests/test_undefined.py new file mode 100644 index 0000000000..3b6f18292f --- /dev/null +++ b/dom/bindings/parser/tests/test_undefined.py @@ -0,0 +1,243 @@ +def WebIDLTest(parser, harness): + try: + parser.parse( + """ + dictionary Dict { + undefined undefinedMember; + double bar; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "undefined must not be used as the type of a dictionary member") + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + dictionary Dict { + (undefined or double) undefinedMemberOfUnionInDict; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of a dictionary member, " + "whether directly or in a union", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + double bar(undefined foo); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a regular operation)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + getter double(undefined name); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a getter)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + setter undefined(DOMString name, undefined value); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a setter)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + deleter undefined (undefined name); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a deleter)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + constructor (undefined foo); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a constructor)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + callback Callback = undefined (undefined foo); + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a callback)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + async iterable(undefined name); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of an async iterable " + "iterator)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + static double bar(undefined foo); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined must not be used as the type of an argument in any " + "circumstance (so not as the argument of a static operation)", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + const undefined FOO = undefined; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined is not a valid type for a constant", + ) + + parser = parser.reset() + threw = False + + try: + parser.parse( + """ + interface Foo { + const any FOO = undefined; + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "undefined is not a valid value for a constant", + ) diff --git a/dom/bindings/parser/tests/test_unenumerable_own_properties.py b/dom/bindings/parser/tests/test_unenumerable_own_properties.py new file mode 100644 index 0000000000..37e0167696 --- /dev/null +++ b/dom/bindings/parser/tests/test_unenumerable_own_properties.py @@ -0,0 +1,71 @@ +def WebIDLTest(parser, harness): + + parser.parse( + """ + interface Foo {}; + [LegacyUnenumerableNamedProperties] + interface Bar : Foo { + getter long(DOMString name); + }; + interface Baz : Bar { + getter long(DOMString name); + }; + """ + ) + results = parser.finish() + harness.check(len(results), 3, "Should have three interfaces") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyUnenumerableNamedProperties] + interface NoNamedGetter { + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyUnenumerableNamedProperties=Foo] + interface ShouldNotHaveArg { + getter long(DOMString name); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + [LegacyUnenumerableNamedProperties] + interface Foo { + getter long(DOMString name); + }; + interface Bar : Foo {}; + [LegacyUnenumerableNamedProperties] + interface Baz : Bar { + getter long(DOMString name); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_unforgeable.py b/dom/bindings/parser/tests/test_unforgeable.py new file mode 100644 index 0000000000..18946bcc50 --- /dev/null +++ b/dom/bindings/parser/tests/test_unforgeable.py @@ -0,0 +1,311 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + interface Child : Parent { + }; + interface Parent { + [LegacyUnforgeable] readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + harness.check( + len(results), + 2, + "Should be able to inherit from an interface with " + "[LegacyUnforgeable] properties.", + ) + + parser = parser.reset() + parser.parse( + """ + interface Child : Parent { + const short foo = 10; + }; + interface Parent { + [LegacyUnforgeable] readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + harness.check( + len(results), + 2, + "Should be able to inherit from an interface with " + "[LegacyUnforgeable] properties even if we have a constant with " + "the same name.", + ) + + parser = parser.reset() + parser.parse( + """ + interface Child : Parent { + static attribute short foo; + }; + interface Parent { + [LegacyUnforgeable] readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + harness.check( + len(results), + 2, + "Should be able to inherit from an interface with " + "[LegacyUnforgeable] properties even if we have a static attribute " + "with the same name.", + ) + + parser = parser.reset() + parser.parse( + """ + interface Child : Parent { + static undefined foo(); + }; + interface Parent { + [LegacyUnforgeable] readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + harness.check( + len(results), + 2, + "Should be able to inherit from an interface with " + "[LegacyUnforgeable] properties even if we have a static operation " + "with the same name.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + undefined foo(); + }; + interface Parent { + [LegacyUnforgeable] readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should have thrown when shadowing unforgeable attribute on " + "parent with operation.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + undefined foo(); + }; + interface Parent { + [LegacyUnforgeable] undefined foo(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should have thrown when shadowing unforgeable operation on " + "parent with operation.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + attribute short foo; + }; + interface Parent { + [LegacyUnforgeable] readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should have thrown when shadowing unforgeable attribute on " + "parent with attribute.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + attribute short foo; + }; + interface Parent { + [LegacyUnforgeable] undefined foo(); + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + harness.ok( + threw, + "Should have thrown when shadowing unforgeable operation on " + "parent with attribute.", + ) + + parser = parser.reset() + parser.parse( + """ + interface Child : Parent { + }; + interface Parent {}; + interface mixin Mixin { + [LegacyUnforgeable] readonly attribute long foo; + }; + Parent includes Mixin; + """ + ) + + results = parser.finish() + harness.check( + len(results), + 4, + "Should be able to inherit from an interface with a " + "mixin with [LegacyUnforgeable] properties.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + undefined foo(); + }; + interface Parent {}; + interface mixin Mixin { + [LegacyUnforgeable] readonly attribute long foo; + }; + Parent includes Mixin; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown when shadowing unforgeable attribute " + "of parent's consequential interface.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + }; + interface Parent : GrandParent {}; + interface GrandParent {}; + interface mixin Mixin { + [LegacyUnforgeable] readonly attribute long foo; + }; + GrandParent includes Mixin; + interface mixin ChildMixin { + undefined foo(); + }; + Child includes ChildMixin; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown when our consequential interface shadows unforgeable attribute " + "of ancestor's consequential interface.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Child : Parent { + }; + interface Parent : GrandParent {}; + interface GrandParent {}; + interface mixin Mixin { + [LegacyUnforgeable] undefined foo(); + }; + GrandParent includes Mixin; + interface mixin ChildMixin { + undefined foo(); + }; + Child includes ChildMixin; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown when our consequential interface shadows unforgeable operation " + "of ancestor's consequential interface.", + ) + + parser = parser.reset() + parser.parse( + """ + interface iface { + [LegacyUnforgeable] attribute long foo; + }; + """ + ) + + results = parser.finish() + harness.check( + len(results), 1, "Should allow writable [LegacyUnforgeable] attribute." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface iface { + [LegacyUnforgeable] static readonly attribute long foo; + }; + """ + ) + + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for static [LegacyUnforgeable] attribute.") diff --git a/dom/bindings/parser/tests/test_union.py b/dom/bindings/parser/tests/test_union.py new file mode 100644 index 0000000000..675e2d0264 --- /dev/null +++ b/dom/bindings/parser/tests/test_union.py @@ -0,0 +1,196 @@ +import string + +# We'd like to use itertools.chain but it's 2.6 or higher. + + +def chain(*iterables): + # chain('ABC', 'DEF') --> A B C D E F + for it in iterables: + for element in it: + yield element + + +# We'd like to use itertools.combinations but it's 2.6 or higher. +def combinations(iterable, r): + # combinations('ABCD', 2) --> AB AC AD BC BD CD + # combinations(range(4), 3) --> 012 013 023 123 + pool = tuple(iterable) + n = len(pool) + if r > n: + return + indices = list(range(r)) + yield tuple(pool[i] for i in indices) + while True: + for i in reversed(range(r)): + if indices[i] != i + n - r: + break + else: + return + indices[i] += 1 + for j in range(i + 1, r): + indices[j] = indices[j - 1] + 1 + yield tuple(pool[i] for i in indices) + + +# We'd like to use itertools.combinations_with_replacement but it's 2.7 or +# higher. +def combinations_with_replacement(iterable, r): + # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC + pool = tuple(iterable) + n = len(pool) + if not n and r: + return + indices = [0] * r + yield tuple(pool[i] for i in indices) + while True: + for i in reversed(range(r)): + if indices[i] != n - 1: + break + else: + return + indices[i:] = [indices[i] + 1] * (r - i) + yield tuple(pool[i] for i in indices) + + +def WebIDLTest(parser, harness): + types = [ + "float", + "double", + "short", + "unsigned short", + "long", + "unsigned long", + "long long", + "unsigned long long", + "boolean", + "byte", + "octet", + "DOMString", + "ByteString", + "USVString", + # "sequence<float>", + "object", + "ArrayBuffer", + # "Date", + "TestInterface1", + "TestInterface2", + ] + + testPre = """ + interface TestInterface1 { + }; + interface TestInterface2 { + }; + """ + + interface = ( + testPre + + """ + interface PrepareForTest { + """ + ) + for (i, type) in enumerate(types): + interface += string.Template( + """ + readonly attribute ${type} attr${i}; + """ + ).substitute(i=i, type=type) + interface += """ + }; + """ + + parser.parse(interface) + results = parser.finish() + + iface = results[2] + + parser = parser.reset() + + def typesAreDistinguishable(t): + return all(u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2)) + + def typesAreNotDistinguishable(t): + return any(not u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2)) + + def unionTypeName(t): + if len(t) > 2: + t[0:2] = [unionTypeName(t[0:2])] + return "(" + " or ".join(t) + ")" + + # typeCombinations is an iterable of tuples containing the name of the type + # as a string and the parsed IDL type. + def unionTypes(typeCombinations, predicate): + for c in typeCombinations: + if predicate(t[1] for t in c): + yield unionTypeName([t[0] for t in c]) + + # We limit invalid union types with a union member type to the subset of 3 + # types with one invalid combination. + # typeCombinations is an iterable of tuples containing the name of the type + # as a string and the parsed IDL type. + def invalidUnionWithUnion(typeCombinations): + for c in typeCombinations: + if ( + typesAreNotDistinguishable((c[0][1], c[1][1])) + and typesAreDistinguishable((c[1][1], c[2][1])) + and typesAreDistinguishable((c[0][1], c[2][1])) + ): + yield unionTypeName([t[0] for t in c]) + + # Create a list of tuples containing the name of the type as a string and + # the parsed IDL type. + types = zip(types, (a.type for a in iface.members)) + + validUnionTypes = chain( + unionTypes(combinations(types, 2), typesAreDistinguishable), + unionTypes(combinations(types, 3), typesAreDistinguishable), + ) + invalidUnionTypes = chain( + unionTypes(combinations_with_replacement(types, 2), typesAreNotDistinguishable), + invalidUnionWithUnion(combinations(types, 3)), + ) + interface = ( + testPre + + """ + interface TestUnion { + """ + ) + for (i, type) in enumerate(validUnionTypes): + interface += string.Template( + """ + undefined method${i}(${type} arg); + ${type} returnMethod${i}(); + attribute ${type} attr${i}; + undefined optionalMethod${i}(${type}? arg); + """ + ).substitute(i=i, type=type) + interface += """ + }; + """ + parser.parse(interface) + results = parser.finish() + + parser = parser.reset() + + for invalid in invalidUnionTypes: + interface = ( + testPre + + string.Template( + """ + interface TestUnion { + undefined method(${type} arg); + }; + """ + ).substitute(type=invalid) + ) + + threw = False + try: + parser.parse(interface) + results = parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() diff --git a/dom/bindings/parser/tests/test_union_any.py b/dom/bindings/parser/tests/test_union_any.py new file mode 100644 index 0000000000..83ee7493b7 --- /dev/null +++ b/dom/bindings/parser/tests/test_union_any.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface AnyNotInUnion { + undefined foo((any or DOMString) arg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_union_nullable.py b/dom/bindings/parser/tests/test_union_nullable.py new file mode 100644 index 0000000000..d3255a7e05 --- /dev/null +++ b/dom/bindings/parser/tests/test_union_nullable.py @@ -0,0 +1,60 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface OneNullableInUnion { + undefined foo((object? or DOMString?) arg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Two nullable member types of a union should have thrown.") + + parser.reset() + threw = False + + try: + parser.parse( + """ + interface NullableInNullableUnion { + undefined foo((object? or DOMString)? arg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "A nullable union type with a nullable member type should have " "thrown.", + ) + + parser.reset() + threw = False + + try: + parser.parse( + """ + interface NullableInUnionNullableUnionHelper { + }; + interface NullableInUnionNullableUnion { + undefined foo(((object? or DOMString) or NullableInUnionNullableUnionHelper)? arg); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "A nullable union type with a nullable member type should have " "thrown.", + ) diff --git a/dom/bindings/parser/tests/test_usvstring.py b/dom/bindings/parser/tests/test_usvstring.py new file mode 100644 index 0000000000..effede391c --- /dev/null +++ b/dom/bindings/parser/tests/test_usvstring.py @@ -0,0 +1,40 @@ +# -*- coding: UTF-8 -*- + +import WebIDL + + +def WebIDLTest(parser, harness): + parser.parse( + """ + interface TestUSVString { + attribute USVString svs; + }; + """ + ) + + results = parser.finish() + + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") + iface = results[0] + harness.check( + iface.identifier.QName(), "::TestUSVString", "Interface has the right QName" + ) + harness.check( + iface.identifier.name, "TestUSVString", "Interface has the right name" + ) + harness.check(iface.parent, None, "Interface has no parent") + + members = iface.members + harness.check(len(members), 1, "Should be one member") + + attr = members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.check( + attr.identifier.QName(), "::TestUSVString::svs", "Attr has correct QName" + ) + harness.check(attr.identifier.name, "svs", "Attr has correct name") + harness.check(str(attr.type), "USVString", "Attr type is the correct name") + harness.ok(attr.type.isUSVString(), "Should be USVString type") + harness.ok(attr.type.isString(), "Should be String collective type") + harness.ok(not attr.type.isDOMString(), "Should be not be DOMString type") diff --git a/dom/bindings/parser/tests/test_variadic_callback.py b/dom/bindings/parser/tests/test_variadic_callback.py new file mode 100644 index 0000000000..fab1c7ef10 --- /dev/null +++ b/dom/bindings/parser/tests/test_variadic_callback.py @@ -0,0 +1,10 @@ +def WebIDLTest(parser, harness): + parser.parse( + """ + callback TestVariadicCallback = any(any... arguments); + """ + ) + + parser.finish() + + harness.ok(True, "TestVariadicCallback callback parsed without error.") diff --git a/dom/bindings/parser/tests/test_variadic_constraints.py b/dom/bindings/parser/tests/test_variadic_constraints.py new file mode 100644 index 0000000000..8ffbfe0374 --- /dev/null +++ b/dom/bindings/parser/tests/test_variadic_constraints.py @@ -0,0 +1,74 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface VariadicConstraints1 { + undefined foo(byte... arg1, byte arg2); + }; + """ + ) + parser.finish() + + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown on variadic argument followed by required " "argument.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface VariadicConstraints2 { + undefined foo(byte... arg1, optional byte arg2); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown on variadic argument followed by optional " "argument.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface VariadicConstraints3 { + undefined foo(optional byte... arg1); + }; + """ + ) + parser.finish() + + except Exception: + threw = True + + harness.ok( + threw, + "Should have thrown on variadic argument explicitly flagged as " "optional.", + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface VariadicConstraints4 { + undefined foo(byte... arg1 = 0); + }; + """ + ) + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown on variadic argument with default value.") |