Bug 1414372: Introduce interface mixins r=bzbarsky

Add IDLInterfaceMixin with a new superclass shared with existing IDLInterfaceOrNamespace.

Differential Revision: https://phabricator.services.mozilla.com/D38802

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Kagami Sascha Rosylight 2019-08-15 16:53:49 +00:00
parent 65bd8bce7d
commit b14e2b2516
8 changed files with 786 additions and 176 deletions

View File

@ -82,7 +82,7 @@ class Configuration(DescriptorProvider):
# don't have any of those. See similar block above for "implements"
# statements!
if not iface.isExternal():
for partialIface in iface.getPartialInterfaces():
for partialIface in iface.getPartials():
if (partialIface.filename() != iface.filename() and
# Unfortunately, NavigatorProperty does exactly the
# thing we're trying to prevent here. I'm not sure how

View File

@ -314,7 +314,7 @@ class IDLScope(IDLObject):
newObject.location)
raise WebIDLError(
"Multiple unresolvable definitions of identifier '%s' in scope '%s%s"
"Multiple unresolvable definitions of identifier '%s' in scope '%s'%s"
% (identifier.name, str(self), conflictdesc), [])
def _lookupIdentifier(self, identifier):
@ -605,7 +605,7 @@ class IDLPartialInterfaceOrNamespace(IDLObject):
self._haveSecureContextExtendedAttribute = False
self._nonPartialInterfaceOrNamespace = nonPartialInterfaceOrNamespace
self._finished = False
nonPartialInterfaceOrNamespace.addPartialInterface(self)
nonPartialInterfaceOrNamespace.addPartial(self)
def addExtendedAttributes(self, attrs):
for attr in attrs:
@ -676,30 +676,212 @@ def globalNameSetToExposureSet(globalScope, nameSet, exposureSet):
for name in nameSet:
exposureSet.update(globalScope.globalNameMapping[name])
class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
def __init__(self, location, parentScope, name, parent, members,
isKnownNonPartial, toStringTag):
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 resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
assert isinstance(scope, IDLScope)
assert isinstance(originalObject, IDLInterfaceMember)
assert isinstance(newObject, IDLInterfaceMember)
retval = IDLScope.resolveIdentifierConflict(self, scope, identifier,
originalObject, newObject)
# 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"
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)
# 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])
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 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 to
# prevent exposing to PrimaryGlobal by default.
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, toStringTag):
assert isKnownNonPartial or not parent
assert isKnownNonPartial or len(members) == 0
self.parent = None
self._callback = False
self._finished = False
self.members = []
self.maplikeOrSetlikeOrIterable = None
self._partialInterfaces = []
self._extendedAttrDict = {}
# namedConstructors needs deterministic ordering because bindings code
# outputs the constructs in the order that namedConstructors enumerates
# them.
self.namedConstructors = list()
self.legacyWindowAliases = []
self.implementedInterfaces = set()
self.includedMixins = set()
self._consequential = False
self._isKnownNonPartial = False
# self.interfacesBasedOnSelf is the set of interfaces that inherit from
# self or have self as a consequential interface, including self itself.
# Used for distinguishability checking.
@ -720,8 +902,7 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
self.toStringTag = toStringTag
IDLObjectWithScope.__init__(self, location, parentScope, name)
IDLExposureMixins.__init__(self, location)
IDLInterfaceOrInterfaceMixinOrNamespace.__init__(self, location, parentScope, name)
if isKnownNonPartial:
self.setNonPartial(location, parent, members)
@ -741,31 +922,13 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
def isIteratorInterface(self):
return self.iterableInterface is not None
def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
assert isinstance(scope, IDLScope)
assert isinstance(originalObject, IDLInterfaceMember)
assert isinstance(newObject, IDLInterfaceMember)
retval = IDLScope.resolveIdentifierConflict(self, scope, identifier,
originalObject, newObject)
# Might be a ctor, which isn't in self.members
if newObject in self.members:
self.members.remove(newObject)
return retval
def finish(self, scope):
if self._finished:
return
self._finished = True
if not self._isKnownNonPartial:
raise WebIDLError("Interface %s does not have a non-partial "
"declaration" % self.identifier.name,
[self.location])
IDLExposureMixins.finish(self, scope)
IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope)
if len(self.legacyWindowAliases) > 0:
if not self.hasInterfaceObject():
@ -777,12 +940,6 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
"but not exposed in Window" % self.identifier.name,
[self.location])
# Now go ahead and merge in our partial interfaces.
for partial in self._partialInterfaces:
partial.finish(scope)
self.addExtendedAttributes(partial.propagatedExtendedAttrs)
self.members.extend(partial.members)
# Generate maplike/setlike interface members. Since generated members
# need to be treated like regular interface members, do this before
# things like exposure setting.
@ -804,19 +961,6 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
# our required methods in Codegen. Generate members now.
self.maplikeOrSetlikeOrIterable.expand(self.members, self.isJSImplemented())
# Now that we've merged in our partial interfaces, 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 that defined them"
# and "partial interfaces default to interface they're a partial for"
# rules from the spec.
for m in self.members:
# If m, or the partial interface 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)
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):
@ -912,6 +1056,8 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
for iface in self.implementedInterfaces:
iface.finish(scope)
for mixin in self.includedMixins:
mixin.finish(scope)
cycleInGraph = self.findInterfaceLoopPoint(self)
if cycleInGraph:
@ -926,24 +1072,7 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
# And that we're not consequential.
assert not self.isConsequential()
# Now resolve() and finish() our members before importing the
# ones from our implemented interfaces.
# 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 member has larger exposure set "
"than the interface itself",
[member.location, self.location])
self.finishMembers(scope)
ctor = self.ctor()
if ctor is not None:
@ -996,6 +1125,10 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
self.members.extend(additionalMembers)
iface.interfacesImplementingSelf.add(self)
for mixin in sorted(self.includedMixins,
key=lambda x: x.identifier.name):
self.members.extend(mixin.members)
for ancestor in self.getInheritedInterfaces():
ancestor.interfacesBasedOnSelf.add(self)
if (ancestor.maplikeOrSetlikeOrIterable is not None and
@ -1430,6 +1563,10 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
assert(isinstance(implementedInterface, IDLInterface))
self.implementedInterfaces.add(implementedInterface)
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
@ -1478,34 +1615,11 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
if loopPoint:
return loopPoint
return None
def getExtendedAttribute(self, name):
return self._extendedAttrDict.get(name, None)
def setNonPartial(self, location, parent, members):
assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
if self._isKnownNonPartial:
raise WebIDLError("Two non-partial definitions for the "
"same %s" %
("interface" if self.isInterface()
else "namespace"),
[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
IDLInterfaceOrInterfaceMixinOrNamespace.setNonPartial(self, location, members)
assert not self.parent
self.parent = parent
# Put the new members at the beginning
self.members = members + self.members
def addPartialInterface(self, partial):
assert self.identifier.name == partial.identifier.name
self._partialInterfaces.append(partial)
def getPartialInterfaces(self):
# Don't let people mutate our guts.
return list(self._partialInterfaces)
def getJSImplementation(self):
classId = self.getExtendedAttribute("JSImplementation")
@ -1568,6 +1682,7 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
def _getDependentObjects(self):
deps = set(self.members)
deps.update(self.implementedInterfaces)
deps.update(self.includedMixins)
if self.parent:
deps.add(self.parent)
return deps
@ -5418,6 +5533,49 @@ class IDLImplementsStatement(IDLObject):
"allowed on implements statements",
[attrs[0].location, self.location])
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])
if interface.isCallback():
raise WebIDLError("Left-hand side of 'includes' is a callback "
"interface",
[self.interface.location])
if not isinstance(mixin, IDLInterfaceMixin):
raise WebIDLError("Right-hand side of 'includes' is not an "
"interface mixin",
[self.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):
"""
@ -5514,12 +5672,14 @@ class Tokenizer(object):
"module": "MODULE",
"interface": "INTERFACE",
"partial": "PARTIAL",
"mixin": "MIXIN",
"dictionary": "DICTIONARY",
"exception": "EXCEPTION",
"enum": "ENUM",
"callback": "CALLBACK",
"typedef": "TYPEDEF",
"implements": "IMPLEMENTS",
"includes": "INCLUDES",
"const": "CONST",
"null": "NULL",
"true": "TRUE",
@ -5674,7 +5834,7 @@ class Parser(Tokenizer):
def p_Definition(self, p):
"""
Definition : CallbackOrInterface
Definition : CallbackOrInterfaceOrMixin
| Namespace
| Partial
| Dictionary
@ -5682,13 +5842,14 @@ class Parser(Tokenizer):
| Enum
| Typedef
| ImplementsStatement
| IncludesStatement
"""
p[0] = p[1]
assert p[1] # We might not have implemented something ...
def p_CallbackOrInterfaceCallback(self, p):
def p_CallbackOrInterfaceOrMixinCallback(self, p):
"""
CallbackOrInterface : CALLBACK CallbackRestOrInterface
CallbackOrInterfaceOrMixin : CALLBACK CallbackRestOrInterface
"""
if p[2].isInterface():
assert isinstance(p[2], IDLInterface)
@ -5696,17 +5857,17 @@ class Parser(Tokenizer):
p[0] = p[2]
def p_CallbackOrInterfaceInterface(self, p):
def p_CallbackOrInterfaceOrMixinInterfaceOrMixin(self, p):
"""
CallbackOrInterface : Interface
CallbackOrInterfaceOrMixin : INTERFACE InterfaceOrMixin
"""
p[0] = p[1]
p[0] = p[2]
def p_CallbackRestOrInterface(self, p):
"""
CallbackRestOrInterface : CallbackRest
| CallbackConstructorRest
| Interface
| CallbackInterface
"""
assert p[1]
p[0] = p[1]
@ -5747,14 +5908,27 @@ class Parser(Tokenizer):
# True for isKnownNonPartial
return constructor(*(constructorArgs + [True]))
def p_Interface(self, p):
def p_InterfaceOrMixin(self, p):
"""
Interface : INTERFACE IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON
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(self.getLocation(p, 2), p[2])
members = p[5]
parent = p[3]
identifier = IDLUnresolvedIdentifier(location, p[1])
members = p[4]
parent = p[2]
p[0] = self.handleNonPartialObject(
location, identifier, IDLInterface,
@ -5763,10 +5937,10 @@ class Parser(Tokenizer):
def p_InterfaceForwardDecl(self, p):
"""
Interface : INTERFACE IDENTIFIER SEMICOLON
InterfaceRest : IDENTIFIER SEMICOLON
"""
location = self.getLocation(p, 1)
identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
identifier = IDLUnresolvedIdentifier(location, p[1])
try:
if self.globalScope()._lookupIdentifier(identifier):
@ -5784,6 +5958,19 @@ class Parser(Tokenizer):
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
@ -5803,10 +5990,15 @@ class Parser(Tokenizer):
"""
p[0] = p[2]
def p_PartialDefinitionInterface(self, p):
"""
PartialDefinition : INTERFACE PartialInterfaceOrPartialMixin
"""
p[0] = p[2]
def p_PartialDefinition(self, p):
"""
PartialDefinition : PartialInterface
| PartialNamespace
PartialDefinition : PartialNamespace
| PartialDictionary
"""
p[0] = p[1]
@ -5849,32 +6041,53 @@ class Parser(Tokenizer):
if not nonPartialObject:
nonPartialObject = nonPartialConstructor(
# No members, False for isKnownNonPartial
*(nonPartialConstructorArgs + [[], False]))
*(nonPartialConstructorArgs), members=[], isKnownNonPartial=False)
partialObject = None
if isinstance(nonPartialObject, IDLDictionary):
partialObject = IDLPartialDictionary(
*(partialConstructorArgs + [nonPartialObject]))
elif isinstance(nonPartialObject, (IDLInterface, IDLNamespace)):
elif isinstance(nonPartialObject, (IDLInterface, IDLInterfaceMixin, IDLNamespace)):
partialObject = IDLPartialInterfaceOrNamespace(
*(partialConstructorArgs + [nonPartialObject]))
else:
raise WebIDLError("Unknown partial object type %s" %
type(partialObject))
type(partialObject),
[location])
return partialObject
def p_PartialInterface(self, p):
def p_PartialInterfaceOrPartialMixin(self, p):
"""
PartialInterface : INTERFACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON
PartialInterfaceOrPartialMixin : PartialInterfaceRest
| PartialMixinRest
"""
p[0] = p[1]
def p_PartialInterfaceRest(self, p):
"""
PartialInterfaceRest : IDENTIFIER LBRACE InterfaceMembers 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, IDLInterface,
[location, self.globalScope(), identifier, None],
location, identifier, IDLInterfaceMixin,
[location, self.globalScope(), identifier],
[location, identifier, members])
def p_PartialNamespace(self, p):
@ -5919,7 +6132,7 @@ class Parser(Tokenizer):
"""
InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers
"""
p[0] = [p[2]] if p[2] else []
p[0] = [p[2]]
assert not p[1] or p[2]
p[2].addExtendedAttributes(p[1])
@ -5939,6 +6152,32 @@ class Parser(Tokenizer):
"""
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
@ -6115,6 +6354,15 @@ class Parser(Tokenizer):
p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor,
implementee)
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
@ -7244,10 +7492,17 @@ class Parser(Tokenizer):
# XXX khuey hates this bit and wants to nuke it from orbit.
implementsStatements = [p for p in self._productions if
isinstance(p, IDLImplementsStatement)]
# Make sure we finish IDLIncludesStatements before we finish the
# IDLInterfaces.
includesStatements = [p for p in self._productions if
isinstance(p, IDLIncludesStatement)]
otherStatements = [p for p in self._productions if
not isinstance(p, IDLImplementsStatement)]
not isinstance(p, (IDLImplementsStatement,
IDLIncludesStatement))]
for production in implementsStatements:
production.finish(self.globalScope())
for production in includesStatements:
production.finish(self.globalScope())
for production in otherStatements:
production.finish(self.globalScope())

View File

@ -0,0 +1,373 @@
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;
void foo();
};
partial interface mixin A {
readonly attribute boolean y;
void 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;
void foo(long arg);
};
interface mixin A {
readonly attribute boolean x;
void 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:
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:
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:
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:
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:
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:
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:
threw = True
harness.ok(threw,
"Should not allow getters on interface mixins")
parser = parser.reset()
threw = False
try:
parser.parse("""
interface mixin A {
setter void (DOMString propertyName, double propertyValue);
};
""")
results = parser.finish()
except:
threw = True
harness.ok(threw,
"Should not allow setters on interface mixins")
parser = parser.reset()
threw = False
try:
parser.parse("""
interface mixin A {
deleter void (DOMString propertyName);
};
""")
results = parser.finish()
except:
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:
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:
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:
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:
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:
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:
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:
threw = True
harness.ok(threw,
"Should fail if an interface mixin includes maplike")
parser = parser.reset()
parser.parse("""
[Global] interface Window {};
[Global] 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] interface Window {};
[Global] 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")

