def WebIDLTest(parser, harness):
    # Basic functionality
    threw = False
    try:
        parser.parse(
            """
            typedef [EnforceRange] long Foo;
            typedef [Clamp] long Bar;
            typedef [LegacyNullToEmptyString] DOMString Baz;
            dictionary A {
                required [EnforceRange] long a;
                required [Clamp] long b;
                [ChromeOnly, EnforceRange] long c;
                Foo d;
            };
            interface B {
                attribute Foo typedefFoo;
                attribute [EnforceRange] long foo;
                attribute [Clamp] long bar;
                attribute [LegacyNullToEmptyString] DOMString baz;
                undefined method([EnforceRange] long foo, [Clamp] long bar,
                                 [LegacyNullToEmptyString] DOMString baz);
                undefined method2(optional [EnforceRange] long foo, optional [Clamp] long bar,
                                  optional [LegacyNullToEmptyString] DOMString baz);
                undefined method3(optional [LegacyNullToEmptyString] UTF8String foo = "");
            };
            interface C {
                attribute [EnforceRange] long? foo;
                attribute [Clamp] long? bar;
                undefined method([EnforceRange] long? foo, [Clamp] long? bar);
                undefined method2(optional [EnforceRange] long? foo, optional [Clamp] long? bar);
            };
            interface Setlike {
                setlike<[Clamp] long>;
            };
            interface Maplike {
                maplike<[Clamp] long, [EnforceRange] long>;
            };
            interface Iterable {
                iterable<[Clamp] long, [EnforceRange] long>;
            };
        """
        )
        results = parser.finish()
    except Exception:
        threw = True

    harness.ok(not threw, "Should not have thrown on parsing normal")
    if not threw:
        harness.check(
            results[0].innerType.hasEnforceRange(), True, "Foo is [EnforceRange]"
        )
        harness.check(results[1].innerType.hasClamp(), True, "Bar is [Clamp]")
        harness.check(
            results[2].innerType.legacyNullToEmptyString,
            True,
            "Baz is [LegacyNullToEmptyString]",
        )
        A = results[3]
        harness.check(
            A.members[0].type.hasEnforceRange(), True, "A.a is [EnforceRange]"
        )
        harness.check(A.members[1].type.hasClamp(), True, "A.b is [Clamp]")
        harness.check(
            A.members[2].type.hasEnforceRange(), True, "A.c is [EnforceRange]"
        )
        harness.check(
            A.members[3].type.hasEnforceRange(), True, "A.d is [EnforceRange]"
        )
        B = results[4]
        harness.check(
            B.members[0].type.hasEnforceRange(), True, "B.typedefFoo is [EnforceRange]"
        )
        harness.check(
            B.members[1].type.hasEnforceRange(), True, "B.foo is [EnforceRange]"
        )
        harness.check(B.members[2].type.hasClamp(), True, "B.bar is [Clamp]")
        harness.check(
            B.members[3].type.legacyNullToEmptyString,
            True,
            "B.baz is [LegacyNullToEmptyString]",
        )
        method = B.members[4].signatures()[0][1]
        harness.check(
            method[0].type.hasEnforceRange(),
            True,
            "foo argument of method is [EnforceRange]",
        )
        harness.check(
            method[1].type.hasClamp(), True, "bar argument of method is [Clamp]"
        )
        harness.check(
            method[2].type.legacyNullToEmptyString,
            True,
            "baz argument of method is [LegacyNullToEmptyString]",
        )
        method2 = B.members[5].signatures()[0][1]
        harness.check(
            method2[0].type.hasEnforceRange(),
            True,
            "foo argument of method2 is [EnforceRange]",
        )
        harness.check(
            method2[1].type.hasClamp(), True, "bar argument of method2 is [Clamp]"
        )
        harness.check(
            method2[2].type.legacyNullToEmptyString,
            True,
            "baz argument of method2 is [LegacyNullToEmptyString]",
        )

        method3 = B.members[6].signatures()[0][1]
        harness.check(
            method3[0].type.legacyNullToEmptyString,
            True,
            "bar argument of method2 is [LegacyNullToEmptyString]",
        )
        harness.check(
            method3[0].defaultValue.type.isUTF8String(),
            True,
            "default value of bar argument of method2 is correctly coerced to UTF8String",
        )

        C = results[5]
        harness.ok(C.members[0].type.nullable(), "C.foo is nullable")
        harness.ok(C.members[0].type.hasEnforceRange(), "C.foo has [EnforceRange]")
        harness.ok(C.members[1].type.nullable(), "C.bar is nullable")
        harness.ok(C.members[1].type.hasClamp(), "C.bar has [Clamp]")
        method = C.members[2].signatures()[0][1]
        harness.ok(method[0].type.nullable(), "foo argument of method is nullable")
        harness.ok(
            method[0].type.hasEnforceRange(),
            "foo argument of method has [EnforceRange]",
        )
        harness.ok(method[1].type.nullable(), "bar argument of method is nullable")
        harness.ok(method[1].type.hasClamp(), "bar argument of method has [Clamp]")
        method2 = C.members[3].signatures()[0][1]
        harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable")
        harness.ok(
            method2[0].type.hasEnforceRange(),
            "foo argument of method2 has [EnforceRange]",
        )
        harness.ok(method2[1].type.nullable(), "bar argument of method2 is nullable")
        harness.ok(method2[1].type.hasClamp(), "bar argument of method2 has [Clamp]")

    # Test [AllowShared]
    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [AllowShared] ArrayBufferView Foo;
            dictionary A {
                required [AllowShared] ArrayBufferView a;
                [ChromeOnly, AllowShared] ArrayBufferView b;
                Foo c;
            };
            interface B {
                attribute Foo typedefFoo;
                attribute [AllowShared] ArrayBufferView foo;
                undefined method([AllowShared] ArrayBufferView foo);
                undefined method2(optional [AllowShared] ArrayBufferView foo);
            };
            interface C {
                attribute [AllowShared] ArrayBufferView? foo;
                undefined method([AllowShared] ArrayBufferView? foo);
                undefined method2(optional [AllowShared] ArrayBufferView? foo);
            };
            interface Setlike {
                setlike<[AllowShared] ArrayBufferView>;
            };
            interface Maplike {
                maplike<[Clamp] long, [AllowShared] ArrayBufferView>;
            };
            interface Iterable {
                iterable<[Clamp] long, [AllowShared] ArrayBufferView>;
            };
        """
        )
        results = parser.finish()
    except Exception:
        threw = True

    harness.ok(not threw, "Should not have thrown on parsing normal")
    if not threw:
        harness.ok(results[0].innerType.hasAllowShared(), "Foo is [AllowShared]")
        A = results[1]
        harness.ok(A.members[0].type.hasAllowShared(), "A.a is [AllowShared]")
        harness.ok(A.members[1].type.hasAllowShared(), "A.b is [AllowShared]")
        harness.ok(A.members[2].type.hasAllowShared(), "A.c is [AllowShared]")
        B = results[2]
        harness.ok(B.members[0].type.hasAllowShared(), "B.typedefFoo is [AllowShared]")
        harness.ok(B.members[1].type.hasAllowShared(), "B.foo is [AllowShared]")
        method = B.members[2].signatures()[0][1]
        harness.ok(
            method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]"
        )
        method2 = B.members[3].signatures()[0][1]
        harness.ok(
            method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]"
        )
        C = results[3]
        harness.ok(C.members[0].type.nullable(), "C.foo is nullable")
        harness.ok(C.members[0].type.hasAllowShared(), "C.foo is [AllowShared]")
        method = C.members[1].signatures()[0][1]
        harness.ok(method[0].type.nullable(), "foo argument of method is nullable")
        harness.ok(
            method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]"
        )
        method2 = C.members[2].signatures()[0][1]
        harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable")
        harness.ok(
            method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]"
        )

    ATTRIBUTES = [
        ("[Clamp]", "long"),
        ("[EnforceRange]", "long"),
        ("[LegacyNullToEmptyString]", "DOMString"),
        ("[AllowShared]", "ArrayBufferView"),
    ]
    TEMPLATES = [
        (
            "required dictionary members",
            """
            dictionary Foo {
                %s required %s foo;
            };
        """,
        ),
        (
            "optional arguments",
            """
            interface Foo {
                undefined foo(%s optional %s foo);
            };
        """,
        ),
        (
            "typedefs",
            """
            %s typedef %s foo;
        """,
        ),
        (
            "attributes",
            """
            interface Foo {
            %s attribute %s foo;
            };
        """,
        ),
        (
            "readonly attributes",
            """
            interface Foo {
                readonly attribute %s %s foo;
            };
        """,
        ),
        (
            "readonly unresolved attributes",
            """
            interface Foo {
              readonly attribute Bar baz;
            };
            typedef %s %s Bar;
        """,
        ),
        (
            "method",
            """
            interface Foo {
              %s %s foo();
            };
        """,
        ),
        (
            "interface",
            """
            %s
            interface Foo {
              attribute %s foo;
            };
        """,
        ),
        (
            "partial interface",
            """
            interface Foo {
              undefined foo();
            };
            %s
            partial interface Foo {
              attribute %s bar;
            };
        """,
        ),
        (
            "interface mixin",
            """
            %s
            interface mixin Foo {
              attribute %s foo;
            };
        """,
        ),
        (
            "namespace",
            """
            %s
            namespace Foo {
              attribute %s foo;
            };
        """,
        ),
        (
            "partial namespace",
            """
            namespace Foo {
              undefined foo();
            };
            %s
            partial namespace Foo {
              attribute %s bar;
            };
        """,
        ),
        (
            "dictionary",
            """
            %s
            dictionary Foo {
              %s foo;
            };
        """,
        ),
    ]

    for (name, template) in TEMPLATES:
        parser = parser.reset()
        threw = False
        try:
            parser.parse(template % ("", "long"))
            parser.finish()
        except Exception:
            threw = True
        harness.ok(not threw, "Template for %s parses without attributes" % name)
        for (attribute, type) in ATTRIBUTES:
            parser = parser.reset()
            threw = False
            try:
                parser.parse(template % (attribute, type))
                parser.finish()
            except Exception:
                threw = True
            harness.ok(threw, "Should not allow %s on %s" % (attribute, name))

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [Clamp, EnforceRange] long Foo;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [EnforceRange, Clamp] long Foo;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [Clamp] long Foo;
            typedef [EnforceRange] Foo bar;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [EnforceRange] long Foo;
            typedef [Clamp] Foo bar;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs")

    TYPES = [
        "DOMString",
        "unrestricted float",
        "float",
        "unrestricted double",
        "double",
    ]

    for type in TYPES:
        parser = parser.reset()
        threw = False
        try:
            parser.parse(
                """
                typedef [Clamp] %s Foo;
            """
                % type
            )
            parser.finish()
        except Exception:
            threw = True

        harness.ok(threw, "Should not allow [Clamp] on %s" % type)

        parser = parser.reset()
        threw = False
        try:
            parser.parse(
                """
                typedef [EnforceRange] %s Foo;
            """
                % type
            )
            parser.finish()
        except Exception:
            threw = True

        harness.ok(threw, "Should not allow [EnforceRange] on %s" % type)

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [LegacyNullToEmptyString] long Foo;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(threw, "Should not allow [LegacyNullToEmptyString] on long")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [LegacyNullToEmptyString] JSString Foo;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(threw, "Should not allow [LegacyNullToEmptyString] on JSString")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [LegacyNullToEmptyString] DOMString? Foo;
        """
        )
        parser.finish()
    except Exception:
        threw = True

    harness.ok(
        threw, "Should not allow [LegacyNullToEmptyString] on nullable DOMString"
    )

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [AllowShared] DOMString Foo;
        """
        )
        results = parser.finish()
    except Exception:
        threw = True
    harness.ok(threw, "[AllowShared] only allowed on buffer source types")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            typedef [AllowShared=something] ArrayBufferView Foo;
        """
        )
        results = parser.finish()
    except Exception:
        threw = True
    harness.ok(threw, "[AllowShared] must take no arguments")

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            interface Foo {
               undefined foo([Clamp] Bar arg);
            };
            typedef long Bar;
        """
        )
        results = parser.finish()
    except Exception:
        threw = True
    harness.ok(not threw, "Should allow type attributes on unresolved types")
    harness.check(
        results[0].members[0].signatures()[0][1][0].type.hasClamp(),
        True,
        "Unresolved types with type attributes should correctly resolve with attributes",
    )

    parser = parser.reset()
    threw = False
    try:
        parser.parse(
            """
            interface Foo {
               undefined foo(Bar arg);
            };
            typedef [Clamp] long Bar;
        """
        )
        results = parser.finish()
    except Exception:
        threw = True
    harness.ok(not threw, "Should allow type attributes on typedefs")
    harness.check(
        results[0].members[0].signatures()[0][1][0].type.hasClamp(),
        True,
        "Unresolved types that resolve to typedefs with attributes should correctly resolve with "
        "attributes",
    )
