tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_selectors.html (61861B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <title>Test for CSS Selectors</title>
      5  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      6  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      7 </head>
      8 <body onload="run()">
      9 <p id="display"><iframe id="iframe" src="about:blank"></iframe><iframe id="cloneiframe" src="about:blank"></iframe></p>
     10 <pre id="test">
     11 <script class="testbody" type="text/javascript">
     12 
     13 SimpleTest.waitForExplicitFinish();
     14 SimpleTest.requestLongerTimeout(2);
     15 
     16 var cloneiframe;
     17 
     18 function run() {
     19  SpecialPowers.pushPrefEnv({ set: [["layout.css.xul-tree-pseudos.content.enabled", true]] }, runTests);
     20 }
     21 function runTests() {
     22    var iframe = document.getElementById("iframe");
     23    var ifwin = iframe.contentWindow;
     24    var ifdoc = iframe.contentDocument;
     25 
     26    cloneiframe = document.getElementById("cloneiframe");
     27 
     28    var style_elem = ifdoc.createElement("style");
     29    style_elem.setAttribute("type", "text/css");
     30    ifdoc.getElementsByTagName("head")[0].appendChild(style_elem);
     31    var style_text = ifdoc.createTextNode("");
     32    style_elem.appendChild(style_text);
     33 
     34    var gCounter = 0;
     35 
     36    /*
     37     * selector: the selector to test
     38     * body_contents: what to set the body's innerHTML to
     39     * match_fn: a function that, given the document object into which
     40     *   body_contents has been inserted, produces an array of nodes that
     41     *   should match selector
     42     * notmatch_fn: likewise, but for nodes that should not match
     43     * namespaces (optional): @namespace rules to be included in the sheet
     44     */
     45    function test_selector_in_html(selector, body_contents, match_fn, notmatch_fn, namespaces)
     46    {
     47        var zi = ++gCounter;
     48        if (typeof(body_contents) == "string") {
     49            ifdoc.body.innerHTML = body_contents;
     50        } else {
     51            // It's a function.
     52            ifdoc.body.innerHTML = "";
     53            body_contents(ifdoc.body);
     54        }
     55        if (!namespaces) {
     56            namespaces = "";
     57        }
     58        style_text.data = namespaces + selector + "{ z-index: " + zi + " }";
     59 
     60        var idx = style_text.parentNode.sheet.cssRules.length - 1;
     61        if (namespaces == "") {
     62            is(idx, 0, "unexpected rule index for " + selector);
     63        }
     64        if (idx < 0 ||
     65            style_text.parentNode.sheet.cssRules[idx].type !=
     66                CSSRule.STYLE_RULE)
     67        {
     68            ok(false, "selector " + selector + " could not be parsed");
     69            return;
     70        }
     71 
     72        var should_match = match_fn(ifdoc);
     73        var should_not_match = notmatch_fn(ifdoc);
     74        if (should_match.length + should_not_match.length == 0) {
     75            ok(false, "nothing to check");
     76        }
     77 
     78        for (let i = 0; i < should_match.length; ++i) {
     79            let e = should_match[i];
     80            is(ifwin.getComputedStyle(e).zIndex, String(zi),
     81               "element in " + body_contents + " matched " + selector);
     82        }
     83        for (let i = 0; i < should_not_match.length; ++i) {
     84            let e = should_not_match[i];
     85            is(ifwin.getComputedStyle(e).zIndex, "auto",
     86               "element in " + body_contents + " did not match " + selector);
     87        }
     88 
     89        // Now, since we're here, may as well make sure serialization
     90        // works correctly.  It need not produce the exact same text,
     91        // but it should produce a selector that matches the same
     92        // elements.
     93        zi = ++gCounter;
     94        var ser1 = style_text.parentNode.sheet.cssRules[idx].selectorText;
     95        style_text.data = namespaces + ser1 + "{ z-index: " + zi + " }";
     96        for (let i = 0; i < should_match.length; ++i) {
     97            let e = should_match[i];
     98            is(ifwin.getComputedStyle(e).zIndex, String(zi),
     99               "element in " + body_contents + " matched " + ser1 +
    100               " which is the reserialization of " + selector);
    101        }
    102        for (let i = 0; i < should_not_match.length; ++i) {
    103            let e = should_not_match[i];
    104            is(ifwin.getComputedStyle(e).zIndex, "auto",
    105               "element in " + body_contents + " did not match " + ser1 +
    106               " which is the reserialization of " + selector);
    107        }
    108 
    109        // But when we serialize the serialized result, we should get
    110        // the same text.
    111        isnot(style_text.parentNode.sheet.cssRules[idx], undefined,
    112              "parse of selector \"" + ser1 + "\" failed");
    113 
    114        if (style_text.parentNode.sheet.cssRules[idx] !== undefined) {
    115            var ser2 = style_text.parentNode.sheet.cssRules[idx].selectorText;
    116            is(ser2, ser1, "parse+serialize of selector \"" + selector +
    117                           "\" is idempotent");
    118        }
    119 
    120        ifdoc.body.innerHTML = "";
    121        style_text.data = "";
    122 
    123        // And now test that when we clone the style sheet, we end up
    124        // with the same selector (serializes to same string, and
    125        // matches the same things).
    126        zi = ++gCounter;
    127        var style_sheet = "data:text/css," +
    128            escape(namespaces + selector + "{ z-index: " + zi + " }");
    129        var style_sheet_link =
    130            "<link rel='stylesheet' href='" + style_sheet + "'>";
    131        var html_doc = "<!DOCTYPE HTML>" +
    132                       style_sheet_link + style_sheet_link +
    133                       "<body>";
    134        if (typeof(body_contents) == "string") {
    135            html_doc += body_contents;
    136        }
    137        var docurl = "data:text/html," + escape(html_doc);
    138        defer_clonedoc_tests(docurl, function() {
    139            var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
    140            var clonedoc = wrappedCloneFrame.contentDocument;
    141            var clonewin = wrappedCloneFrame.contentWindow;
    142 
    143            if (typeof(body_contents) != "string") {
    144                body_contents(clonedoc.body);
    145            }
    146 
    147            var links = clonedoc.getElementsByTagName("link");
    148            // cause a clone
    149            links[1].sheet.insertRule("#nonexistent { color: purple}", idx + 1);
    150            // remove the uncloned sheet
    151            links[0].remove();
    152 
    153            var should_match1 = match_fn(clonedoc);
    154            var should_not_match1 = notmatch_fn(clonedoc);
    155 
    156            if (should_match1.length + should_not_match1.length == 0) {
    157                ok(false, "nothing to check");
    158            }
    159 
    160            for (let i = 0; i < should_match1.length; ++i) {
    161                let e = should_match1[i];
    162                is(clonewin.getComputedStyle(e).zIndex, String(zi),
    163                   "element in " + body_contents + " matched clone of " +
    164                   selector);
    165            }
    166            for (let i = 0; i < should_not_match1.length; ++i) {
    167                let e = should_not_match1[i];
    168                is(clonewin.getComputedStyle(e).zIndex, "auto",
    169                   "element in " + body_contents + " did not match clone of " +
    170                   selector);
    171            }
    172 
    173            var ser3 = links[0].sheet.cssRules[idx].selectorText;
    174            is(ser3, ser1,
    175               "selector " + selector + " serializes correctly after cloning");
    176        });
    177    }
    178 
    179    function should_serialize_to(selector, serialization)
    180    {
    181        style_text.data = selector + "{ z-index: 0 }";
    182        is(style_text.parentNode.sheet.cssRules[0].selectorText,
    183           serialization,
    184           "selector '" + selector + "' should serialize to '" +
    185             serialization + "'.");
    186    }
    187 
    188    function test_parseable(selector)
    189    {
    190        ifdoc.body.innerHTML = "<p></p>";
    191 
    192        var zi = ++gCounter;
    193        style_text.data = "p, " + selector + "{ z-index: " + zi + " }";
    194        var should_match = ifdoc.getElementsByTagName("p")[0];
    195        var parsed = ifwin.getComputedStyle(should_match).zIndex == zi;
    196        ok(parsed, "selector " + selector + " was parsed");
    197        if (!parsed) {
    198            return;
    199        }
    200 
    201        // Test that it serializes to something that is also parseable.
    202        var ser1 = style_elem.sheet.cssRules[0].selectorText;
    203        zi = ++gCounter;
    204        style_text.data = ser1 + "{ z-index: " + zi + " }";
    205        is(ifwin.getComputedStyle(should_match).zIndex, String(zi),
    206           "serialization " + ser1 + " of selector p, " + selector +
    207           " was parsed");
    208        var ser2 = style_elem.sheet.cssRules[0].selectorText;
    209        is(ser2, ser1,
    210           "parse+serialize of selector " + selector + " is idempotent");
    211 
    212        ifdoc.body.innerHTML = "";
    213        style_text.data = "";
    214 
    215        // Test that it clones to the same thing it serializes to.
    216        zi = ++gCounter;
    217        var style_sheet = "data:text/css," +
    218                          escape("p, " + selector + "{ z-index: " + zi + " }");
    219        var style_sheet_link =
    220            "<link rel='stylesheet' href='" + style_sheet + "'>";
    221        var html_doc = "<!DOCTYPE HTML>" +
    222                       style_sheet_link + style_sheet_link +
    223                       "<p></p>";
    224        var docurl = "data:text/html," + escape(html_doc);
    225 
    226        defer_clonedoc_tests(docurl, function() {
    227            var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
    228            var clonedoc = wrappedCloneFrame.contentDocument;
    229            var clonewin = wrappedCloneFrame.contentWindow;
    230            var links = clonedoc.getElementsByTagName("link");
    231            // cause a clone
    232            links[1].sheet.insertRule("#nonexistent { color: purple}", 0);
    233            // remove the uncloned sheet
    234            links[0].remove();
    235 
    236            should_match = clonedoc.getElementsByTagName("p")[0];
    237            is(clonewin.getComputedStyle(should_match).zIndex, String(zi),
    238               "selector " + selector + " was cloned correctly");
    239            var ser3 = links[0].sheet.cssRules[1].selectorText;
    240            is(ser3, ser1,
    241               "selector " + selector + " serializes correctly after cloning");
    242        });
    243    }
    244 
    245    function test_unparseable_via_api(selector)
    246    {
    247        try {
    248          // Test that it is also unparseable when followed by EOF.
    249          ifdoc.body.matches(selector);
    250          ok(false, "selector '" + selector + "' plus EOF is parse error");
    251        } catch(ex) {
    252          is(ex.name, "SyntaxError",
    253             "selector '" + selector + "' plus EOF is parse error");
    254          is(ex.code, DOMException.SYNTAX_ERR,
    255             "selector '" + selector + "' plus EOF is parse error");
    256        }
    257    }
    258 
    259    function test_parseable_via_api(selector)
    260    {
    261        var threw = false;
    262        try {
    263          // Test that a selector is parseable when followed by EOF.
    264          ifdoc.body.matches(selector);
    265        } catch(ex) {
    266          threw = true;
    267        }
    268        ok(!threw, "selector '" + selector + "' was parsed");
    269    }
    270 
    271    function test_balanced_unparseable(selector)
    272    {
    273        var zi1 = ++gCounter;
    274        var zi2 = ++gCounter;
    275        ifdoc.body.innerHTML = "<p></p><div></div>";
    276        style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }" +
    277                          "div { z-index: " + zi2 + " }";
    278        var should_not_match = ifdoc.getElementsByTagName("p")[0];
    279        var should_match = ifdoc.getElementsByTagName("div")[0];
    280        is(ifwin.getComputedStyle(should_not_match).zIndex, "auto",
    281           "selector " + selector + " was a parser error");
    282        is(ifwin.getComputedStyle(should_match).zIndex, String(zi2),
    283           "selector " + selector + " error was recovered from");
    284        ifdoc.body.innerHTML = "";
    285        style_text.data = "";
    286        test_unparseable_via_api(selector);
    287    }
    288 
    289    function test_unbalanced_unparseable(selector)
    290    {
    291        var zi1 = ++gCounter;
    292        var zi2 = ++gCounter;
    293        ifdoc.body.innerHTML = "<p></p>";
    294        style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }";
    295        var should_not_match = ifdoc.getElementsByTagName("p")[0];
    296        is(ifwin.getComputedStyle(should_not_match).zIndex, "auto",
    297           "selector " + selector + " was a parser error");
    298        is(style_text.parentNode.sheet.cssRules.length, 0,
    299           "sheet should have no rules since " + selector + " is parse error");
    300        ifdoc.body.innerHTML = "";
    301        style_text.data = "";
    302        test_unparseable_via_api(selector);
    303    }
    304 
    305    // [attr] selector
    306    test_parseable("[attr]")
    307    test_parseable_via_api("[attr");
    308    test_parseable("[ATTR]")
    309    should_serialize_to("[attr]", "[attr]");
    310    should_serialize_to("[ATTR]", "[ATTR]");
    311 
    312    // Whether we should drop the bar is debatable. This matches Edge
    313    // and Safari at the time of writing.
    314    should_serialize_to("[|attr]", "[attr]");
    315    should_serialize_to("[|ATTR]", "[ATTR]");
    316 
    317    // [attr= ] selector
    318    test_parseable("[attr=\"x\"]");
    319    test_parseable("[attr='x']");
    320    test_parseable("[attr=x]");
    321    test_parseable("[attr=\"\"]");
    322    test_parseable("[attr='']");
    323    test_parseable("[attr=\"foo bar\"]");
    324    test_parseable_via_api("[attr=x");
    325 
    326    test_balanced_unparseable("[attr=]");
    327    test_balanced_unparseable("[attr=foo bar]");
    328 
    329    test_selector_in_html(
    330        '[title=""]',
    331        '<p title=""></p>'
    332        + '<div lang=" "></div><div lang="\t"></div><div lang="\n"></div>',
    333        function(doc) { return doc.getElementsByTagName("p"); },
    334        function(doc) { return doc.getElementsByTagName("div"); }
    335    );
    336 
    337    // [attr~= ] selector
    338    test_parseable("[attr~=\"x\"]");
    339    test_parseable("[attr~='x']");
    340    test_parseable("[attr~=x]");
    341    test_parseable("[attr~=\"\"]");
    342    test_parseable("[attr~='']");
    343    test_parseable("[attr~=\"foo bar\"]");
    344    test_parseable_via_api("[attr~=x");
    345 
    346    test_balanced_unparseable("[attr~=]");
    347    test_balanced_unparseable("[attr~=foo bar]");
    348 
    349    test_selector_in_html(
    350        '[class~="x x"]',
    351        '<div class="x x"></div><div class="x"></div><div class="x\tx"></div>div class="x\nx"></div>',
    352        function(doc) { return []; },
    353        function(doc) { return doc.getElementsByTagName("div"); }
    354    );
    355 
    356    // [attr|="x"]
    357    test_parseable('[attr|="x"]');
    358    test_parseable("[attr|='x']");
    359    test_parseable('[attr|=x]');
    360    test_parseable_via_api("[attr|=x");
    361 
    362    test_parseable('[attr|=""]');
    363    test_parseable("[attr|='']");
    364    test_balanced_unparseable('[attr|=]');
    365 
    366    test_selector_in_html(
    367        '[lang|=""]',
    368        '<p lang=""></p><p lang="-"></p><p lang="-GB"></p>'
    369        + '<div lang="en-GB"></div><div lang="en-"></div>',
    370        function(doc) { return doc.getElementsByTagName("p"); },
    371        function(doc) { return doc.getElementsByTagName("div"); }
    372    );
    373 
    374    // [attr$= ] selector
    375    test_parseable("[attr$=\"x\"]");
    376    test_parseable("[attr$='x']");
    377    test_parseable("[attr$=x]");
    378    test_parseable("[attr$=\"\"]");
    379    test_parseable("[attr$='']");
    380    test_parseable("[attr$=\"foo bar\"]");
    381    test_parseable_via_api("[attr$=x");
    382 
    383    test_balanced_unparseable("[attr$=]");
    384    test_balanced_unparseable("[attr$=foo bar]");
    385 
    386    // [attr^= ] selector
    387    test_parseable("[attr^=\"x\"]");
    388    test_parseable("[attr^='x']");
    389    test_parseable("[attr^=x]");
    390    test_parseable("[attr^=\"\"]");
    391    test_parseable("[attr^='']");
    392    test_parseable("[attr^=\"foo bar\"]");
    393    test_parseable_via_api("[attr^=x");
    394 
    395    test_balanced_unparseable("[attr^=]");
    396    test_balanced_unparseable("[attr^=foo bar]");
    397 
    398    // attr[*= ] selector
    399    test_parseable("[attr*=\"x\"]");
    400    test_parseable("[attr*='x']");
    401    test_parseable("[attr*=x]");
    402    test_parseable("[attr*=\"\"]");
    403    test_parseable("[attr*='']");
    404    test_parseable("[attr*=\"foo bar\"]");
    405    test_parseable_via_api("[attr^=x");
    406 
    407    test_balanced_unparseable("[attr*=]");
    408    test_balanced_unparseable("[attr*=foo bar]");
    409 
    410    // And now tests for correctness of matching of attr selectors.
    411    var attrTestBody =
    412        // Paragraphs 1-5
    413        "<p attr></p> <p attr=''></p> <p attr='foo'></p> <p att></p> <p></p>" +
    414        // Paragraphs 6-8
    415        "<p attr='foo bar'></p> <p attr='foo-bar'></p> <p attr='foobar'></p>" +
    416        // Paragraphs 9-10
    417        "<p attr='foo bar baz'></p> <p attr='foo-bar-baz'></p>" +
    418        // Paragraphs 11-12
    419        "<p attr='foo-bar baz'></p> <p attr='   foo-bar  '></p> " +
    420        // Paragraph 13-15
    421        "<p attr=' foo '></p> <p attr='fo'></p> <p attr='bar baz-foo'></p>";
    422    test_selector_in_html(
    423        "[attr]", attrTestBody,
    424        pset([1,2,3,6,7,8,9,10,11,12,13,14,15]), pset([4,5]));
    425    test_selector_in_html(
    426        "[attr=foo]", attrTestBody,
    427        pset([3]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14,15]));
    428    test_selector_in_html(
    429        "[attr~=foo]", attrTestBody,
    430        pset([3,6,9,13]), pset([1,2,4,5,7,8,10,11,12,14,15]));
    431    test_selector_in_html(
    432        "[attr~=bar]", attrTestBody,
    433        pset([6,9,15]), pset([1,2,3,4,5,7,8,10,11,12,13,14]));
    434    test_selector_in_html(
    435        "[attr~=baz]", attrTestBody,
    436        pset([9,11]), pset([1,2,3,4,5,6,7,8,10,12,13,14,15]));
    437    test_selector_in_html(
    438        "[attr|=foo]", attrTestBody,
    439        pset([3,7,10,11]), pset([1,2,4,5,6,8,9,12,13,14,15]));
    440    test_selector_in_html(
    441        "[attr|='bar baz']", attrTestBody,
    442        pset([15]), pset([1,2,3,4,5,6,7,8,9,10,11,12,13,14]));
    443    test_selector_in_html(
    444        "[attr$=foo]", attrTestBody,
    445        pset([3,15]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14]));
    446    test_selector_in_html(
    447        "[attr$=bar]", attrTestBody,
    448        pset([6,7,8]), pset([1,2,3,4,5,9,10,11,12,13,14,15]));
    449    test_selector_in_html(
    450        "[attr^=foo]", attrTestBody,
    451        pset([3,6,7,8,9,10,11]), pset([1,2,4,5,12,13,14,15]));
    452    test_selector_in_html(
    453        "[attr*=foo]", attrTestBody,
    454        pset([3,6,7,8,9,10,11,12,13,15]), pset([1,2,4,5,14]));
    455 
    456    // Bug 420814
    457    test_selector_in_html(
    458        "div ~ div p",
    459        "<div></div><div><div><p>match</p></div></div>",
    460        function(doc) { return doc.getElementsByTagName("p"); },
    461        function(doc) { return []; }
    462    );
    463 
    464    // Bug 420245
    465    test_selector_in_html(
    466        "p[attr$=\"\"]",
    467        "<p attr=\"foo\">This should not match</p>",
    468        function(doc) { return []; },
    469        function(doc) { return doc.getElementsByTagName("p"); }
    470    );
    471    test_selector_in_html(
    472        "div + p[attr~=\"\"]",
    473        "<div>Dummy</div><p attr=\"foo\">This should not match</p>",
    474        function(doc) { return []; },
    475        function(doc) { return doc.getElementsByTagName("p"); }
    476    );
    477    test_selector_in_html(
    478        "div[attr^=\"\"]",
    479        "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>",
    480        function(doc) { return []; },
    481        function(doc) { return doc.getElementsByTagName("div"); }
    482    );
    483    test_selector_in_html(
    484        "div[attr*=\"\"]",
    485        "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>",
    486        function(doc) { return []; },
    487        function(doc) { return doc.getElementsByTagName("div"); }
    488    );
    489 
    490    // :nth-child(), etc.
    491    // Follow the whitespace rules as proposed in
    492    // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
    493    test_balanced_unparseable(":nth-child()");
    494    test_balanced_unparseable(":nth-of-type( )");
    495    test_parseable(":nth-last-child( odd)");
    496    test_parseable(":nth-last-of-type(even )");
    497    test_parseable(":nth-child(n )");
    498    test_parseable(":nth-of-type( 2n)");
    499    test_parseable(":nth-last-child( -n)");
    500    test_parseable(":nth-last-of-type(-2n )");
    501    test_balanced_unparseable(":nth-child(- n)");
    502    test_balanced_unparseable(":nth-of-type(-2 n)");
    503    test_balanced_unparseable(":nth-last-of-type(2n1)");
    504    test_balanced_unparseable(":nth-child(2n++1)");
    505    test_balanced_unparseable(":nth-of-type(2n-+1)");
    506    test_balanced_unparseable(":nth-last-child(2n+-1)");
    507    test_balanced_unparseable(":nth-last-of-type(2n--1)");
    508    test_parseable(":nth-child( 3n + 1 )");
    509    test_parseable(":nth-child( +3n - 2 )");
    510    test_parseable(":nth-child( -n+ 6)");
    511    test_parseable(":nth-child( +6 )");
    512    test_balanced_unparseable(":nth-child(3 n)");
    513    test_balanced_unparseable(":nth-child(+ 2n)");
    514    test_balanced_unparseable(":nth-child(+ 2)");
    515    test_parseable(":nth-child(3)");
    516    test_parseable(":nth-of-type(-3)");
    517    test_parseable(":nth-last-child(+3)");
    518    test_parseable(":nth-last-of-type(0)");
    519    test_parseable(":nth-child(-0)");
    520    test_parseable(":nth-of-type(3n)");
    521    test_parseable(":nth-last-child(-3n)");
    522    test_parseable(":nth-last-of-type(+3n)");
    523    test_parseable(":nth-last-of-type(0n)");
    524    test_parseable(":nth-child(-0n)");
    525    test_parseable(":nth-of-type(n)");
    526    test_parseable(":nth-last-child(-n)");
    527    test_parseable(":nth-last-of-type(2n+1)");
    528    test_parseable(":nth-child(2n-1)");
    529    test_parseable(":nth-of-type(2n+0)");
    530    test_parseable(":nth-last-child(2n-0)");
    531    test_parseable(":nth-child(-0n+0)");
    532    test_parseable(":nth-of-type(n+1)");
    533    test_parseable(":nth-last-child(n-1)");
    534    test_parseable(":nth-last-of-type(-n+1)");
    535    test_parseable(":nth-child(-n-1)");
    536    test_balanced_unparseable(":nth-child(2-n)");
    537    test_balanced_unparseable(":nth-child(2-n-1)");
    538    test_balanced_unparseable(":nth-child(n-2-1)");
    539    // Bug 750388
    540    test_parseable(":nth-child(+n)");
    541    test_balanced_unparseable(":nth-child(+ n)");
    542    test_parseable(":nth-child(+n+2)");
    543    test_parseable(":nth-child(+n-2)");
    544    test_parseable(":nth-child(+n + 2)");
    545    test_parseable(":nth-child(+n - 2)");
    546    test_balanced_unparseable(":nth-child(+ n+2)");
    547    test_balanced_unparseable(":nth-child(+ n-2)");
    548    test_balanced_unparseable(":nth-child(+ n + 2)");
    549    test_balanced_unparseable(":nth-child(+ n - 2)");
    550    test_parseable(":nth-child(+n-100)");
    551    test_parseable(":nth-child(+n - 100)");
    552    test_balanced_unparseable(":nth-child(+ n-100)");
    553    test_balanced_unparseable(":nth-child(+-n+2)");
    554    test_balanced_unparseable(":nth-child(+ -n+2)");
    555    test_balanced_unparseable(":nth-child(+-n-100)");
    556    test_balanced_unparseable(":nth-child(+ -n-100)");
    557    test_balanced_unparseable(":nth-child(++n-100)");
    558    test_balanced_unparseable(":nth-child(-+n-100)");
    559    test_balanced_unparseable(":nth-child(++2n - 100)");
    560    test_balanced_unparseable(":nth-child(+-2n - 100)");
    561    test_balanced_unparseable(":nth-child(-+2n - 100)");
    562    test_balanced_unparseable(":nth-child(--2n - 100)");
    563    test_balanced_unparseable(":nth-child(+/**/+2n - 100)");
    564    test_balanced_unparseable(":nth-child(+/**/-2n - 100)");
    565    test_balanced_unparseable(":nth-child(-/**/+2n - 100)");
    566    test_balanced_unparseable(":nth-child(-/**/-2n - 100)");
    567    test_balanced_unparseable(":nth-child(+/**/+/**/2n - 100)");
    568    test_balanced_unparseable(":nth-child(+/**/-/**/2n - 100)");
    569    test_balanced_unparseable(":nth-child(-/**/+/**/2n - 100)");
    570    test_balanced_unparseable(":nth-child(-/**/-/**/2n - 100)");
    571    test_balanced_unparseable(":nth-child(++/**/2n - 100)");
    572    test_balanced_unparseable(":nth-child(+-/**/2n - 100)");
    573    test_balanced_unparseable(":nth-child(-+/**/2n - 100)");
    574    test_balanced_unparseable(":nth-child(--/**/2n - 100)");
    575    test_balanced_unparseable(":nth-child(-even)");
    576    test_balanced_unparseable(":nth-child(-odd)");
    577    test_balanced_unparseable(":nth-child(+even)");
    578    test_balanced_unparseable(":nth-child(+odd)");
    579    test_balanced_unparseable(":nth-child(+ even)");
    580    test_balanced_unparseable(":nth-child(+ odd)");
    581    test_balanced_unparseable(":nth-child(+-n)");
    582    test_balanced_unparseable(":nth-child(+-n-)");
    583    test_balanced_unparseable(":nth-child(-+n)");
    584    test_balanced_unparseable(":nth-child(+n--)");
    585    test_parseable(":nth-child(n+2)");
    586    test_parseable(":nth-child(n/**/+/**/2)");
    587    test_parseable(":nth-child(n-2)");
    588    test_parseable(":nth-child(n/**/-/**/2)");
    589    test_balanced_unparseable(":nth-child(n++2)");
    590    test_balanced_unparseable(":nth-child(n+-2)");
    591    test_balanced_unparseable(":nth-child(n-+2)");
    592    test_balanced_unparseable(":nth-child(n--2)");
    593    test_balanced_unparseable(":nth-child(n/**/++2)");
    594    test_balanced_unparseable(":nth-child(n/**/+-2)");
    595    test_balanced_unparseable(":nth-child(n/**/-+2)");
    596    test_balanced_unparseable(":nth-child(n/**/--2)");
    597    test_balanced_unparseable(":nth-child(n/**/+/**/+2)");
    598    test_balanced_unparseable(":nth-child(n/**/+/**/-2)");
    599    test_balanced_unparseable(":nth-child(n/**/-/**/+2)");
    600    test_balanced_unparseable(":nth-child(n/**/-/**/-2)");
    601    test_balanced_unparseable(":nth-child(n+/**/+2)");
    602    test_balanced_unparseable(":nth-child(n+/**/-2)");
    603    test_balanced_unparseable(":nth-child(n-/**/+2)");
    604    test_balanced_unparseable(":nth-child(n-/**/-2)");
    605    test_balanced_unparseable(":nth-child(n++/**/2)");
    606    test_balanced_unparseable(":nth-child(n+-/**/2)");
    607    test_balanced_unparseable(":nth-child(n-+/**/2)");
    608    test_balanced_unparseable(":nth-child(n--/**/2)");
    609    test_balanced_unparseable(":nth-child(n/**/++/**/2)");
    610    test_balanced_unparseable(":nth-child(n/**/+-/**/2)");
    611    test_balanced_unparseable(":nth-child(n/**/-+/**/2)");
    612    test_balanced_unparseable(":nth-child(n/**/--/**/2)");
    613    test_balanced_unparseable(":nth-child(n/**/+/**/+/**/2)");
    614    test_balanced_unparseable(":nth-child(n/**/+/**/-/**/2)");
    615    test_balanced_unparseable(":nth-child(n/**/-/**/+/**/2)");
    616    test_balanced_unparseable(":nth-child(n/**/-/**/-/**/2)");
    617    test_balanced_unparseable(":nth-child(n+/**/+/**/2)");
    618    test_balanced_unparseable(":nth-child(n+/**/-/**/2)");
    619    test_balanced_unparseable(":nth-child(n-/**/+/**/2)");
    620    test_balanced_unparseable(":nth-child(n-/**/-/**/2)");
    621    test_parseable(":nth-child(2n+2)");
    622    test_parseable(":nth-child(2n/**/+/**/2)");
    623    test_parseable(":nth-child(2n-2)");
    624    test_parseable(":nth-child(2n/**/-/**/2)");
    625    test_balanced_unparseable(":nth-child(2n++2)");
    626    test_balanced_unparseable(":nth-child(2n+-2)");
    627    test_balanced_unparseable(":nth-child(2n-+2)");
    628    test_balanced_unparseable(":nth-child(2n--2)");
    629    test_balanced_unparseable(":nth-child(2n/**/++2)");
    630    test_balanced_unparseable(":nth-child(2n/**/+-2)");
    631    test_balanced_unparseable(":nth-child(2n/**/-+2)");
    632    test_balanced_unparseable(":nth-child(2n/**/--2)");
    633    test_balanced_unparseable(":nth-child(2n/**/+/**/+2)");
    634    test_balanced_unparseable(":nth-child(2n/**/+/**/-2)");
    635    test_balanced_unparseable(":nth-child(2n/**/-/**/+2)");
    636    test_balanced_unparseable(":nth-child(2n/**/-/**/-2)");
    637    test_balanced_unparseable(":nth-child(2n+/**/+2)");
    638    test_balanced_unparseable(":nth-child(2n+/**/-2)");
    639    test_balanced_unparseable(":nth-child(2n-/**/+2)");
    640    test_balanced_unparseable(":nth-child(2n-/**/-2)");
    641    test_balanced_unparseable(":nth-child(2n++/**/2)");
    642    test_balanced_unparseable(":nth-child(2n+-/**/2)");
    643    test_balanced_unparseable(":nth-child(2n-+/**/2)");
    644    test_balanced_unparseable(":nth-child(2n--/**/2)");
    645    test_balanced_unparseable(":nth-child(2n/**/++/**/2)");
    646    test_balanced_unparseable(":nth-child(2n/**/+-/**/2)");
    647    test_balanced_unparseable(":nth-child(2n/**/-+/**/2)");
    648    test_balanced_unparseable(":nth-child(2n/**/--/**/2)");
    649    test_balanced_unparseable(":nth-child(2n/**/+/**/+/**/2)");
    650    test_balanced_unparseable(":nth-child(2n/**/+/**/-/**/2)");
    651    test_balanced_unparseable(":nth-child(2n/**/-/**/+/**/2)");
    652    test_balanced_unparseable(":nth-child(2n/**/-/**/-/**/2)");
    653    test_balanced_unparseable(":nth-child(2n+/**/+/**/2)");
    654    test_balanced_unparseable(":nth-child(2n+/**/-/**/2)");
    655    test_balanced_unparseable(":nth-child(2n-/**/+/**/2)");
    656    test_balanced_unparseable(":nth-child(2n-/**/-/**/2)");
    657    test_parseable(":nth-child(+/**/n+2)");
    658    test_parseable(":nth-child(+n/**/+2)");
    659    test_parseable(":nth-child(+n/**/+2)");
    660    test_parseable(":nth-child(+n+/**/2)");
    661    test_parseable(":nth-child(+n+2/**/)");
    662    test_balanced_unparseable(":nth-child(+1/**/n+2)");
    663    test_parseable(":nth-child(+1n/**/+2)");
    664    test_parseable(":nth-child(+1n/**/+2)");
    665    test_parseable(":nth-child(+1n+/**/2)");
    666    test_parseable(":nth-child(+1n+2/**/)");
    667    test_balanced_unparseable(":nth-child(-/**/n+2)");
    668    test_parseable(":nth-child(-n/**/+2)");
    669    test_parseable(":nth-child(-n/**/+2)");
    670    test_parseable(":nth-child(-n+/**/2)");
    671    test_parseable(":nth-child(-n+2/**/)");
    672    test_balanced_unparseable(":nth-child(-1/**/n+2)");
    673    test_parseable(":nth-child(-1n/**/+2)");
    674    test_parseable(":nth-child(-1n/**/+2)");
    675    test_parseable(":nth-child(-1n+/**/2)");
    676    test_parseable(":nth-child(-1n+2/**/)");
    677    test_balanced_unparseable(":nth-child(-/**/ n+2)");
    678    test_balanced_unparseable(":nth-child(- /**/n+2)");
    679    test_balanced_unparseable(":nth-child(+/**/ n+2)");
    680    test_balanced_unparseable(":nth-child(+ /**/n+2)");
    681    test_parseable(":nth-child(+/**/n-2)");
    682    test_parseable(":nth-child(+n/**/-2)");
    683    test_parseable(":nth-child(+n/**/-2)");
    684    test_parseable(":nth-child(+n-/**/2)");
    685    test_parseable(":nth-child(+n-2/**/)");
    686    test_balanced_unparseable(":nth-child(+1/**/n-2)");
    687    test_parseable(":nth-child(+1n/**/-2)");
    688    test_parseable(":nth-child(+1n/**/-2)");
    689    test_parseable(":nth-child(+1n-/**/2)");
    690    test_parseable(":nth-child(+1n-2/**/)");
    691    test_balanced_unparseable(":nth-child(-/**/n-2)");
    692    test_parseable(":nth-child(-n/**/-2)");
    693    test_parseable(":nth-child(-n/**/-2)");
    694    test_parseable(":nth-child(-n-/**/2)");
    695    test_parseable(":nth-child(-n-2/**/)");
    696    test_balanced_unparseable(":nth-child(-1/**/n-2)");
    697    test_parseable(":nth-child(-1n/**/-2)");
    698    test_parseable(":nth-child(-1n/**/-2)");
    699    test_parseable(":nth-child(-1n-/**/2)");
    700    test_parseable(":nth-child(-1n-2/**/)");
    701    test_balanced_unparseable(":nth-child(-/**/ n-2)");
    702    test_balanced_unparseable(":nth-child(- /**/n-2)");
    703    test_balanced_unparseable(":nth-child(+/**/ n-2)");
    704    test_balanced_unparseable(":nth-child(+ /**/n-2)");
    705    test_parseable(":nth-child(+/**/N-2)");
    706    test_parseable(":nth-child(+N/**/-2)");
    707    test_parseable(":nth-child(+N/**/-2)");
    708    test_parseable(":nth-child(+N-/**/2)");
    709    test_parseable(":nth-child(+N-2/**/)");
    710    test_balanced_unparseable(":nth-child(+1/**/N-2)");
    711    test_parseable(":nth-child(+1N/**/-2)");
    712    test_parseable(":nth-child(+1N/**/-2)");
    713    test_parseable(":nth-child(+1N-/**/2)");
    714    test_parseable(":nth-child(+1N-2/**/)");
    715    test_balanced_unparseable(":nth-child(-/**/N-2)");
    716    test_parseable(":nth-child(-N/**/-2)");
    717    test_parseable(":nth-child(-N/**/-2)");
    718    test_parseable(":nth-child(-N-/**/2)");
    719    test_parseable(":nth-child(-N-2/**/)");
    720    test_balanced_unparseable(":nth-child(-1/**/N-2)");
    721    test_parseable(":nth-child(-1N/**/-2)");
    722    test_parseable(":nth-child(-1N/**/-2)");
    723    test_parseable(":nth-child(-1N-/**/2)");
    724    test_parseable(":nth-child(-1N-2/**/)");
    725    test_balanced_unparseable(":nth-child(-/**/ N-2)");
    726    test_balanced_unparseable(":nth-child(- /**/N-2)");
    727    test_balanced_unparseable(":nth-child(+/**/ N-2)");
    728    test_balanced_unparseable(":nth-child(+ /**/N-2)");
    729    test_parseable(":nth-child( +n + 1 )");
    730    test_parseable(":nth-child( +/**/n + 1 )");
    731    test_balanced_unparseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
    732    test_parseable(":nth-child( -2n/**/ + /**/4 )");
    733    test_parseable(":nth-child( -2n/**/+/**/4 )");
    734    test_parseable(":nth-child( -2n  /**/+/**/4 )");
    735    test_balanced_unparseable(":nth-child( -/**/n  /**/+ /**/ 4 )");
    736    test_parseable(":nth-child( +/**/n  /**/+ /**/ 4 )");
    737    test_balanced_unparseable(":nth-child(+1/**/n-1)");
    738    test_balanced_unparseable(":nth-child(1/**/n-1)");
    739    // bug 876570
    740    test_balanced_unparseable(":nth-child(+2n-)");
    741    test_balanced_unparseable(":nth-child(+n-)");
    742    test_balanced_unparseable(":nth-child(-2n-)");
    743    test_balanced_unparseable(":nth-child(-n-)");
    744    test_balanced_unparseable(":nth-child(2n-)");
    745    test_balanced_unparseable(":nth-child(n-)");
    746    test_balanced_unparseable(":nth-child(+2n+)");
    747    test_balanced_unparseable(":nth-child(+n+)");
    748    test_balanced_unparseable(":nth-child(-2n+)");
    749    test_balanced_unparseable(":nth-child(-n+)");
    750    test_balanced_unparseable(":nth-child(2n+)");
    751    test_balanced_unparseable(":nth-child(n+)");
    752 
    753    // exercise the an+b matching logic particularly hard for
    754    // :nth-child() (since we know we use the same code for all 4)
    755    var seven_ps = "<p></p><p></p><p></p><p></p><p></p><p></p><p></p>";
    756    function pset(indices) { // takes an array of 1-based indices
    757        return function pset_filter(doc) {
    758            var a = doc.getElementsByTagName("p");
    759            var result = [];
    760            for (var i in indices)
    761                result.push(a[indices[i] - 1]);
    762            return result;
    763        }
    764    }
    765    test_selector_in_html(":nth-child(0)", seven_ps,
    766                          pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
    767    test_selector_in_html(":nth-child(-3)", seven_ps,
    768                          pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
    769    test_selector_in_html(":nth-child(3)", seven_ps,
    770                          pset([3]), pset([1, 2, 4, 5, 6, 7]));
    771    test_selector_in_html(":nth-child(0n+3)", seven_ps,
    772                          pset([3]), pset([1, 2, 4, 5, 6, 7]));
    773    test_selector_in_html(":nth-child(-0n+3)", seven_ps,
    774                          pset([3]), pset([1, 2, 4, 5, 6, 7]));
    775    test_selector_in_html(":nth-child(8)", seven_ps,
    776                          pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
    777    test_selector_in_html(":nth-child(odd)", seven_ps,
    778                          pset([1, 3, 5, 7]), pset([2, 4, 6]));
    779    test_selector_in_html(":nth-child(even)", seven_ps,
    780                          pset([2, 4, 6]), pset([1, 3, 5, 7]));
    781    test_selector_in_html(":nth-child(2n-1)", seven_ps,
    782                          pset([1, 3, 5, 7]), pset([2, 4, 6]));
    783    test_selector_in_html(":nth-child( 2n - 1 )", seven_ps,
    784                          pset([1, 3, 5, 7]), pset([2, 4, 6]));
    785    test_selector_in_html(":nth-child(2n+1)", seven_ps,
    786                          pset([1, 3, 5, 7]), pset([2, 4, 6]));
    787    test_selector_in_html(":nth-child( 2n + 1 )", seven_ps,
    788                          pset([1, 3, 5, 7]), pset([2, 4, 6]));
    789    test_selector_in_html(":nth-child(2n+0)", seven_ps,
    790                          pset([2, 4, 6]), pset([1, 3, 5, 7]));
    791    test_selector_in_html(":nth-child(2n-0)", seven_ps,
    792                          pset([2, 4, 6]), pset([1, 3, 5, 7]));
    793    test_selector_in_html(":nth-child(-n+3)", seven_ps,
    794                          pset([1, 2, 3]), pset([4, 5, 6, 7]));
    795    test_selector_in_html(":nth-child(-n-3)", seven_ps,
    796                          pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
    797    test_selector_in_html(":nth-child(n)", seven_ps,
    798                          pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
    799    test_selector_in_html(":nth-child(n-3)", seven_ps,
    800                          pset([1, 2, 3, 4, 5, 6, 7]), pset([]));
    801    test_selector_in_html(":nth-child(n+3)", seven_ps,
    802                          pset([3, 4, 5, 6, 7]), pset([1, 2]));
    803    test_selector_in_html(":nth-child(2n+3)", seven_ps,
    804                          pset([3, 5, 7]), pset([1, 2, 4, 6]));
    805    test_selector_in_html(":nth-child(2n)", seven_ps,
    806                          pset([2, 4, 6]), pset([1, 3, 5, 7]));
    807    test_selector_in_html(":nth-child(2n-3)", seven_ps,
    808                          pset([1, 3, 5, 7]), pset([2, 4, 6]));
    809    test_selector_in_html(":nth-child(-1n+3)", seven_ps,
    810                          pset([1, 2, 3]), pset([4, 5, 6, 7]));
    811    test_selector_in_html(":nth-child(-2n+3)", seven_ps,
    812                          pset([1, 3]), pset([2, 4, 5, 6, 7]));
    813    // And a few spot-checks for the other :nth-* selectors
    814    test_selector_in_html(":nth-child(4n+1)", seven_ps,
    815                          pset([1, 5]), pset([2, 3, 4, 6, 7]));
    816    test_selector_in_html(":nth-last-child(4n+1)", seven_ps,
    817                          pset([3, 7]), pset([1, 2, 4, 5, 6]));
    818    test_selector_in_html(":nth-of-type(4n+1)", seven_ps,
    819                          pset([1, 5]), pset([2, 3, 4, 6, 7]));
    820    test_selector_in_html(":nth-last-of-type(4n+1)", seven_ps,
    821                          pset([3, 7]), pset([1, 2, 4, 5, 6]));
    822    test_selector_in_html(":nth-child(6)", seven_ps,
    823                          pset([6]), pset([1, 2, 3, 4, 5, 7]));
    824    test_selector_in_html(":nth-last-child(6)", seven_ps,
    825                          pset([2]), pset([1, 3, 4, 5, 6, 7]));
    826    test_selector_in_html(":nth-of-type(6)", seven_ps,
    827                          pset([6]), pset([1, 2, 3, 4, 5, 7]));
    828    test_selector_in_html(":nth-last-of-type(6)", seven_ps,
    829                          pset([2]), pset([1, 3, 4, 5, 6, 7]));
    830 
    831    // Test [first|last|only]-[child|node|of-type]
    832    var interesting_doc = "<!----> <div id='p1'> <!---->x<p id='s1'></p> <!----><p id='s2'></p> <!----></div> <!----><p id='p2'> <!----><span id='s3'></span> <!----><span id='s4'></span> <!---->x</p> <!----><div id='p3'> <!----><p id='s5'></p> <!----></div> <!---->";
    833    function idset(ids) { // takes an array of ids
    834        return function idset_filter(doc) {
    835            var result = [];
    836            for (var id of ids)
    837                result.push(doc.getElementById(id));
    838            return result;
    839        }
    840    }
    841    function classset(classes) { // takes an array of classes
    842        return function classset_filter(doc) {
    843            var i, j, els;
    844            var result = [];
    845            for (i = 0; i < classes.length; i++) {
    846                els = doc.getElementsByClassName(classes[i]);
    847                for (j = 0; j < els.length; j++) {
    848                    result.push(els[j]);
    849                }
    850            }
    851            return result;
    852        }
    853    }
    854    function emptyset(doc) { return []; }
    855    test_parseable(":first-child");
    856    test_parseable(":last-child");
    857    test_parseable(":only-child");
    858    test_parseable(":-moz-first-node");
    859    test_parseable(":-moz-last-node");
    860    test_parseable(":first-of-type");
    861    test_parseable(":last-of-type");
    862    test_parseable(":only-of-type");
    863    test_selector_in_html(":first-child", seven_ps,
    864                          pset([1]), pset([2, 3, 4, 5, 6, 7]));
    865    test_selector_in_html(":first-child", interesting_doc,
    866                          idset(["p1", "s1", "s3", "s5"]),
    867                          idset(["s2", "p2", "s4", "p3"]));
    868    test_selector_in_html(":-moz-first-node", interesting_doc,
    869                          idset(["p1", "s3", "s5"]),
    870                          idset(["s1", "s2", "p2", "s4", "p3"]));
    871    test_selector_in_html(":last-child", seven_ps,
    872                          pset([7]), pset([1, 2, 3, 4, 5, 6]));
    873    test_selector_in_html(":last-child", interesting_doc,
    874                          idset(["s2", "s4", "p3", "s5"]),
    875                          idset(["p1", "s1", "p2", "s3"]));
    876    test_selector_in_html(":-moz-last-node", interesting_doc,
    877                          idset(["s2", "p3", "s5"]),
    878                          idset(["p1", "s1", "p2", "s3", "s4"]));
    879    test_selector_in_html(":only-child", seven_ps,
    880                          pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
    881    test_selector_in_html(":only-child", interesting_doc,
    882                          idset(["s5"]),
    883                          idset(["p1", "s1", "s2", "p2", "s3", "s4", "p3"]));
    884    test_selector_in_html(":first-of-type", seven_ps,
    885                          pset([1]), pset([2, 3, 4, 5, 6, 7]));
    886    test_selector_in_html(":first-of-type", interesting_doc,
    887                          idset(["p1", "s1", "p2", "s3", "s5"]),
    888                          idset(["s2", "s4", "p3"]));
    889    test_selector_in_html(":last-of-type", seven_ps,
    890                          pset([7]), pset([1, 2, 3, 4, 5, 6]));
    891    test_selector_in_html(":last-of-type", interesting_doc,
    892                          idset(["s2", "p2", "s4", "p3", "s5"]),
    893                          idset(["p1", "s1", "s3"]));
    894    test_selector_in_html(":only-of-type", seven_ps,
    895                          pset([]), pset([1, 2, 3, 4, 5, 6, 7]));
    896    test_selector_in_html(":only-of-type", interesting_doc,
    897                          idset(["p2", "s5"]),
    898                          idset(["p1", "s1", "s2", "s3", "s4", "p3"]));
    899 
    900    // And a bunch of tests for the of-type aspect of :nth-of-type() and
    901    // :nth-last-of-type().  Note that the last div here contains two
    902    // children.
    903    var mixed_elements="<p></p><p></p><div></div><p></p><div><p></p><address></address></div><address></address>";
    904    function pdaset(ps, divs, addresses) { // takes an array of 1-based indices
    905        var l = { p: ps, div: divs, address: addresses };
    906        return function pdaset_filter(doc) {
    907            var result = [];
    908            for (var tag in l) {
    909                var a = doc.getElementsByTagName(tag);
    910                var indices = l[tag];
    911                for (var i in indices)
    912                    result.push(a[indices[i] - 1]);
    913            }
    914            return result;
    915        }
    916    }
    917    test_selector_in_html(":nth-of-type(odd)", mixed_elements,
    918                          pdaset([1, 3, 4], [1], [1, 2]),
    919                          pdaset([2], [2], []));
    920    test_selector_in_html(":nth-of-type(2n-0)", mixed_elements,
    921                          pdaset([2], [2], []),
    922                          pdaset([1, 3, 4], [1], [1, 2]));
    923    test_selector_in_html(":nth-last-of-type(even)", mixed_elements,
    924                          pdaset([2], [1], []),
    925                          pdaset([1, 3, 4], [2], [1, 2]));
    926 
    927    // Test greediness of descendant combinators.
    928    var four_children="<div id='a'><div id='b'><div id='c'><div id='d'><\/div><\/div><\/div><\/div>";
    929    test_selector_in_html("#a > div div", four_children,
    930                          idset(["c", "d"]), idset(["a", "b"]));
    931    test_selector_in_html("#a > #b div", four_children,
    932                          idset(["c", "d"]), idset(["a", "b"]));
    933    test_selector_in_html("#a div > div", four_children,
    934                          idset(["c", "d"]), idset(["a", "b"]));
    935    test_selector_in_html("#a #b > div", four_children,
    936                          idset(["c"]), idset(["a", "b", "d"]));
    937    test_selector_in_html("#a > #b div", four_children,
    938                          idset(["c", "d"]), idset(["a", "b"]));
    939    test_selector_in_html("#a #c > div", four_children,
    940                          idset(["d"]), idset(["a", "b", "c"]));
    941    test_selector_in_html("#a > #c div", four_children,
    942                          idset([]), idset(["a", "b", "c", "d"]));
    943 
    944    // More descendant combinator greediness (bug 511147)
    945    test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div></div>',
    946                         classset(["match"]), classset(["a", "b"]));
    947    test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="x"></div><div class="match"></div></div>',
    948                         classset(["match"]), classset(["a", "b", "x"]));
    949    test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>',
    950                         classset(["match"]), classset(["a", "b", "x"]));
    951    test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="x"><p>filler filler <i>filler</i> filler</p></div><div></div><div class="b"></div><div></div><div class="x"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>',
    952                         classset(["match"]), classset(["a", "b", "x"]));
    953    test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div><div class="match"></div></div>',
    954                         classset(["match"]), classset(["a", "b"]));
    955 
    956    test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div></div>',
    957                         emptyset, classset(["a", "b", "nomatch"]));
    958    test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div><div class="nomatch"></div></div>',
    959                         emptyset, classset(["a", "b", "nomatch"]));
    960    test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div><div><div class="nomatch"></div></div><div></div></div>',
    961                         emptyset, classset(["a", "b", "nomatch"]));
    962    test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div></div><div class="nomatch"></div>',
    963                         emptyset, classset(["a", "b", "nomatch"]));
    964 
    965    // Test serialization of pseudo-elements.
    966    should_serialize_to("p::first-letter", "p::first-letter");
    967    should_serialize_to("p:first-letter", "p::first-letter");
    968    should_serialize_to("div>p:first-letter", "div > p::first-letter");
    969    should_serialize_to("span +div:first-line", "span + div::first-line");
    970    should_serialize_to("input::placeholder", "input::placeholder");
    971    should_serialize_to("input:placeholder-shown", "input:placeholder-shown");
    972 
    973    // Test serialization of ::-moz-placeholder.
    974    should_serialize_to("input::-moz-placeholder", "input::placeholder");
    975 
    976    should_serialize_to(':lang("foo\\"bar")', ':lang(foo\\"bar)');
    977 
    978    // Test default namespaces, including inside :not().
    979    var html_default_ns = "@namespace url(http://www.w3.org/1999/xhtml);";
    980    var html_ns = "@namespace html url(http://www.w3.org/1999/xhtml);";
    981    var xul_default_ns = "@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);";
    982    var single_a = "<a id='a' href='data:text/plain,this_better_be_unvisited'></a>";
    983    var set_single = idset(['a']);
    984    var empty_set = idset([]);
    985    test_selector_in_html("a", single_a, set_single, empty_set,
    986                          html_default_ns);
    987    test_selector_in_html("a", single_a, empty_set, set_single,
    988                          xul_default_ns);
    989    test_selector_in_html("*|a", single_a, set_single, empty_set,
    990                          xul_default_ns);
    991    test_selector_in_html("html|a", single_a, set_single, empty_set,
    992                          xul_default_ns + html_ns);
    993    // Type selectors inside :not() bring in default namespaces, but
    994    // non-type selectors don't.
    995    test_selector_in_html("*|a:not(*)", single_a, set_single, empty_set,
    996                          xul_default_ns);
    997    test_selector_in_html("*|a:not(a)", single_a, set_single, empty_set,
    998                          xul_default_ns);
    999    test_selector_in_html("*|a:not(*|*)", single_a, empty_set, set_single,
   1000                          xul_default_ns);
   1001    test_selector_in_html("*|a:not(*|a)", single_a, empty_set, set_single,
   1002                          xul_default_ns);
   1003    test_selector_in_html("*|a:not(:link)", single_a + "<a id='b'></a>",
   1004                          idset(["b"]), set_single,
   1005                          xul_default_ns);
   1006    test_selector_in_html("*|a:not(:visited)", single_a + "<a id='b'></a>",
   1007                          idset(["a", "b"]), empty_set,
   1008                          xul_default_ns);
   1009    test_selector_in_html("*|a:not(html|*)", single_a, empty_set, set_single,
   1010                          xul_default_ns + html_ns);
   1011    test_selector_in_html("*|a:not(html|a)", single_a, empty_set, set_single,
   1012                          xul_default_ns + html_ns);
   1013    test_selector_in_html("*|a:not(|*)", single_a, set_single, empty_set,
   1014                          xul_default_ns + html_ns);
   1015    test_selector_in_html("*|a:not(|a)", single_a, set_single, empty_set,
   1016                          xul_default_ns + html_ns);
   1017    test_selector_in_html("html|a:not(|*)", single_a, set_single, empty_set,
   1018                          xul_default_ns + html_ns);
   1019    test_selector_in_html("html|a:not(|a)", single_a, set_single, empty_set,
   1020                          xul_default_ns + html_ns);
   1021    test_selector_in_html("html|a:not(*|*)", single_a, empty_set, set_single,
   1022                          xul_default_ns + html_ns);
   1023    test_selector_in_html("html|a:not(*|a)", single_a, empty_set, set_single,
   1024                          xul_default_ns + html_ns);
   1025 
   1026    // Test -moz-locale-dir
   1027    test_balanced_unparseable(":-moz-locale-dir(ltr)");
   1028    test_balanced_unparseable(":-moz-locale-dir(rtl)");
   1029    test_balanced_unparseable(":-moz-locale-dir(rTl)");
   1030    test_balanced_unparseable(":-moz-locale-dir(LTR)");
   1031    test_balanced_unparseable(":-moz-locale-dir(other)");
   1032 
   1033    test_balanced_unparseable(":-moz-locale-dir()");
   1034    test_balanced_unparseable(":-moz-locale-dir(())");
   1035    test_balanced_unparseable(":-moz-locale-dir(3())");
   1036    test_balanced_unparseable(":-moz-locale-dir(f{})");
   1037    test_balanced_unparseable(":-moz-locale-dir('ltr')");
   1038    test_balanced_unparseable(":-moz-locale-dir(ltr, other)");
   1039    test_balanced_unparseable(":-moz-locale-dir(ltr other)");
   1040    test_balanced_unparseable(":-moz-locale-dir");
   1041 
   1042    // Test :dir()
   1043    test_parseable(":dir(ltr)");
   1044    test_parseable(":dir(rtl)");
   1045    test_parseable(":dir(rTl)");
   1046    test_parseable(":dir(LTR)");
   1047    test_parseable(":dir(other)");
   1048    if (document.body.matches(":dir(ltr)")) {
   1049        test_selector_in_html("a:dir(LTr)", single_a,
   1050                              set_single, empty_set);
   1051        test_selector_in_html("a:dir(ltR)", single_a,
   1052                              set_single, empty_set);
   1053        test_selector_in_html("a:dir(LTR)", single_a,
   1054                              set_single, empty_set);
   1055        test_selector_in_html("a:dir(RTl)", single_a,
   1056                              empty_set, set_single);
   1057    } else {
   1058        test_selector_in_html("a:dir(RTl)", single_a,
   1059                              set_single, empty_set);
   1060        test_selector_in_html("a:dir(rtL)", single_a,
   1061                              set_single, empty_set);
   1062        test_selector_in_html("a:dir(RTL)", single_a,
   1063                              set_single, empty_set);
   1064        test_selector_in_html("a:dir(LTr)", single_a,
   1065                              empty_set, set_single);
   1066    }
   1067    test_selector_in_html("a:dir(other)", single_a,
   1068                          empty_set, set_single);
   1069 
   1070    test_balanced_unparseable(":dir()");
   1071    test_balanced_unparseable(":dir(())");
   1072    test_balanced_unparseable(":dir(3())");
   1073    test_balanced_unparseable(":dir(f{})");
   1074    test_balanced_unparseable(":dir('ltr')");
   1075    test_balanced_unparseable(":dir(ltr, other)");
   1076    test_balanced_unparseable(":dir(ltr other)");
   1077    test_balanced_unparseable(":dir");
   1078 
   1079    // Test chrome-only -moz-lwtheme
   1080    test_balanced_unparseable(":-moz-broken");
   1081 
   1082    test_balanced_unparseable(":-moz-tree-row(selected)");
   1083    test_balanced_unparseable("::-moz-tree-row(selected)");
   1084    test_balanced_unparseable("::-MoZ-trEE-RoW(sElEcTeD)");
   1085    test_balanced_unparseable(":-moz-tree-row(selected focus)");
   1086    test_balanced_unparseable(":-moz-tree-row(selected , focus)");
   1087    test_balanced_unparseable("::-moz-tree-row(selected ,focus)");
   1088    test_balanced_unparseable(":-moz-tree-row(selected, focus)");
   1089    test_balanced_unparseable("::-moz-tree-row(selected,focus)");
   1090    test_balanced_unparseable(":-moz-tree-row(selected     focus)");
   1091    test_balanced_unparseable("::-moz-tree-row(selected    ,   focus)");
   1092    test_balanced_unparseable("::-moz-tree-twisty(  hover open  )");
   1093    test_balanced_unparseable("::-moz-tree-row(selected {[]} )");
   1094    test_balanced_unparseable(":-moz-tree-twisty(open())");
   1095    test_balanced_unparseable("::-moz-tree-twisty(hover ())");
   1096 
   1097    test_parseable(":-moz-window-inactive");
   1098    test_parseable("div p:-moz-window-inactive:hover span");
   1099 
   1100    // Chrome-only
   1101    test_unbalanced_unparseable(":-moz-browser-frame");
   1102 
   1103    // Plugin pseudoclasses are chrome-only:
   1104    test_unbalanced_unparseable(":-moz-type-unsupported");
   1105    test_unbalanced_unparseable(":-moz-type-unsupported-platform");
   1106    test_unbalanced_unparseable(":-moz-handler-clicktoplay");
   1107    test_unbalanced_unparseable(":-moz-handler-vulnerable-updatable");
   1108    test_unbalanced_unparseable(":-moz-handler-vulnerable-no-update");
   1109    test_unbalanced_unparseable(":-moz-handler-disabled");
   1110    test_unbalanced_unparseable(":-moz-handler-blocked");
   1111    test_unbalanced_unparseable(":-moz-handler-crashed");
   1112 
   1113    // We're not in a UA sheet, so this should be invalid.
   1114    test_balanced_unparseable(":-moz-inert");
   1115    test_balanced_unparseable(":-moz-native-anonymous");
   1116    test_balanced_unparseable(":-moz-table-border-nonzero");
   1117 
   1118    // Case sensitivity of tag selectors
   1119    function setup_cased_spans(body) {
   1120      var data = [
   1121        { tag: "span" },
   1122        { tag: "sPaN" },
   1123        { tag: "Span" },
   1124        { tag: "SPAN" },
   1125        { ns: "http://www.w3.org/1999/xhtml", tag: "span" },
   1126        { ns: "http://www.w3.org/1999/xhtml", tag: "sPaN" },
   1127        { ns: "http://www.w3.org/1999/xhtml", tag: "Span" },
   1128        { ns: "http://www.w3.org/1999/xhtml", tag: "SPAN" },
   1129        { ns: "http://example.com/useless", tag: "span" },
   1130        { ns: "http://example.com/useless", tag: "sPaN" },
   1131        { ns: "http://example.com/useless", tag: "Span" },
   1132        { ns: "http://example.com/useless", tag: "SPAN" },
   1133      ]
   1134      for (var i in data) {
   1135        var ent = data[i];
   1136        var elem;
   1137        if ("ns" in ent) {
   1138          elem = body.ownerDocument.createElementNS(ent.ns, ent.tag);
   1139        } else {
   1140          elem = body.ownerDocument.createElement(ent.tag);
   1141        }
   1142        body.appendChild(elem);
   1143      }
   1144    }
   1145    function bodychildset(indices) {
   1146      return function bodychildset_filter(doc) {
   1147        var body = doc.body;
   1148        var result = [];
   1149        for (var i in indices) {
   1150          result.push(body.childNodes[indices[i]]);
   1151        }
   1152        return result;
   1153      }
   1154    }
   1155    test_selector_in_html("span", setup_cased_spans,
   1156                          bodychildset([0, 1, 2, 3, 4, 8]),
   1157                          bodychildset([5, 6, 7, 9, 10, 11]));
   1158    test_selector_in_html("sPaN", setup_cased_spans,
   1159                          bodychildset([0, 1, 2, 3, 4, 9]),
   1160                          bodychildset([5, 6, 7, 8, 10, 11]));
   1161    test_selector_in_html("Span", setup_cased_spans,
   1162                          bodychildset([0, 1, 2, 3, 4, 10]),
   1163                          bodychildset([5, 6, 7, 8, 9, 11]));
   1164    test_selector_in_html("SPAN", setup_cased_spans,
   1165                          bodychildset([0, 1, 2, 3, 4, 11]),
   1166                          bodychildset([5, 6, 7, 8, 9, 10]));
   1167 
   1168    // bug 528096 (tree pseudos)
   1169    test_unbalanced_unparseable(":-moz-tree-column((){} a");
   1170    test_unbalanced_unparseable(":-moz-tree-column(x(){} a");
   1171    test_unbalanced_unparseable(":-moz-tree-column(a b (){} a");
   1172    test_unbalanced_unparseable(":-moz-tree-column(a, b (){} a");
   1173 
   1174    // Bug 543428 (escaping)
   1175    test_selector_in_html("\\32|a", single_a, set_single, empty_set,
   1176                          "@namespace \\32  url(http://www.w3.org/1999/xhtml);");
   1177    test_selector_in_html("-\\32|a", single_a, set_single, empty_set,
   1178                          "@namespace -\\32  url(http://www.w3.org/1999/xhtml);");
   1179    test_selector_in_html("\\2|a", single_a, set_single, empty_set,
   1180                          "@namespace \\0002  url(http://www.w3.org/1999/xhtml);");
   1181    test_selector_in_html("-\\2|a", single_a, set_single, empty_set,
   1182                          "@namespace -\\000002  url(http://www.w3.org/1999/xhtml);");
   1183    var spans = "<span class='2'></span><span class='&#x2;'></span>" +
   1184                "<span id='2'></span><span id='&#x2;'></span>"
   1185    test_selector_in_html(".\\32", spans,
   1186                          bodychildset([0]), bodychildset([1, 2, 3]));
   1187    test_selector_in_html("[class=\\32]", spans,
   1188                          bodychildset([0]), bodychildset([1, 2, 3]));
   1189    test_selector_in_html(".\\2", spans,
   1190                          bodychildset([1]), bodychildset([0, 2, 3]));
   1191    test_selector_in_html("[class=\\2]", spans,
   1192                          bodychildset([1]), bodychildset([0, 2, 3]));
   1193    test_selector_in_html("#\\32", spans,
   1194                          bodychildset([2]), bodychildset([0, 1, 3]));
   1195    test_selector_in_html("[id=\\32]", spans,
   1196                          bodychildset([2]), bodychildset([0, 1, 3]));
   1197    test_selector_in_html("#\\2", spans,
   1198                          bodychildset([3]), bodychildset([0, 1, 2]));
   1199    test_selector_in_html("[id=\\2]", spans,
   1200                          bodychildset([3]), bodychildset([0, 1, 2]));
   1201    test_balanced_unparseable("#2");
   1202 
   1203    // Bug 553805:  :not() containing nothing is forbidden
   1204    test_balanced_unparseable(":not()");
   1205    test_balanced_unparseable(":not( )");
   1206    test_balanced_unparseable(":not( \t\n )");
   1207    test_balanced_unparseable(":not(/*comment*/)");
   1208    test_balanced_unparseable(":not( /*comment*/  /* comment */  )");
   1209    test_balanced_unparseable("p :not()");
   1210    test_balanced_unparseable("p :not( )");
   1211    test_balanced_unparseable("p :not( \t\n )");
   1212    test_balanced_unparseable("p :not(/*comment*/)");
   1213    test_balanced_unparseable("p :not( /*comment*/  /* comment */  )");
   1214    test_balanced_unparseable("p:not()");
   1215    test_balanced_unparseable("p:not( )");
   1216    test_balanced_unparseable("p:not( \t\n )");
   1217    test_balanced_unparseable("p:not(/*comment*/)");
   1218    test_balanced_unparseable("p:not( /*comment*/  /* comment */  )");
   1219 
   1220    test_balanced_unparseable(":not(:nth-child(2k))");
   1221    test_balanced_unparseable(":not(:nth-child(()))");
   1222 
   1223    // Bug 1685621 - Serialization of :not()
   1224    should_serialize_to(":not([disabled][selected])", ":not([disabled][selected])");
   1225    should_serialize_to(":not([disabled],[selected])", ":not([disabled], [selected])");
   1226 
   1227    // :-moz-any()
   1228    test_parseable(":-moz-any()");
   1229    test_parseable(":-moz-any('foo')");
   1230    test_parseable(":-moz-any(div p)");
   1231    test_parseable(":-moz-any(div ~ p)");
   1232    test_parseable(":-moz-any(div~p)");
   1233    test_parseable(":-moz-any(div + p)");
   1234    test_parseable(":-moz-any(div+p)");
   1235    test_parseable(":-moz-any(div > p)");
   1236    test_parseable(":-moz-any(div>p)");
   1237    test_parseable(":-moz-any(div, p)");
   1238    test_parseable(":-moz-any( div , p  )");
   1239    test_parseable(":-moz-any(div,p)");
   1240    test_parseable(":-moz-any(div)");
   1241    test_parseable(":-moz-any(div,p,:link,span:focus)");
   1242    test_parseable(":-moz-any(:active,:focus)");
   1243    test_parseable(":-moz-any(:active,:link:focus)");
   1244    test_parseable(":-moz-any(div,:nonexistentpseudo)");
   1245    var any_elts = "<input type='text'><a href='http://www.example.com/'></a><div></div><a name='foo'>";
   1246    test_selector_in_html(":-moz-any(a,input)", any_elts,
   1247                          bodychildset([0, 1, 3]), bodychildset([2]));
   1248    test_selector_in_html(":-moz-any(:link,:not(a))", any_elts,
   1249                          bodychildset([0, 1, 2]), bodychildset([3]));
   1250    test_selector_in_html(":-moz-any([href],input[type],input[name])", any_elts,
   1251                          bodychildset([0, 1]), bodychildset([2, 3]));
   1252    test_selector_in_html(":-moz-any(div,a):-moz-any([type],[href],[name])",
   1253                          any_elts,
   1254                          bodychildset([1, 3]), bodychildset([0, 2]));
   1255 
   1256    // Test that we don't tokenize an empty HASH.
   1257    test_balanced_unparseable("#");
   1258    test_balanced_unparseable("# ");
   1259    test_balanced_unparseable("#, p");
   1260    test_balanced_unparseable("# , p");
   1261    test_balanced_unparseable("p #");
   1262    test_balanced_unparseable("p # ");
   1263    test_balanced_unparseable("p #, p");
   1264    test_balanced_unparseable("p # , p");
   1265 
   1266    // Test that a backslash alone at EOF outside of a string is treated
   1267    // as U+FFFD.
   1268    test_parseable_via_api("#a\\");
   1269    test_parseable_via_api("#\\");
   1270    test_parseable_via_api("\\");
   1271 
   1272    // Test that newline escapes are only supported in strings.
   1273    test_balanced_unparseable("di\\\nv");
   1274    test_balanced_unparseable("div \\\n p");
   1275    test_balanced_unparseable("div\\\n p");
   1276    test_balanced_unparseable("div \\\np");
   1277    test_balanced_unparseable("div\\\np");
   1278 
   1279    // Test that :-moz-placeholder is parsable.
   1280    test_parseable(":-moz-placeholder");
   1281 
   1282    // Test that things other than user-action pseudo-classes are
   1283    // rejected after pseudo-elements.  Some of these tests rely on
   1284    // using a pseudo-element that supports a user-action pseudo-class
   1285    // after it, so we need to use the prefixed ::-moz-color-swatch,
   1286    // which is one of the ones with
   1287    // CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (none of which are
   1288    // unprefixed).
   1289    test_parseable("::-moz-color-swatch:hover");
   1290    test_parseable("::-moz-color-swatch:is(:hover)");
   1291    test_parseable("::-moz-color-swatch:not(:hover)");
   1292    test_parseable("::-moz-color-swatch:where(:hover)");
   1293    test_balanced_unparseable("::-moz-color-swatch:not(.foo)");
   1294    test_balanced_unparseable("::-moz-color-swatch:first-child");
   1295    test_balanced_unparseable("::-moz-color-swatch:host");
   1296    test_balanced_unparseable("::-moz-color-swatch:host(div)");
   1297    test_balanced_unparseable("::-moz-color-swatch:nth-child(1)");
   1298    test_balanced_unparseable("::-moz-color-swatch:hover#foo");
   1299    test_balanced_unparseable(".foo::after:not(.bar) ~ h3");
   1300 
   1301    for (let selector of [
   1302      "::-moz-color-swatch:where(.foo)",
   1303      "::-moz-color-swatch:is(.foo)",
   1304      "::-moz-color-swatch:where(p, :hover)",
   1305      "::-moz-color-swatch:is(p, :hover)",
   1306    ]) {
   1307      test_parseable(selector);
   1308      should_serialize_to(selector, selector);
   1309      ok(!CSS.supports(`selector(${selector})`), "supports should report false for forging selector parse failure");
   1310    }
   1311 
   1312    run_deferred_tests();
   1313 }
   1314 
   1315 var deferred_tests = [];
   1316 
   1317 function defer_clonedoc_tests(docurl, onloadfunc)
   1318 {
   1319    deferred_tests.push( { docurl: docurl, onloadfunc: onloadfunc } );
   1320 }
   1321 
   1322 function run_deferred_tests()
   1323 {
   1324    if (deferred_tests.length == 0) {
   1325        SimpleTest.finish();
   1326        return;
   1327    }
   1328 
   1329    cloneiframe.onload = deferred_tests_onload;
   1330    cloneiframe.src = deferred_tests[0].docurl;
   1331 }
   1332 
   1333 function deferred_tests_onload(event)
   1334 {
   1335    if (event.target != cloneiframe)
   1336        return;
   1337 
   1338    deferred_tests[0].onloadfunc();
   1339    deferred_tests.shift();
   1340 
   1341    run_deferred_tests();
   1342 }
   1343 
   1344 </script>
   1345 </pre>
   1346 </body>
   1347 </html>