aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/script/dom/bindings/codegen/parser/WebIDL.py
diff options
context:
space:
mode:
authorMs2ger <ms2ger@gmail.com>2014-08-19 11:01:05 +0200
committerMs2ger <ms2ger@gmail.com>2014-08-19 11:01:05 +0200
commit0bb0951265a3e141c56d4ce5338dcdda01a00106 (patch)
tree816e2c472be60206b36e88b5aacdb89125d5f95e /src/components/script/dom/bindings/codegen/parser/WebIDL.py
parenteb6eee513f44ef10aaf0085054096c192153912a (diff)
downloadservo-0bb0951265a3e141c56d4ce5338dcdda01a00106.tar.gz
servo-0bb0951265a3e141c56d4ce5338dcdda01a00106.zip
Update WebIDL.py.
Diffstat (limited to 'src/components/script/dom/bindings/codegen/parser/WebIDL.py')
-rw-r--r--src/components/script/dom/bindings/codegen/parser/WebIDL.py627
1 files changed, 560 insertions, 67 deletions
diff --git a/src/components/script/dom/bindings/codegen/parser/WebIDL.py b/src/components/script/dom/bindings/codegen/parser/WebIDL.py
index 4d39f54159c..32f80e82c56 100644
--- a/src/components/script/dom/bindings/codegen/parser/WebIDL.py
+++ b/src/components/script/dom/bindings/codegen/parser/WebIDL.py
@@ -9,6 +9,7 @@ import re
import os
import traceback
import math
+from collections import defaultdict
# Machinery
@@ -219,6 +220,12 @@ class IDLScope(IDLObject):
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)
+ self.primaryGlobalAttr = None
+ self.primaryGlobalName = None
def __str__(self):
return self.QName()
@@ -446,24 +453,85 @@ class IDLExternalInterface(IDLObjectWithIdentifier):
raise WebIDLError("Servo does not support external interfaces.",
[self.location])
+class IDLPartialInterface(IDLObject):
+ def __init__(self, location, name, members, nonPartialInterface):
+ 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._nonPartialInterface = nonPartialInterface
+ self._finished = False
+ nonPartialInterface.addPartialInterface(self)
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ if identifier in ["Constructor", "NamedConstructor"]:
+ self.propagatedExtendedAttrs.append(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
+ # Need to make sure our non-partial interface gets finished so it can
+ # report cases when we only have partial interfaces.
+ self._nonPartialInterface.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])
+
class IDLInterface(IDLObjectWithScope):
def __init__(self, location, parentScope, name, parent, members,
- isPartial):
+ isKnownNonPartial):
assert isinstance(parentScope, IDLScope)
assert isinstance(name, IDLUnresolvedIdentifier)
- assert not isPartial or not parent
+ assert isKnownNonPartial or not parent
+ assert isKnownNonPartial or len(members) == 0
self.parent = None
self._callback = False
self._finished = False
self.members = []
+ 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.implementedInterfaces = set()
self._consequential = False
- self._isPartial = True
+ 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.
@@ -478,14 +546,16 @@ class IDLInterface(IDLObjectWithScope):
self.totalMembersInSlots = 0
# Tracking of the number of own own members we have in slots
self._ownMembersInSlots = 0
+ # _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()
IDLObjectWithScope.__init__(self, location, parentScope, name)
- if not isPartial:
+ if isKnownNonPartial:
self.setNonPartial(location, parent, members)
- else:
- # Just remember our members for now
- self.members = members
def __str__(self):
return "Interface '%s'" % self.identifier.name
@@ -517,11 +587,42 @@ class IDLInterface(IDLObjectWithScope):
self._finished = True
- if self._isPartial:
+ if not self._isKnownNonPartial:
raise WebIDLError("Interface %s does not have a non-partial "
"declaration" % self.identifier.name,
[self.location])
+ # 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])
+
+ if len(self._exposureGlobalNames) == 0:
+ self._exposureGlobalNames.add(scope.primaryGlobalName)
+
+ globalNameSetToExposureSet(scope, self._exposureGlobalNames,
+ self.exposureSet)
+
+ # 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)
+
+ # 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):
@@ -543,8 +644,10 @@ class IDLInterface(IDLObjectWithScope):
self.totalMembersInSlots = self.parent.totalMembersInSlots
- # Interfaces with [Global] must not have anything inherit from them
- if self.parent.getExtendedAttribute("Global"):
+ # Interfaces with [Global] or [PrimaryGlobal] must not
+ # have anything inherit from them
+ if (self.parent.getExtendedAttribute("Global") or
+ self.parent.getExtendedAttribute("PrimaryGlobal")):
# Note: This is not a self.parent.isOnGlobalProtoChain() check
# because ancestors of a [Global] interface can have other
# descendants.
@@ -552,6 +655,14 @@ class IDLInterface(IDLObjectWithScope):
"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 or inherit from
# anything that has consequential interfaces.
# XXXbz Can non-callbacks inherit from callbacks? Spec issue pending.
@@ -597,6 +708,14 @@ class IDLInterface(IDLObjectWithScope):
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])
+
ctor = self.ctor()
if ctor is not None:
ctor.finish(scope)
@@ -617,6 +736,12 @@ class IDLInterface(IDLObjectWithScope):
key=lambda x: x.identifier.name):
# Flag the interface as being someone's consequential interface
iface.setIsConsequentialInterfaceOf(self)
+ # Verify that we're not exposed somewhere where iface is not exposed
+ if not self.exposureSet.issubset(iface.exposureSet):
+ raise WebIDLError("Interface %s is exposed in globals where its "
+ "consequential interface %s is not exposed." %
+ (self.identifier.name, iface.identifier.name),
+ [self.location, iface.location])
additionalMembers = iface.originalMembers;
for additionalMember in additionalMembers:
for member in self.members:
@@ -633,8 +758,36 @@ class IDLInterface(IDLObjectWithScope):
for ancestorConsequential in ancestor.getConsequentialInterfaces():
ancestorConsequential.interfacesBasedOnSelf.add(self)
+ # Deal with interfaces marked [Unforgeable], 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("Unforgeable"):
+ # 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("Unforgeable interface %s does not have a "
+ "stringifier" % self.identifier.name,
+ [self.location])
+
+ for m in self.members:
+ if ((m.isMethod() and m.isJsonifier()) or
+ m.identifier.name == "toJSON"):
+ raise WebIDLError("Unforgeable interface %s has a "
+ "jsonifier 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("Unforgeable 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() and member.isUnforgeable() and
+ if ((member.isAttr() or member.isMethod()) and
+ member.isUnforgeable() and
not hasattr(member, "originatingInterface")):
member.originatingInterface = self
@@ -657,16 +810,16 @@ class IDLInterface(IDLObjectWithScope):
# worry about anything other than our parent, because it has already
# imported its ancestors unforgeable attributes into its member
# list.
- for unforgeableAttr in (attr for attr in self.parent.members if
- attr.isAttr() and not attr.isStatic() and
- attr.isUnforgeable()):
+ for unforgeableMember in (member for member in self.parent.members if
+ (member.isAttr() or member.isMethod()) and
+ member.isUnforgeable()):
shadows = [ m for m in self.members if
(m.isAttr() or m.isMethod()) and
not m.isStatic() and
- m.identifier.name == unforgeableAttr.identifier.name ]
+ m.identifier.name == unforgeableMember.identifier.name ]
if len(shadows) != 0:
- locs = [unforgeableAttr.location] + [ s.location for s
- in shadows ]
+ locs = [unforgeableMember.location] + [ s.location for s
+ in shadows ]
raise WebIDLError("Interface %s shadows [Unforgeable] "
"members of %s" %
(self.identifier.name,
@@ -675,10 +828,10 @@ class IDLInterface(IDLObjectWithScope):
# 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 of ancestor interfaces, with their corresponding
- # getters, on our interface, but that gets pretty complicated
- # and seems unnecessary.
- self.members.append(unforgeableAttr)
+ # attributes/methods of ancestor interfaces, with their
+ # corresponding getters, on our interface, but that gets pretty
+ # complicated and seems unnecessary.
+ self.members.append(unforgeableMember)
# Ensure that there's at most one of each {named,indexed}
# {getter,setter,creator,deleter}, at most one stringifier,
@@ -749,6 +902,26 @@ class IDLInterface(IDLObjectWithScope):
parent = parent.parent
def validate(self):
+ # We don't support consequential unforgeable interfaces. Need to check
+ # this here, becaue in finish() an interface might not know yet that
+ # it's consequential.
+ if self.getExtendedAttribute("Unforgeable") and self.isConsequential():
+ raise WebIDLError(
+ "%s is an unforgeable consequential interface" %
+ self.identifier.name,
+ [self.location] +
+ list(i.location for i in
+ (self.interfacesBasedOnSelf - { self }) ))
+
+ # We also don't support inheriting from unforgeable interfaces.
+ if self.getExtendedAttribute("Unforgeable") and self.hasChildInterfaces():
+ raise WebIDLError("%s is an unforgeable ancestor interface" %
+ self.identifier.name,
+ [self.location] +
+ list(i.location for i in
+ self.interfacesBasedOnSelf if i.parent == self))
+
+
for member in self.members:
member.validate()
@@ -791,6 +964,12 @@ class IDLInterface(IDLObjectWithScope):
attr = fowardAttr
putForwards = attr.getExtendedAttribute("PutForwards")
+ if (self.getExtendedAttribute("Pref") and
+ self._exposureGlobalNames != set([self.parentScope.primaryGlobalName])):
+ raise WebIDLError("[Pref] used on an member that is not %s-only" %
+ self.parentScope.primaryGlobalName,
+ [self.location])
+
def isInterface(self):
return True
@@ -848,7 +1027,6 @@ class IDLInterface(IDLObjectWithScope):
return not self.isCallback() and self.getUserData('hasConcreteDescendant', False)
def addExtendedAttributes(self, attrs):
- self._extendedAttrDict = {}
for attr in attrs:
identifier = attr.identifier()
@@ -949,17 +1127,42 @@ class IDLInterface(IDLObjectWithScope):
"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.globalNames.update(self.globalNames)
+ for globalName in self.globalNames:
+ self.parentScope.globalNameMapping[globalName].add(self.identifier.name)
+ self._isOnGlobalProtoChain = True
+ elif identifier == "PrimaryGlobal":
if not attr.noArguments():
- raise WebIDLError("[Global] must take no arguments",
+ raise WebIDLError("[PrimaryGlobal] must take no arguments",
[attr.location])
+ if self.parentScope.primaryGlobalAttr is not None:
+ raise WebIDLError(
+ "[PrimaryGlobal] specified twice",
+ [attr.location,
+ self.parentScope.primaryGlobalAttr.location])
+ self.parentScope.primaryGlobalAttr = attr
+ self.parentScope.primaryGlobalName = self.identifier.name
+ self.parentScope.globalNames.add(self.identifier.name)
+ self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name)
self._isOnGlobalProtoChain = True
elif (identifier == "NeedNewResolve" or
identifier == "OverrideBuiltins" or
- identifier == "ChromeOnly"):
+ identifier == "ChromeOnly" or
+ identifier == "Unforgeable" or
+ identifier == "LegacyEventInit"):
# 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
@@ -1036,11 +1239,11 @@ class IDLInterface(IDLObjectWithScope):
def setNonPartial(self, location, parent, members):
assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
- if not self._isPartial:
+ if self._isKnownNonPartial:
raise WebIDLError("Two non-partial definitions for the "
"same interface",
[location, self.location])
- self._isPartial = False
+ 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
@@ -1049,6 +1252,10 @@ class IDLInterface(IDLObjectWithScope):
# 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 getJSImplementation(self):
classId = self.getExtendedAttribute("JSImplementation")
if not classId:
@@ -1276,6 +1483,7 @@ class IDLType(IDLObject):
'any',
'domstring',
'bytestring',
+ 'scalarvaluestring',
'object',
'date',
'void',
@@ -1328,6 +1536,9 @@ class IDLType(IDLObject):
def isDOMString(self):
return False
+ def isScalarValueString(self):
+ return False
+
def isVoid(self):
return self.name == "Void"
@@ -1428,13 +1639,17 @@ class IDLType(IDLObject):
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):
+ def __init__(self, location, name, promiseInnerType=None):
IDLType.__init__(self, location, name)
+ self._promiseInnerType = promiseInnerType
def isComplete(self):
return False
@@ -1455,8 +1670,11 @@ class IDLUnresolvedType(IDLType):
obj = obj.complete(scope)
return obj
+ if self._promiseInnerType and not self._promiseInnerType.isComplete():
+ self._promiseInnerType = self._promiseInnerType.complete(scope)
+
name = self.name.resolve(scope, None)
- return IDLWrapperType(self.location, obj)
+ return IDLWrapperType(self.location, obj, self._promiseInnerType)
def isDistinguishableFrom(self, other):
raise TypeError("Can't tell whether an unresolved type is or is not "
@@ -1501,6 +1719,9 @@ class IDLNullableType(IDLType):
def isDOMString(self):
return self.inner.isDOMString()
+ def isScalarValueString(self):
+ return self.inner.isScalarValueString()
+
def isFloat(self):
return self.inner.isFloat()
@@ -1622,6 +1843,9 @@ class IDLSequenceType(IDLType):
def isDOMString(self):
return False
+ def isScalarValueString(self):
+ return False
+
def isVoid(self):
return False
@@ -1669,7 +1893,7 @@ class IDLSequenceType(IDLType):
# Just forward to the union; it'll deal
return other.isDistinguishableFrom(self)
return (other.isPrimitive() or other.isString() or other.isEnum() or
- other.isDate() or other.isNonCallbackInterface())
+ other.isDate() or other.isNonCallbackInterface() or other.isMozMap())
def _getDependentObjects(self):
return self.inner._getDependentObjects()
@@ -1723,7 +1947,10 @@ class IDLMozMapType(IDLType):
# Just forward to the union; it'll deal
return other.isDistinguishableFrom(self)
return (other.isPrimitive() or other.isString() or other.isEnum() or
- other.isDate() or other.isNonCallbackInterface())
+ other.isDate() or other.isNonCallbackInterface() or other.isSequence())
+
+ def isExposedInAllOf(self, exposureSet):
+ return self.inner.unroll().isExposedInAllOf(exposureSet)
def _getDependentObjects(self):
return self.inner._getDependentObjects()
@@ -1835,6 +2062,14 @@ class IDLUnionType(IDLType):
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 _getDependentObjects(self):
return set(self.memberTypes)
@@ -1876,6 +2111,9 @@ class IDLArrayType(IDLType):
def isDOMString(self):
return False
+ def isScalarValueString(self):
+ return False
+
def isVoid(self):
return False
@@ -1971,6 +2209,9 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier):
def isDOMString(self):
return self.inner.isDOMString()
+ def isScalarValueString(self):
+ return self.inner.isScalarValueString()
+
def isVoid(self):
return self.inner.isVoid()
@@ -2038,11 +2279,13 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier):
return self.inner._getDependentObjects()
class IDLWrapperType(IDLType):
- def __init__(self, location, inner):
+ def __init__(self, location, inner, promiseInnerType=None):
IDLType.__init__(self, location, inner.identifier.name)
self.inner = inner
self._identifier = inner.identifier
self.builtin = False
+ assert not promiseInnerType or inner.identifier.name == "Promise"
+ self._promiseInnerType = promiseInnerType
def __eq__(self, other):
return isinstance(other, IDLWrapperType) and \
@@ -2067,6 +2310,9 @@ class IDLWrapperType(IDLType):
def isDOMString(self):
return False
+ def isScalarValueString(self):
+ return False
+
def isVoid(self):
return False
@@ -2163,6 +2409,20 @@ class IDLWrapperType(IDLType):
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, though ideally we'd only do this when
+ # exposureSet contains the primary global's name.
+ return True
+ if (iface.identifier.name == "Promise" and
+ # Check the internal type
+ not self._promiseInnerType.unroll().isExposedInAllOf(exposureSet)):
+ return False
+ 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
@@ -2203,6 +2463,7 @@ class IDLBuiltinType(IDLType):
'any',
'domstring',
'bytestring',
+ 'scalarvaluestring',
'object',
'date',
'void',
@@ -2237,6 +2498,7 @@ class IDLBuiltinType(IDLType):
Types.any: IDLType.Tags.any,
Types.domstring: IDLType.Tags.domstring,
Types.bytestring: IDLType.Tags.bytestring,
+ Types.scalarvaluestring: IDLType.Tags.scalarvaluestring,
Types.object: IDLType.Tags.object,
Types.date: IDLType.Tags.date,
Types.void: IDLType.Tags.void,
@@ -2269,7 +2531,8 @@ class IDLBuiltinType(IDLType):
def isString(self):
return self._typeTag == IDLBuiltinType.Types.domstring or \
- self._typeTag == IDLBuiltinType.Types.bytestring
+ self._typeTag == IDLBuiltinType.Types.bytestring or \
+ self._typeTag == IDLBuiltinType.Types.scalarvaluestring
def isByteString(self):
return self._typeTag == IDLBuiltinType.Types.bytestring
@@ -2277,6 +2540,9 @@ class IDLBuiltinType(IDLType):
def isDOMString(self):
return self._typeTag == IDLBuiltinType.Types.domstring
+ def isScalarValueString(self):
+ return self._typeTag == IDLBuiltinType.Types.scalarvaluestring
+
def isInteger(self):
return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long
@@ -2429,6 +2695,9 @@ BuiltinTypes = {
IDLBuiltinType.Types.bytestring:
IDLBuiltinType(BuiltinLocation("<builtin type>"), "ByteString",
IDLBuiltinType.Types.bytestring),
+ IDLBuiltinType.Types.scalarvaluestring:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "ScalarValueString",
+ IDLBuiltinType.Types.scalarvaluestring),
IDLBuiltinType.Types.object:
IDLBuiltinType(BuiltinLocation("<builtin type>"), "Object",
IDLBuiltinType.Types.object),
@@ -2563,6 +2832,13 @@ class IDLValue(IDLObject):
raise WebIDLError("Trying to convert unrestricted value %s to non-unrestricted"
% self.value, [location]);
return self
+ elif self.type.isString() and type.isScalarValueString():
+ # Allow ScalarValueStrings to use default value just like
+ # DOMString. No coercion is required in this case as Codegen.py
+ # treats ScalarValueString just like DOMString, but with an
+ # extra normalization step.
+ assert self.type.isDOMString()
+ return self
raise WebIDLError("Cannot coerce type %s to type %s." %
(self.type, type), [location])
@@ -2598,6 +2874,34 @@ class IDLNullValue(IDLObject):
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:
+ 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 IDLUndefinedValue(IDLObject):
def __init__(self, location):
IDLObject.__init__(self, location)
@@ -2633,6 +2937,11 @@ class IDLInterfaceMember(IDLObjectWithIdentifier):
IDLObjectWithIdentifier.__init__(self, location, None, identifier)
self.tag = tag
self._extendedAttrDict = {}
+ # _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()
def isMethod(self):
return self.tag == IDLInterfaceMember.Tags.Method
@@ -2655,6 +2964,22 @@ class IDLInterfaceMember(IDLObjectWithIdentifier):
def getExtendedAttribute(self, name):
return self._extendedAttrDict.get(name, None)
+ def finish(self, scope):
+ for globalName in self._exposureGlobalNames:
+ if globalName not in scope.globalNames:
+ raise WebIDLError("Unknown [Exposed] value %s" % globalName,
+ [self.location])
+ globalNameSetToExposureSet(scope, self._exposureGlobalNames,
+ self.exposureSet)
+ self._scope = scope
+
+ def validate(self):
+ if (self.getExtendedAttribute("Pref") and
+ self.exposureSet != set([self._scope.primaryGlobalName])):
+ raise WebIDLError("[Pref] used on an interface member that is not "
+ "%s-only" % self._scope.primaryGlobalName,
+ [self.location])
+
class IDLConst(IDLInterfaceMember):
def __init__(self, location, identifier, type, value):
IDLInterfaceMember.__init__(self, location, identifier,
@@ -2675,6 +3000,8 @@ class IDLConst(IDLInterfaceMember):
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():
@@ -2693,7 +3020,23 @@ class IDLConst(IDLInterfaceMember):
self.value = coercedValue
def validate(self):
- pass
+ 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 == "AvailableIn" or
+ identifier == "CheckPermissions"):
+ # 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])
@@ -2731,6 +3074,8 @@ class IDLAttribute(IDLInterfaceMember):
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)
@@ -2777,6 +3122,8 @@ class IDLAttribute(IDLInterfaceMember):
"interface type as its type", [self.location])
def validate(self):
+ IDLInterfaceMember.validate(self)
+
if ((self.getExtendedAttribute("Cached") or
self.getExtendedAttribute("StoreInSlot")) and
not self.getExtendedAttribute("Constant") and
@@ -2792,6 +3139,10 @@ class IDLAttribute(IDLInterfaceMember):
"sequence-valued, dictionary-valued, and "
"MozMap-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])
def handleExtendedAttribute(self, attr):
identifier = attr.identifier()
@@ -2824,9 +3175,6 @@ class IDLAttribute(IDLInterfaceMember):
[attr.location, self.location])
self.lenientThis = True
elif identifier == "Unforgeable":
- if not self.readonly:
- raise WebIDLError("[Unforgeable] is only allowed on readonly "
- "attributes", [attr.location, self.location])
if self.isStatic():
raise WebIDLError("[Unforgeable] is only allowed on non-static "
"attributes", [attr.location, self.location])
@@ -2886,7 +3234,7 @@ class IDLAttribute(IDLInterfaceMember):
[attr.location, self.location])
elif (identifier == "CrossOriginReadable" or
identifier == "CrossOriginWritable"):
- if not attr.noArguments():
+ if not attr.noArguments() and identifier == "CrossOriginReadable":
raise WebIDLError("[%s] must take no arguments" % identifier,
[attr.location])
if self.isStatic():
@@ -2897,6 +3245,8 @@ class IDLAttribute(IDLInterfaceMember):
raise WebIDLError("[LenientThis] is not allowed in combination "
"with [%s]" % identifier,
[attr.location, self.location])
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
elif (identifier == "Pref" or
identifier == "SetterThrows" or
identifier == "Pure" or
@@ -3183,6 +3533,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
assert isinstance(jsonifier, bool)
self._jsonifier = jsonifier
self._specialType = specialType
+ self._unforgeable = False
if static and identifier.name == "prototype":
raise WebIDLError("The identifier of a static operation must not be 'prototype'",
@@ -3314,6 +3665,8 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
self._overloads]
def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
overloadWithPromiseReturnType = None
overloadWithoutPromiseReturnType = None
for overload in self._overloads:
@@ -3385,6 +3738,8 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
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:
@@ -3404,6 +3759,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
distinguishingIndex),
[self.location, overload.location])
+ for overload in self._overloads:
+ if not overload.returnType.unroll().isExposedInAllOf(self.exposureSet):
+ raise WebIDLError("Overload returns a type that is not exposed "
+ "everywhere where the method is exposed",
+ [overload.location])
+
def overloadsForArgCount(self, argc):
return [overload for overload in self._overloads if
len(overload.arguments) == argc or
@@ -3458,9 +3819,10 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
"[SetterThrows]",
[attr.location, self.location])
elif identifier == "Unforgeable":
- raise WebIDLError("Methods must not be flagged as "
- "[Unforgeable]",
- [attr.location, self.location])
+ if self.isStatic():
+ raise WebIDLError("[Unforgeable] is only allowed on non-static "
+ "methods", [attr.location, self.location])
+ self._unforgeable = True
elif identifier == "SameObject":
raise WebIDLError("Methods must not be flagged as [SameObject]",
[attr.location, self.location]);
@@ -3481,15 +3843,21 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
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 == "Pure" or
+ identifier == "CrossOriginCallable" or
+ identifier == "WebGLHandlesContextLoss"):
+ # Known no-argument attributes.
+ if not attr.noArguments():
+ raise WebIDLError("[%s] must take no arguments" % identifier,
+ [attr.location])
elif (identifier == "Throws" or
identifier == "NewObject" or
identifier == "ChromeOnly" or
identifier == "Pref" or
identifier == "Func" or
identifier == "AvailableIn" or
- identifier == "Pure" or
- identifier == "CrossOriginCallable" or
- identifier == "WebGLHandlesContextLoss" or
identifier == "CheckPermissions"):
# Known attributes that we don't need to do anything with here
pass
@@ -3501,6 +3869,9 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
def returnsPromise(self):
return self._overloads[0].returnType.isPromise()
+ def isUnforgeable(self):
+ return self._unforgeable
+
def _getDependentObjects(self):
deps = set()
for overload in self._overloads:
@@ -3612,7 +3983,7 @@ class Tokenizer(object):
return t
def t_IDENTIFIER(self, t):
- r'[A-Z_a-z][0-9A-Z_a-z]*'
+ r'[A-Z_a-z][0-9A-Z_a-z-]*'
t.type = self.keywords.get(t.value, 'IDENTIFIER')
return t
@@ -3668,6 +4039,7 @@ class Tokenizer(object):
"Date": "DATE",
"DOMString": "DOMSTRING",
"ByteString": "BYTESTRING",
+ "ScalarValueString": "SCALARVALUESTRING",
"any": "ANY",
"boolean": "BOOLEAN",
"byte": "BYTE",
@@ -3677,6 +4049,7 @@ class Tokenizer(object):
"object": "OBJECT",
"octet": "OCTET",
"optional": "OPTIONAL",
+ "Promise": "PROMISE",
"sequence": "SEQUENCE",
"MozMap": "MOZMAP",
"short": "SHORT",
@@ -3717,6 +4090,45 @@ class Tokenizer(object):
lextab='webidllex',
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 '%s' defined, but not used":
+ # Munge things so we don't have to hardcode filenames and
+ # line numbers in our whitelist.
+ whitelistmsg = "Rule '%s' 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)
@@ -3792,10 +4204,11 @@ class Parser(Tokenizer):
parent = p[3]
try:
- if self.globalScope()._lookupIdentifier(identifier):
- p[0] = self.globalScope()._lookupIdentifier(identifier)
+ existingObj = self.globalScope()._lookupIdentifier(identifier)
+ if existingObj:
+ p[0] = existingObj
if not isinstance(p[0], IDLInterface):
- raise WebIDLError("Partial interface has the same name as "
+ raise WebIDLError("Interface has the same name as "
"non-interface object",
[location, p[0].location])
p[0].setNonPartial(location, parent, members)
@@ -3806,7 +4219,7 @@ class Parser(Tokenizer):
pass
p[0] = IDLInterface(location, self.globalScope(), identifier, parent,
- members, isPartial=False)
+ members, isKnownNonPartial=True)
def p_InterfaceForwardDecl(self, p):
"""
@@ -3839,26 +4252,26 @@ class Parser(Tokenizer):
identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3])
members = p[5]
+ nonPartialInterface = None
try:
- if self.globalScope()._lookupIdentifier(identifier):
- p[0] = self.globalScope()._lookupIdentifier(identifier)
- if not isinstance(p[0], IDLInterface):
+ nonPartialInterface = self.globalScope()._lookupIdentifier(identifier)
+ if nonPartialInterface:
+ if not isinstance(nonPartialInterface, IDLInterface):
raise WebIDLError("Partial interface has the same name as "
"non-interface object",
- [location, p[0].location])
- # Just throw our members into the existing IDLInterface. If we
- # have extended attributes, those will get added to it
- # automatically.
- p[0].members.extend(members)
- return
+ [location, nonPartialInterface.location])
except Exception, ex:
if isinstance(ex, WebIDLError):
raise ex
pass
- p[0] = IDLInterface(location, self.globalScope(), identifier, None,
- members, isPartial=True)
- pass
+ if not nonPartialInterface:
+ nonPartialInterface = IDLInterface(location, self.globalScope(),
+ identifier, None,
+ [], isKnownNonPartial=False)
+ partialInterface = IDLPartialInterface(location, identifier, members,
+ nonPartialInterface)
+ p[0] = partialInterface
def p_Inheritance(self, p):
"""
@@ -3921,7 +4334,7 @@ class Parser(Tokenizer):
def p_DictionaryMember(self, p):
"""
- DictionaryMember : Type IDENTIFIER DefaultValue SEMICOLON
+ DictionaryMember : Type IDENTIFIER Default SEMICOLON
"""
# These quack a lot like optional arguments, so just treat them that way.
t = p[1]
@@ -3933,16 +4346,27 @@ class Parser(Tokenizer):
defaultValue=defaultValue, variadic=False,
dictionaryMember=True)
- def p_DefaultValue(self, p):
+ def p_Default(self, p):
"""
- DefaultValue : EQUALS ConstValue
- |
+ Default : EQUALS DefaultValue
+ |
"""
if len(p) > 1:
p[0] = p[2]
else:
p[0] = None
+ def p_DefaultValue(self, p):
+ """
+ DefaultValue : ConstValue
+ | LBRACKET RBRACKET
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ assert len(p) == 3 # Must be []
+ p[0] = IDLEmptySequenceValue(self.getLocation(p, 1))
+
def p_Exception(self, p):
"""
Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON
@@ -4401,7 +4825,7 @@ class Parser(Tokenizer):
def p_Argument(self, p):
"""
- Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName DefaultValue
+ Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName Default
"""
t = p[3]
assert isinstance(t, IDLType)
@@ -4512,6 +4936,7 @@ class Parser(Tokenizer):
| ExtendedAttributeArgList
| ExtendedAttributeIdent
| ExtendedAttributeNamedArgList
+ | ExtendedAttributeIdentList
"""
p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1])
@@ -4552,6 +4977,7 @@ class Parser(Tokenizer):
| DATE
| DOMSTRING
| BYTESTRING
+ | SCALARVALUESTRING
| ANY
| ATTRIBUTE
| BOOLEAN
@@ -4685,6 +5111,20 @@ class Parser(Tokenizer):
type = IDLNullableType(self.getLocation(p, 5), type)
p[0] = type
+ # Note: Promise<void> is allowed, so we want to parametrize on
+ # ReturnType, not Type. Also, we want this to end up picking up
+ # the Promise interface for now, hence the games with IDLUnresolvedType.
+ def p_NonAnyTypePromiseType(self, p):
+ """
+ NonAnyType : PROMISE LT ReturnType GT Null
+ """
+ innerType = p[3]
+ promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise")
+ type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3])
+ if p[5]:
+ type = IDLNullableType(self.getLocation(p, 5), type)
+ p[0] = type
+
def p_NonAnyTypeMozMapType(self, p):
"""
NonAnyType : MOZMAP LT Type GT Null
@@ -4701,6 +5141,11 @@ class Parser(Tokenizer):
"""
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:
@@ -4805,6 +5250,12 @@ class Parser(Tokenizer):
"""
p[0] = IDLBuiltinType.Types.bytestring
+ def p_PrimitiveOrStringTypeScalarValueString(self, p):
+ """
+ PrimitiveOrStringType : SCALARVALUESTRING
+ """
+ p[0] = IDLBuiltinType.Types.scalarvaluestring
+
def p_UnsignedIntegerTypeUnsigned(self, p):
"""
UnsignedIntegerType : UNSIGNED IntegerType
@@ -4960,6 +5411,34 @@ class Parser(Tokenizer):
"""
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])
+ idents.insert(0, p[1])
+ p[0] = idents
+
+ def p_IdentifiersList(self, p):
+ """
+ Identifiers : COMMA IDENTIFIER Identifiers
+ """
+ idents = list(p[3])
+ idents.insert(0, p[2])
+ 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",
@@ -4969,12 +5448,26 @@ class Parser(Tokenizer):
def __init__(self, outputdir='', lexer=None):
Tokenizer.__init__(self, outputdir, lexer)
+
+ logger = SqueakyCleanLogger()
self.parser = yacc.yacc(module=self,
outputdir=outputdir,
tabmodule='webidlyacc',
- errorlog=yacc.NullLogger(),
- picklefile='WebIDLGrammar.pkl')
+ errorlog=logger
+ # 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'
+ )
+ logger.reportGrammarErrors()
+
self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None)
+ # To make our test harness work, pretend like we have a primary global already. Note that we _don't_ set _globalScope.primaryGlobalAttr, so we'll still be able to detect multiple PrimaryGlobal extended attributes.
+ self._globalScope.primaryGlobalName = "FakeTestPrimaryGlobal"
+ self._globalScope.globalNames.add("FakeTestPrimaryGlobal")
+ self._globalScope.globalNameMapping["FakeTestPrimaryGlobal"].add("FakeTestPrimaryGlobal")
self._installBuiltins(self._globalScope)
self._productions = []