diff --git a/client.py b/client.py
index 3dcd37056b46267ea61890516966a9ca0fb14844..064d09bed970611cf0ac0d7bfaff8db6cce560c8 100755
--- a/client.py
+++ b/client.py
@@ -7,6 +7,9 @@ NSS_DIRS  = (('dbm', 'mozilla/dbm'),
              ('security/dbm', 'mozilla/security/dbm'))
 NSSCKBI_DIRS = (('security/nss/lib/ckfw/builtins', 'mozilla/security/nss/lib/ckfw/builtins'),)
 LIBFFI_DIRS = (('js/ctypes/libffi', 'libffi'),)
+WEBIDLPARSER_DIR = 'dom/bindings/parser'
+WEBIDLPARSER_REPO = 'https://hg.mozilla.org/users/khuey_mozilla.com/webidl-parser'
+WEBIDLPARSER_EXCLUSIONS = ['.hgignore', '.gitignore', '.hg', 'ply']
 
 CVSROOT_MOZILLA = ':pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot'
 CVSROOT_LIBFFI = ':pserver:anoncvs@sources.redhat.com:/cvs/libffi'
@@ -15,6 +18,7 @@ import os
 import sys
 import datetime
 import shutil
+import glob
 from optparse import OptionParser
 from subprocess import check_call
 
@@ -30,7 +34,6 @@ def do_hg_pull(dir, repository, hg):
     fulldir = os.path.join(topsrcdir, dir)
     # clone if the dir doesn't exist, pull if it does
     if not os.path.exists(fulldir):
-        fulldir = os.path.join(topsrcdir, dir)
         check_call_noisy([hg, 'clone', repository, fulldir])
     else:
         cmd = [hg, 'pull', '-u', '-R', fulldir]
@@ -40,6 +43,25 @@ def do_hg_pull(dir, repository, hg):
     check_call([hg, 'parent', '-R', fulldir,
                 '--template=Updated to revision {node}.\n'])
 