View File

@ -122,34 +122,32 @@ interface CanvasRenderingContext2D {
void demote();
};
CanvasRenderingContext2D implements CanvasState;
CanvasRenderingContext2D implements CanvasTransform;
CanvasRenderingContext2D implements CanvasCompositing;
CanvasRenderingContext2D implements CanvasImageSmoothing;
CanvasRenderingContext2D implements CanvasFillStrokeStyles;
CanvasRenderingContext2D implements CanvasShadowStyles;
CanvasRenderingContext2D implements CanvasFilters;
CanvasRenderingContext2D implements CanvasRect;
CanvasRenderingContext2D implements CanvasDrawPath;
CanvasRenderingContext2D implements CanvasUserInterface;
CanvasRenderingContext2D implements CanvasText;
CanvasRenderingContext2D implements CanvasDrawImage;
CanvasRenderingContext2D implements CanvasImageData;
CanvasRenderingContext2D implements CanvasPathDrawingStyles;
CanvasRenderingContext2D implements CanvasTextDrawingStyles;
CanvasRenderingContext2D implements CanvasPathMethods;
CanvasRenderingContext2D implements CanvasHitRegions;
CanvasRenderingContext2D includes CanvasState;
CanvasRenderingContext2D includes CanvasTransform;
CanvasRenderingContext2D includes CanvasCompositing;
CanvasRenderingContext2D includes CanvasImageSmoothing;
CanvasRenderingContext2D includes CanvasFillStrokeStyles;
CanvasRenderingContext2D includes CanvasShadowStyles;
CanvasRenderingContext2D includes CanvasFilters;
CanvasRenderingContext2D includes CanvasRect;
CanvasRenderingContext2D includes CanvasDrawPath;
CanvasRenderingContext2D includes CanvasUserInterface;
CanvasRenderingContext2D includes CanvasText;
CanvasRenderingContext2D includes CanvasDrawImage;
CanvasRenderingContext2D includes CanvasImageData;
CanvasRenderingContext2D includes CanvasPathDrawingStyles;
CanvasRenderingContext2D includes CanvasTextDrawingStyles;
CanvasRenderingContext2D includes CanvasPathMethods;
CanvasRenderingContext2D includes CanvasHitRegions;
[NoInterfaceObject]
interface CanvasState {
interface mixin CanvasState {
// state
void save(); // push state on state stack
void restore(); // pop state stack and restore state
};
[NoInterfaceObject]
interface CanvasTransform {
interface mixin CanvasTransform {
// transformations (default transform is the identity matrix)
[Throws, LenientFloat]
void scale(double x, double y);
@ -169,21 +167,18 @@ interface CanvasTransform {
void resetTransform();
};
[NoInterfaceObject]
interface CanvasCompositing {
interface mixin CanvasCompositing {
attribute unrestricted double globalAlpha; // (default 1.0)
[Throws]
attribute DOMString globalCompositeOperation; // (default source-over)
};
[NoInterfaceObject]
interface CanvasImageSmoothing {
interface mixin CanvasImageSmoothing {
// drawing images
attribute boolean imageSmoothingEnabled;
};
[NoInterfaceObject]
interface CanvasFillStrokeStyles {
interface mixin CanvasFillStrokeStyles {
// colors and styles (see also the CanvasPathDrawingStyles interface)
attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black)
attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black)
@ -195,8 +190,7 @@ interface CanvasFillStrokeStyles {
CanvasPattern? createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition);
};
[NoInterfaceObject]
interface CanvasShadowStyles {
interface mixin CanvasShadowStyles {
[LenientFloat]
attribute double shadowOffsetX; // (default 0)
[LenientFloat]
@ -206,14 +200,12 @@ interface CanvasShadowStyles {
attribute DOMString shadowColor; // (default transparent black)
};
[NoInterfaceObject]
interface CanvasFilters {
interface mixin CanvasFilters {
[Pref="canvas.filters.enabled", SetterThrows]
attribute DOMString filter; // (default empty string = no filter)
};
[NoInterfaceObject]
interface CanvasRect {
interface mixin CanvasRect {
[LenientFloat]
void clearRect(double x, double y, double w, double h);
[LenientFloat]
@ -222,8 +214,7 @@ interface CanvasRect {
void strokeRect(double x, double y, double w, double h);
};
[NoInterfaceObject]
interface CanvasDrawPath {
interface mixin CanvasDrawPath {
// path API (see also CanvasPathMethods)
void beginPath();
void fill(optional CanvasWindingRule winding = "nonzero");
@ -243,15 +234,13 @@ interface CanvasDrawPath {
boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
};
[NoInterfaceObject]
interface CanvasUserInterface {
interface mixin CanvasUserInterface {
[Pref="canvas.focusring.enabled", Throws] void drawFocusIfNeeded(Element element);
// NOT IMPLEMENTED void scrollPathIntoView();
// NOT IMPLEMENTED void scrollPathIntoView(Path path);
};
[NoInterfaceObject]
interface CanvasText {
interface mixin CanvasText {
// text (see also the CanvasPathDrawingStyles interface)
[Throws, LenientFloat]
void fillText(DOMString text, double x, double y, optional double maxWidth);
@ -261,8 +250,7 @@ interface CanvasText {
TextMetrics measureText(DOMString text);
};
[NoInterfaceObject]
interface CanvasDrawImage {
interface mixin CanvasDrawImage {
[Throws, LenientFloat]
void drawImage(CanvasImageSource image, double dx, double dy);
[Throws, LenientFloat]
@ -271,8 +259,7 @@ interface CanvasDrawImage {
void drawImage(CanvasImageSource image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh);
};
[NoInterfaceObject]
interface CanvasImageData {
interface mixin CanvasImageData {
// pixel manipulation
[NewObject, Throws]
ImageData createImageData(double sw, double sh);
@ -286,8 +273,7 @@ interface CanvasImageData {
void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight);
};
[NoInterfaceObject]
interface CanvasPathDrawingStyles {
interface mixin CanvasPathDrawingStyles {
// line caps/joins
[LenientFloat]
attribute double lineWidth; // (default 1)
@ -303,8 +289,7 @@ interface CanvasPathDrawingStyles {
[LenientFloat] attribute double lineDashOffset;
};
[NoInterfaceObject]
interface CanvasTextDrawingStyles {
interface mixin CanvasTextDrawingStyles {
// text
[SetterThrows]
attribute DOMString font; // (default 10px sans-serif)
@ -312,8 +297,7 @@ interface CanvasTextDrawingStyles {
attribute DOMString textBaseline; // "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" (default: "alphabetic")
};
[NoInterfaceObject]
interface CanvasPathMethods {
interface mixin CanvasPathMethods {
// shared path API methods
void closePath();
[LenientFloat]
@ -340,8 +324,7 @@ interface CanvasPathMethods {
void ellipse(double x, double y, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, optional boolean anticlockwise = false);
};
[NoInterfaceObject]
interface CanvasHitRegions {
interface mixin CanvasHitRegions {
// hit regions
[Pref="canvas.hitregions.enabled", Throws] void addHitRegion(optional HitRegionOptions options = {});
[Pref="canvas.hitregions.enabled"] void removeHitRegion(DOMString id);
@ -397,4 +380,4 @@ interface Path2D
{
[Throws] void addPath(Path2D path, optional DOMMatrix2DInit transform = {});
};
Path2D implements CanvasPathMethods;
Path2D includes CanvasPathMethods;

View File

@ -10,8 +10,7 @@
typedef object JSON;
typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit;
[NoInterfaceObject, Exposed=(Window,Worker)]
interface Body {
interface mixin Body {
[Throws]
readonly attribute boolean bodyUsed;
[Throws]

View File

@ -20,7 +20,7 @@ interface IDBKeyRange {
[Constant]
readonly attribute boolean upperOpen;
[Throws]
boolean includes(any key);
boolean _includes(any key);
[NewObject, Throws]

View File

@ -41,7 +41,7 @@ interface Request {
[ChromeOnly]
void overrideContentPolicyType(nsContentPolicyType context);
};
Request implements Body;
Request includes Body;
dictionary RequestInit {
ByteString method;

View File

@ -33,7 +33,7 @@ interface Response {
// For testing only.
[ChromeOnly] readonly attribute boolean hasCacheInfoChannel;
};
Response implements Body;
Response includes Body;
// This should be part of Body but we don't want to expose body to request yet.
// See bug 1387483.