+def do_hg_replace(dir, repository, tag, exclusions, hg):
+    """
+        Replace the contents of dir with the contents of repository, except for
+        files matching exclusions.
+    """
+    fulldir = os.path.join(topsrcdir, dir)
+    if os.path.exists(fulldir):
+        shutil.rmtree(fulldir)
+
+    assert not os.path.exists(fulldir)
+    check_call_noisy([hg, 'clone', '-u', tag, repository, fulldir])
+
+    for thing in exclusions:
+        for excluded in glob.iglob(os.path.join(fulldir, thing)):
+            if os.path.isdir(excluded):
+                shutil.rmtree(excluded)
+            else:
+                os.remove(excluded)
+
 def do_cvs_export(modules, tag, cvsroot, cvs):
     """Check out a CVS directory without CVS metadata, using "export"
     modules is a list of directories to check out and the corresponding
@@ -60,7 +82,7 @@ def do_cvs_export(modules, tag, cvsroot, cvs):
                          cwd=os.path.join(topsrcdir, parent))
         print "CVS export end: " + datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
 
-o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname | update_libffi tagname")
+o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname | update_libffi tagname | update_webidlparser tagname")
 o.add_option("--skip-mozilla", dest="skip_mozilla",
              action="store_true", default=False,
              help="Obsolete")
@@ -69,6 +91,8 @@ o.add_option("--cvs", dest="cvs", default=os.environ.get('CVS', 'cvs'),
              help="The location of the cvs binary")
 o.add_option("--cvsroot", dest="cvsroot",
              help="The CVSROOT (default for mozilla checkouts: %s)" % CVSROOT_MOZILLA)
+o.add_option("--hg", dest="hg", default=os.environ.get('HG', 'hg'),
+             help="The location of the hg binary")
 
 try:
     options, args = o.parse_args()
@@ -104,6 +128,9 @@ elif action in ('update_libffi'):
     if not options.cvsroot:
         options.cvsroot = CVSROOT_LIBFFI
     do_cvs_export(LIBFFI_DIRS, tag, options.cvsroot, options.cvs)
+elif action in ('update_webidlparser'):
+    tag, = args[1:]
+    do_hg_replace(WEBIDLPARSER_DIR, WEBIDLPARSER_REPO, tag, WEBIDLPARSER_EXCLUSIONS, options.hg)
 else:
     o.print_help()
     sys.exit(2)
diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py
index ad7539cdc63e32ca7e264b1eba153d530e97a172..ac59acd9845b03b6dec7ce623b6b87f01635e450 100644
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -69,14 +69,16 @@ def parseInt(literal):
 # Magic for creating enums
 def M_add_class_attribs(attribs):
     def foo(name, bases, dict_):
-        for v, k in attribs:
+        for v, k in enumerate(attribs):
             dict_[k] = v
+        assert 'length' not in dict_
+        dict_['length'] = len(attribs)
         return type(name, bases, dict_)
     return foo
 
 def enum(*names):
     class Foo(object):
-        __metaclass__ = M_add_class_attribs(enumerate(names))
+        __metaclass__ = M_add_class_attribs(names)
         def __setattr__(self, name, value):  # this makes it read-only
             raise NotImplementedError
     return Foo()
@@ -93,9 +95,8 @@ class WebIDLError(Exception):
                                self.location)
 
 class Location(object):
-    _line = None
-
     def __init__(self, lexer, lineno, lexpos, filename):
+        self._line = None
         self._lineno = lineno
         self._lexpos = lexpos
         self._lexdata = lexer.lexdata
@@ -105,36 +106,47 @@ class Location(object):
         return self._lexpos == other._lexpos and \
                self._file == other._file
 
+    def filename(self):
+        return self.filename
+
     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)
-        self._line = self._lexdata[startofline:endofline]
+        if endofline != -1:
+            self._line = self._lexdata[startofline:endofline]
+        else:
+            self._line = self._lexdata[startofline:]
         self._colno = self._lexpos - startofline
 
-    def pointerline(self):
-        def i():
-            for i in xrange(0, self._colno):
-                yield " "
-            yield "^"
-
-        return "".join(i())
-
     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())
+                                          self._line, self._pointerline())
 
 class BuiltinLocation(object):
     def __init__(self, text):
         self.msg = text
 
+    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
 
@@ -150,7 +162,7 @@ class IDLObject(object):
         self.userData = dict()
 
     def filename(self):
-        return self.location._file
+        return self.location.filename()
 
     def isInterface(self):
         return False
@@ -198,6 +210,11 @@ class IDLScope(IDLObject):
         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
@@ -300,6 +317,9 @@ class IDLUnresolvedIdentifier(IDLObject):
             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)
@@ -368,9 +388,8 @@ class IDLInterface(IDLObjectWithScope):
 
         self.parent = parent
         self._callback = False
-
+        self._finished = False
         self.members = list(members) # clone the list
-        assert iter(self.members) # Assert it's iterable
 
         IDLObjectWithScope.__init__(self, location, parentScope, name)
 
@@ -404,7 +423,7 @@ class IDLInterface(IDLObjectWithScope):
         return retval
 
     def finish(self, scope):
-        if hasattr(self, "_finished"):
+        if self._finished:
             return
 
         self._finished = True
@@ -416,7 +435,6 @@ class IDLInterface(IDLObjectWithScope):
         self.parent = parent
 
         assert iter(self.members)
-        members = None
 
         if self.parent:
             self.parent.finish(scope)
@@ -427,19 +445,6 @@ class IDLInterface(IDLObjectWithScope):
         else:
             members = list(self.members)
 
-        SpecialType = enum(
-            'NamedGetter',
-            'NamedSetter',
-            'NamedCreator',
-            'NamedDeleter',
-            'IndexedGetter',
-            'IndexedSetter',
-            'IndexedCreator',
-            'IndexedDeleter'
-        )
-
-        specialMembersSeen = [False for i in range(8)]
-
         def memberNotOnParentChain(member, iface):
             assert iface
 
@@ -451,59 +456,39 @@ class IDLInterface(IDLObjectWithScope):
                 return False
             return memberNotOnParentChain(member, iface.parent)
 
+        # Ensure that there's at most one of each {named,indexed}
+        # {getter,setter,creator,deleter}.
+        specialMembersSeen = set()
         for member in members:
             if memberNotOnParentChain(member, self):
                 member.resolve(self)
-                
-            if member.tag == IDLInterfaceMember.Tags.Method:
-                if member.isGetter():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedGetter]:
-                            raise WebIDLError("Multiple named getters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedGetter] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedGetter]:
-                            raise WebIDLError("Multiple indexed getters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedGetter] = True
-                if member.isSetter():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedSetter]:
-                            raise WebIDLError("Multiple named setters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedSetter] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedSetter]:
-                            raise WebIDLError("Multiple indexed setters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedSetter] = True
-                if member.isCreator():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedCreator]:
-                            raise WebIDLError("Multiple named creators on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedCreator] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedCreator]:
-                            raise WebIDLError("Multiple indexed creators on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedCreator] = True
-                if member.isDeleter():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedDeleter]:
-                            raise WebIDLError("Multiple named deleters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedDeleter] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedDeleter]:
-                            raise WebIDLError("Multiple indexed Deleters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedDeleter] = True
+
+            if member.tag != IDLInterfaceMember.Tags.Method:
+                continue
+
+            if member.isGetter():
+                memberType = "getters"
+            elif member.isSetter():
+                memberType = "setters"
+            elif member.isCreator():
+                memberType = "creators"
+            elif member.isDeleter():
+                memberType = "deleters"
+            else:
+                continue
+
+            if member.isNamed():
+                memberType = "named " + memberType
+            elif member.isIndexed():
+                memberType = "indexed " + memberType
+            else:
+                continue
+
+            if memberType in specialMembersSeen:
+                raise WebIDLError("Multiple " + memberType + " on %s" % (self),
+                                   self.location)
+
+            specialMembersSeen.add(memberType)
 
         for member in self.members:
             member.finish(scope)
@@ -529,7 +514,7 @@ class IDLInterface(IDLObjectWithScope):
         return depth
 
     def hasConstants(self):
-        return reduce(lambda b, m: b or m.isConst(), self.members, False)
+        return any(m.isConst() for m in self.members)
 
     def hasInterfaceObject(self):
         if self.isCallback():
@@ -567,10 +552,7 @@ class IDLInterface(IDLObjectWithScope):
                 identifier = IDLUnresolvedIdentifier(self.location, "constructor",
                                                      allowForbidden=True)
 
-                method = IDLMethod(self.location, identifier, retType, args,
-                                   False, False, False, False, False, False,
-                                   False, False)
-
+                method = IDLMethod(self.location, identifier, retType, args)
                 method.resolve(self)
 
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
@@ -763,6 +745,12 @@ class IDLNullableType(IDLType):
     def isString(self):
         return self.inner.isString()
 
+    def isFloat(self):
+        return self.inner.isFloat()
+
+    def isInteger(self):
+        return self.inner.isInteger()
+
     def isVoid(self):
         return False
 
@@ -772,6 +760,9 @@ class IDLNullableType(IDLType):
     def isArray(self):
         return self.inner.isArray()
 
+    def isArrayBuffer(self):
+        return self.inner.isArrayBuffer()
+
     def isDictionary(self):
         return self.inner.isDictionary()
 
@@ -797,7 +788,7 @@ class IDLNullableType(IDLType):
         return self
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         if other.nullable():
@@ -835,18 +826,19 @@ class IDLSequenceType(IDLType):
         return True
 
     def isArray(self):
-        return self.inner.isArray()
+        return False
 
     def isDictionary(self):
-        return self.inner.isDictionary()
+        return False
 
     def isInterface(self):
-        return self.inner.isInterface()
+        return False
 
     def isEnum(self):
-        return self.inner.isEnum();
+        return False
 
     def tag(self):
+        # XXXkhuey this is probably wrong.
         return self.inner.tag()
 
     def resolveType(self, parentScope):
@@ -858,10 +850,11 @@ class IDLSequenceType(IDLType):
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
+        self.name = self.inner.name
         return self
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         return (other.isPrimitive() or other.isString() or other.isEnum() or
@@ -895,32 +888,33 @@ class IDLArrayType(IDLType):
         return False
 
     def isPrimitive(self):
-        return self.inner.isPrimitive()
+        return False
 
     def isString(self):
-        return self.inner.isString()
+        return False
 
     def isVoid(self):
         return False
 
     def isSequence(self):
         assert not self.inner.isSequence()
-        return self.inner.isSequence()
+        return False
 
     def isArray(self):
         return True
 
     def isDictionary(self):
         assert not self.inner.isDictionary()
-        return self.inner.isDictionary()
+        return False
 
     def isInterface(self):
-        return self.inner.isInterface()
+        return False
 
     def isEnum(self):
-        return self.inner.isEnum()
+        return False
 
     def tag(self):
+        # XXXkhuey this is probably wrong.
         return self.inner.tag()
 
     def resolveType(self, parentScope):
@@ -932,10 +926,11 @@ class IDLArrayType(IDLType):
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
+        self.name = self.inner.name
         return self
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         return (other.isPrimitive() or other.isString() or other.isEnum() or
@@ -995,7 +990,7 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier):
         return self.inner.tag()
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         return self.inner.isDistinguishableFrom(other)
@@ -1041,6 +1036,10 @@ class IDLWrapperType(IDLType):
     def isEnum(self):
         return isinstance(self.inner, IDLEnum)
 
+    def resolveType(self, parentScope):
+        assert isinstance(parentScope, IDLScope)
+        self.inner.resolve(parentScope)
+
     def isComplete(self):
         return True
 
@@ -1122,32 +1121,32 @@ class IDLBuiltinType(IDLType):
     def __init__(self, location, name, type):
         IDLType.__init__(self, location, name)
         self.builtin = True
-        self.type = type
+        self._typeTag = type
 
     def isPrimitive(self):
-        return self.type <= IDLBuiltinType.Types.double
+        return self._typeTag <= IDLBuiltinType.Types.double
 
     def isString(self):
-        return self.type == IDLBuiltinType.Types.domstring
+        return self._typeTag == IDLBuiltinType.Types.domstring
 
     def isInteger(self):
-        return self.type <= IDLBuiltinType.Types.unsigned_long_long
+        return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long
 
     def isArrayBuffer(self):
-        return self.type == IDLBuiltinType.Types.ArrayBuffer
+        return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
 
     def isInterface(self):
         # ArrayBuffers are interface types per the TypedArray spec,
         # but we handle them as builtins because SpiderMonkey implements
         # ArrayBuffers.
-        return self.type == IDLBuiltinType.Types.ArrayBuffer
+        return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
 
     def isFloat(self):
-        return self.type == IDLBuiltinType.Types.float or \
-               self.type == IDLBuiltinType.Types.double
+        return self._typeTag == IDLBuiltinType.Types.float or \
+               self._typeTag == IDLBuiltinType.Types.double
 
     def tag(self):
-        return IDLBuiltinType.TagLookup[self.type]
+        return IDLBuiltinType.TagLookup[self._typeTag]
 
     def isDistinguishableFrom(self, other):
         if self.isPrimitive() or self.isString():
@@ -1280,7 +1279,7 @@ class IDLValue(IDLObject):
 
             # We're both integer types.  See if we fit.
 
-            (min, max) = integerTypeSizes[type.type]
+            (min, max) = integerTypeSizes[type._typeTag]
             if self.value <= max and self.value >= min:
                 # Promote
                 return IDLValue(self.location, type, self.value)
@@ -1492,8 +1491,10 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
     )
 
     def __init__(self, location, identifier, returnType, arguments,
-                 static, getter, setter, creator, deleter, specialType, legacycaller,
-                 stringifier):
+                 static=False, getter=False, setter=False, creator=False,
+                 deleter=False, specialType=NamedOrIndexed.Neither,
+                 legacycaller=False, stringifier=False):
+        # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
         IDLInterfaceMember.__init__(self, location, identifier,
                                     IDLInterfaceMember.Tags.Method)
 
@@ -1678,6 +1679,7 @@ class Tokenizer(object):
     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:
             raise WebIDLError("Invalid integer literal",
@@ -2261,8 +2263,9 @@ class Parser(Tokenizer):
                  "legacycaller" if legacycaller else ""), allowDoubleUnderscore=True)
 
         method = IDLMethod(self.getLocation(p, 2), identifier, returnType, arguments,
-                           static, getter, setter, creator, deleter, specialType,
-                           legacycaller, False)
+                           static=static, getter=getter, setter=setter, creator=creator,
+                           deleter=deleter, specialType=specialType,
+                           legacycaller=legacycaller, stringifier=False)
         p[0] = method
 
     def p_QualifiersStatic(self, p):
@@ -2861,7 +2864,14 @@ class Parser(Tokenizer):
         for production in self._productions:
             production.finish(self.globalScope())
 
-        return set(self._productions)
+        # 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()
diff --git a/dom/bindings/parser/__init__.py b/dom/bindings/parser/__init__.py
deleted file mode 100644
index c082fe541a204da58b1de885cc2c12f9536399b5..0000000000000000000000000000000000000000
--- a/dom/bindings/parser/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['WebIDL']
diff --git a/dom/bindings/parser/runtests.py b/dom/bindings/parser/runtests.py
index 3835d4bcf2b6a5429b032cfdb46b30027d9c5a5b..37b6e07ce2fafb3aeaca0c016bb572b7f448ad56 100644
--- a/dom/bindings/parser/runtests.py
+++ b/dom/bindings/parser/runtests.py
@@ -37,36 +37,76 @@
 
 import os, sys
 import glob
+import optparse
+import traceback
 import WebIDL
 
 class TestHarness(object):
+    def __init__(self, test, verbose):
+        self.test = test
+        self.verbose = verbose
+        self.printed_intro = False
+
+    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):
+        if self.verbose:
+            print "TEST-PASS | %s" % msg
+
+    def test_fail(self, msg):
+        self.maybe_print_intro()
+        print "TEST-UNEXPECTED-FAIL | %s" % msg
+
     def ok(self, condition, msg):
         if condition:
-            print "TEST-PASS | %s" % msg
+            self.test_pass(msg)
         else:
-            print "TEST-UNEXPECTED-FAIL | %s" % msg
+            self.test_fail(msg)
 
     def check(self, a, b, msg):
         if a == b:
-            print "TEST-PASS | %s" % msg
+            self.test_pass(msg)
         else:
-            print "TEST-UNEXPECTED-FAIL | %s" % msg
+            self.test_fail(msg)
             print "\tGot %s expected %s" % (a, b)
 
-def run_tests():
-    harness = TestHarness()
+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)
 
-    tests = glob.iglob("tests/*.py")
-    sys.path.append("./tests")
     for test in tests:
         (testpath, ext) = os.path.splitext(os.path.basename(test))
         _test = __import__(testpath, globals(), locals(), ['WebIDLTest'])
-        #try:
-        _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
-        #except:
-        #    print "TEST-UNEXPECTED-FAIL | Unhandled exception in Test %s" % testpath
-        #    print sys.exc_info()[0]
-        print "Test %s Complete\n" % testpath
+
+        harness = TestHarness(test, verbose)
+        harness.start()
+        try:
+            _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
+        except:
+            print "TEST-UNEXPECTED-FAIL | Unhandled exception in test %s" % testpath
+            traceback.print_exc()
+        finally:
+            harness.finish()
 
 if __name__ == '__main__':
-    run_tests()
+    usage = """%prog [OPTIONS] [TESTS]
+               Where TESTS are relative to the tests directory."""
+    parser = optparse.OptionParser(usage=usage)
+    parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
+                      help="Don't print passing tests.")
+    options, tests = parser.parse_args()
+
+    run_tests(tests, verbose=options.verbose)
diff --git a/dom/bindings/parser/tests/test_array_of_interface.py b/dom/bindings/parser/tests/test_array_of_interface.py
new file mode 100644
index 0000000000000000000000000000000000000000..265289845957a0eec0ad1004e25c87e006fee325
--- /dev/null
+++ b/dom/bindings/parser/tests/test_array_of_interface.py
@@ -0,0 +1,13 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    parser.parse("""
+      interface A {
+        attribute long a;
+      };
+
+      interface B {
+        attribute A[] b;
+      };
+    """);
+    parser.finish()
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 0000000000000000000000000000000000000000..631e52eba0b10848e3c1903120975c18fef546fa
--- /dev/null
+++ b/dom/bindings/parser/tests/test_builtin_filename.py
@@ -0,0 +1,11 @@
+import WebIDL
+
+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_constructor.py b/dom/bindings/parser/tests/test_constructor.py
index 03819ed8ccf452e97f8c8b3b89924f8de0dd9933..6ec1be1871bdadbf88d45b73ed8cc7450a0fc67c 100644
--- a/dom/bindings/parser/tests/test_constructor.py
+++ b/dom/bindings/parser/tests/test_constructor.py
@@ -62,14 +62,14 @@ def WebIDLTest(parser, harness):
                "Should be an IDLInterface")
 
     checkMethod(results[0].ctor(), "::TestConstructorNoArgs::constructor",
-                "constructor", [("TestConstructorNoArgs", [])])
+                "constructor", [("TestConstructorNoArgs (Wrapper)", [])])
     checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor",
                 "constructor",
-                [("TestConstructorWithArgs",
+                [("TestConstructorWithArgs (Wrapper)",
                  [("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])])
     checkMethod(results[2].ctor(), "::TestConstructorOverloads::constructor",
                 "constructor",
-                [("TestConstructorOverloads",
+                [("TestConstructorOverloads (Wrapper)",
                  [("::TestConstructorOverloads::constructor::foo", "foo", "Object", False, False)]),
-                 ("TestConstructorOverloads",
+                 ("TestConstructorOverloads (Wrapper)",
                  [("::TestConstructorOverloads::constructor::bar", "bar", "Boolean", False, False)])])
diff --git a/dom/bindings/parser/tests/test_deduplicate.py b/dom/bindings/parser/tests/test_deduplicate.py
new file mode 100644
index 0000000000000000000000000000000000000000..6249d36fb8f3c3a58f97aca072a86143c6946f71
--- /dev/null
+++ b/dom/bindings/parser/tests/test_deduplicate.py
@@ -0,0 +1,15 @@
+import WebIDL
+
+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_enum.py b/dom/bindings/parser/tests/test_enum.py
index 68ec73b2ff2171f7d0e3b4a7a4b75ae3ef8b24b9..069c7f336a74a7bfaa2b7dfbac962bc53967586d 100644
--- a/dom/bindings/parser/tests/test_enum.py
+++ b/dom/bindings/parser/tests/test_enum.py
@@ -47,7 +47,7 @@ def WebIDLTest(parser, harness):
     harness.check(len(signatures), 1, "Expect one signature")
 
     (returnType, arguments) = signatures[0]
-    harness.check(str(returnType), "TestEnum", "Method type is the correct name")
+    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")
@@ -58,4 +58,4 @@ def WebIDLTest(parser, harness):
                   "Attr has correct QName")
     harness.check(attr.identifier.name, "foo", "Attr has correct name")
 
-    harness.check(str(attr.type), "TestEnum", "Attr type is the correct name")
+    harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name")
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 0000000000000000000000000000000000000000..7afd15513c6d04100dd190fb936981535b01e6aa
--- /dev/null
+++ b/dom/bindings/parser/tests/test_error_colno.py
@@ -0,0 +1,20 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    # Check that error messages put the '^' in the right place.
+
+    threw = False
+    input = 'interface ?'
+    try:
+        parser.parse(input)
+        results = 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_nullable_equivalency.py b/dom/bindings/parser/tests/test_nullable_equivalency.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8211a8bb57155c5ee28824a01c957cea19521cb
--- /dev/null
+++ b/dom/bindings/parser/tests/test_nullable_equivalency.py
@@ -0,0 +1,134 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    parser.parse("""
+        interface TestNullableEquivalency1 {
+          attribute long  a;
+          attribute long? b;
+        };
+
+        interface TestNullableEquivalency2 {
+          attribute ArrayBuffer  a;
+          attribute ArrayBuffer? b;
+        };
+
+        /* Not implemented */
+        /*dictionary TestNullableEquivalency3Dict {
+          long foo = 42;
+        };
+
+        interface TestNullableEquivalency3 {
+          attribute Test3Dict  a;
+          attribute Test3Dict? b;
+        };*/
+
+        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;
+        };
+
+        /* Not implemented. */
+        /*interface TestNullableEquivalency8 {
+          attribute float  a;
+          attribute float? b;
+        };*/
+
+        interface TestNullableEquivalency8 {
+          attribute double  a;
+          attribute double? b;
+        };
+
+        interface TestNullableEquivalency9 {
+          attribute object  a;
+          attribute object? b;
+        };
+
+        interface TestNullableEquivalency10 {
+          attribute double[]  a;
+          attribute double[]? b;
+        };
+
+        interface TestNullableEquivalency11 {
+          attribute TestNullableEquivalency9[]  a;
+          attribute TestNullableEquivalency9[]? 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'] or \
+           (hasattr(type(type1), attr) and not callable(getattr(type1, attr))):
+            continue
+
+        a1 = getattr(type1, attr)
+
+        if callable(a1):
+            try:
+                v1 = a1()
+            except:
+                # Can't call a1 with no args, so skip this attriute.
+                continue
+
+            try:
+                a2 = getattr(type2, attr)
+            except:
+                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:
+                harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
+                continue
+
+            harness.check(a2, a1, '%s attribute should match' % attr)