tor-browser

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

test_parseDeclarations.js (36029B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const {
      7  parseDeclarations,
      8  _parseCommentDeclarations,
      9  parseNamedDeclarations,
     10 } = require("resource://devtools/shared/css/parsing-utils.js");
     11 const {
     12  isCssPropertyKnown,
     13 } = require("resource://devtools/server/actors/css-properties.js");
     14 
     15 const TEST_DATA = [
     16  // Simple test
     17  {
     18    input: "p:v;",
     19    expected: [
     20      {
     21        name: "p",
     22        value: "v",
     23        priority: "",
     24        offsets: [0, 4],
     25        declarationText: "p:v;",
     26      },
     27    ],
     28  },
     29  // Simple test
     30  {
     31    input: "this:is;a:test;",
     32    expected: [
     33      {
     34        name: "this",
     35        value: "is",
     36        priority: "",
     37        offsets: [0, 8],
     38        declarationText: "this:is;",
     39      },
     40      {
     41        name: "a",
     42        value: "test",
     43        priority: "",
     44        offsets: [8, 15],
     45        declarationText: "a:test;",
     46      },
     47    ],
     48  },
     49  // Test a single declaration with semi-colon
     50  {
     51    input: "name:value;",
     52    expected: [
     53      {
     54        name: "name",
     55        value: "value",
     56        priority: "",
     57        offsets: [0, 11],
     58        declarationText: "name:value;",
     59      },
     60    ],
     61  },
     62  // Test a single declaration without semi-colon
     63  {
     64    input: "name:value",
     65    expected: [
     66      {
     67        name: "name",
     68        value: "value",
     69        priority: "",
     70        offsets: [0, 10],
     71        declarationText: "name:value",
     72      },
     73    ],
     74  },
     75  // Test multiple declarations separated by whitespaces and carriage
     76  // returns and tabs
     77  {
     78    input: "p1 : v1 ; \t\t  \n p2:v2;   \n\n\n\n\t  p3    :   v3;",
     79    expected: [
     80      {
     81        name: "p1",
     82        value: "v1",
     83        priority: "",
     84        offsets: [0, 9],
     85        declarationText: "p1 : v1 ;",
     86      },
     87      {
     88        name: "p2",
     89        value: "v2",
     90        priority: "",
     91        offsets: [16, 22],
     92        declarationText: "p2:v2;",
     93      },
     94      {
     95        name: "p3",
     96        value: "v3",
     97        priority: "",
     98        offsets: [32, 45],
     99        declarationText: "p3    :   v3;",
    100      },
    101    ],
    102  },
    103  // Test simple priority
    104  {
    105    input: "p1: v1; p2: v2 !important;",
    106    expected: [
    107      {
    108        name: "p1",
    109        value: "v1",
    110        priority: "",
    111        offsets: [0, 7],
    112        declarationText: "p1: v1;",
    113      },
    114      {
    115        name: "p2",
    116        value: "v2",
    117        priority: "important",
    118        offsets: [8, 26],
    119        declarationText: "p2: v2 !important;",
    120      },
    121    ],
    122  },
    123  // Test simple priority
    124  {
    125    input: "p1: v1 !important; p2: v2",
    126    expected: [
    127      {
    128        name: "p1",
    129        value: "v1",
    130        priority: "important",
    131        offsets: [0, 18],
    132        declarationText: "p1: v1 !important;",
    133      },
    134      {
    135        name: "p2",
    136        value: "v2",
    137        priority: "",
    138        offsets: [19, 25],
    139        declarationText: "p2: v2",
    140      },
    141    ],
    142  },
    143  // Test simple priority
    144  {
    145    input: "p1: v1 !  important; p2: v2 ! important;",
    146    expected: [
    147      {
    148        name: "p1",
    149        value: "v1",
    150        priority: "important",
    151        offsets: [0, 20],
    152        declarationText: "p1: v1 !  important;",
    153      },
    154      {
    155        name: "p2",
    156        value: "v2",
    157        priority: "important",
    158        offsets: [21, 40],
    159        declarationText: "p2: v2 ! important;",
    160      },
    161    ],
    162  },
    163  // Test simple priority
    164  {
    165    input: "p1: v1 !/*comment*/important;",
    166    expected: [
    167      {
    168        name: "p1",
    169        value: "v1",
    170        priority: "important",
    171        offsets: [0, 29],
    172        declarationText: "p1: v1 !/*comment*/important;",
    173      },
    174    ],
    175  },
    176  // Test priority without terminating ";".
    177  {
    178    input: "p1: v1 !important",
    179    expected: [
    180      {
    181        name: "p1",
    182        value: "v1",
    183        priority: "important",
    184        offsets: [0, 17],
    185        declarationText: "p1: v1 !important",
    186      },
    187    ],
    188  },
    189  // Test trailing "!" without terminating ";".
    190  {
    191    input: "p1: v1 !",
    192    expected: [
    193      {
    194        name: "p1",
    195        value: "v1 !",
    196        priority: "",
    197        offsets: [0, 8],
    198        declarationText: "p1: v1 !",
    199      },
    200    ],
    201  },
    202  // Test invalid priority
    203  {
    204    input: "p1: v1 important;",
    205    expected: [
    206      {
    207        name: "p1",
    208        value: "v1 important",
    209        priority: "",
    210        offsets: [0, 17],
    211        declarationText: "p1: v1 important;",
    212      },
    213    ],
    214  },
    215  // Test invalid priority (in the middle of the declaration).
    216  // See bug 1462553.
    217  {
    218    input: "p1: v1 !important v2;",
    219    expected: [
    220      {
    221        name: "p1",
    222        value: "v1 !important v2",
    223        priority: "",
    224        offsets: [0, 21],
    225        declarationText: "p1: v1 !important v2;",
    226      },
    227    ],
    228  },
    229  {
    230    input: "p1: v1 !    important v2;",
    231    expected: [
    232      {
    233        name: "p1",
    234        value: "v1 ! important v2",
    235        priority: "",
    236        offsets: [0, 25],
    237        declarationText: "p1: v1 !    important v2;",
    238      },
    239    ],
    240  },
    241  {
    242    input: "p1: v1 !  /*comment*/  important v2;",
    243    expected: [
    244      {
    245        name: "p1",
    246        value: "v1 ! important v2",
    247        priority: "",
    248        offsets: [0, 36],
    249        declarationText: "p1: v1 !  /*comment*/  important v2;",
    250      },
    251    ],
    252  },
    253  {
    254    input: "p1: v1 !/*hi*/important v2;",
    255    expected: [
    256      {
    257        name: "p1",
    258        value: "v1 ! important v2",
    259        priority: "",
    260        offsets: [0, 27],
    261        declarationText: "p1: v1 !/*hi*/important v2;",
    262      },
    263    ],
    264  },
    265  // Test various types of background-image urls
    266  {
    267    input: "background-image: url(../../relative/image.png)",
    268    expected: [
    269      {
    270        name: "background-image",
    271        value: "url(../../relative/image.png)",
    272        priority: "",
    273        offsets: [0, 47],
    274        declarationText: "background-image: url(../../relative/image.png)",
    275      },
    276    ],
    277  },
    278  {
    279    input: "background-image: url(http://site.com/test.png)",
    280    expected: [
    281      {
    282        name: "background-image",
    283        value: "url(http://site.com/test.png)",
    284        priority: "",
    285        offsets: [0, 47],
    286        declarationText: "background-image: url(http://site.com/test.png)",
    287      },
    288    ],
    289  },
    290  {
    291    input: "background-image: url(wow.gif)",
    292    expected: [
    293      {
    294        name: "background-image",
    295        value: "url(wow.gif)",
    296        priority: "",
    297        offsets: [0, 30],
    298        declarationText: "background-image: url(wow.gif)",
    299      },
    300    ],
    301  },
    302  // Test that urls with :;{} characters in them are parsed correctly
    303  {
    304    input:
    305      'background: red url("http://site.com/image{}:;.png?id=4#wat") ' +
    306      "repeat top right",
    307    expected: [
    308      {
    309        name: "background",
    310        value:
    311          'red url("http://site.com/image{}:;.png?id=4#wat") ' +
    312          "repeat top right",
    313        priority: "",
    314        offsets: [0, 78],
    315        declarationText:
    316          'background: red url("http://site.com/image{}:;.png?id=4#wat") ' +
    317          "repeat top right",
    318      },
    319    ],
    320  },
    321  // Test that an empty string results in an empty array
    322  { input: "", expected: [] },
    323  // Test that a string comprised only of whitespaces results in an empty array
    324  { input: "       \n \n   \n   \n \t  \t\t\t  ", expected: [] },
    325  // Test that a null input throws an exception
    326  { input: null, throws: true },
    327  // Test that a undefined input throws an exception
    328  { input: undefined, throws: true },
    329  // Test that :;{} characters in quoted content are not parsed as multiple
    330  // declarations
    331  {
    332    input: 'content: ";color:red;}selector{color:yellow;"',
    333    expected: [
    334      {
    335        name: "content",
    336        value: '";color:red;}selector{color:yellow;"',
    337        priority: "",
    338        offsets: [0, 45],
    339        declarationText: 'content: ";color:red;}selector{color:yellow;"',
    340      },
    341    ],
    342  },
    343  // Test that rules aren't parsed, just declarations.
    344  {
    345    input: "body {color:red;} p {color: blue;}",
    346    expected: [],
    347  },
    348  // Test unbalanced : and ;
    349  {
    350    input: "color :red : font : arial;",
    351    expected: [
    352      {
    353        name: "color",
    354        value: "red : font : arial",
    355        priority: "",
    356        offsets: [0, 26],
    357      },
    358    ],
    359  },
    360  {
    361    input: "background: red;;;;;",
    362    expected: [
    363      {
    364        name: "background",
    365        value: "red",
    366        priority: "",
    367        offsets: [0, 16],
    368        declarationText: "background: red;",
    369      },
    370    ],
    371  },
    372  {
    373    input: "background:;",
    374    expected: [
    375      { name: "background", value: "", priority: "", offsets: [0, 12] },
    376    ],
    377  },
    378  { input: ";;;;;", expected: [] },
    379  { input: ":;:;", expected: [] },
    380  // Test name only
    381  {
    382    input: "color",
    383    expected: [{ name: "color", value: "", priority: "", offsets: [0, 5] }],
    384  },
    385  // Test trailing name without :
    386  {
    387    input: "color:blue;font",
    388    expected: [
    389      {
    390        name: "color",
    391        value: "blue",
    392        priority: "",
    393        offsets: [0, 11],
    394        declarationText: "color:blue;",
    395      },
    396      { name: "font", value: "", priority: "", offsets: [11, 15] },
    397    ],
    398  },
    399  // Test trailing name with :
    400  {
    401    input: "color:blue;font:",
    402    expected: [
    403      {
    404        name: "color",
    405        value: "blue",
    406        priority: "",
    407        offsets: [0, 11],
    408        declarationText: "color:blue;",
    409      },
    410      { name: "font", value: "", priority: "", offsets: [11, 16] },
    411    ],
    412  },
    413  // Test leading value
    414  {
    415    input: "Arial;color:blue;",
    416    expected: [
    417      { name: "", value: "Arial", priority: "", offsets: [0, 6] },
    418      {
    419        name: "color",
    420        value: "blue",
    421        priority: "",
    422        offsets: [6, 17],
    423        declarationText: "color:blue;",
    424      },
    425    ],
    426  },
    427  // Test hex colors
    428  {
    429    input: "color: #333",
    430    expected: [
    431      {
    432        name: "color",
    433        value: "#333",
    434        priority: "",
    435        offsets: [0, 11],
    436        declarationText: "color: #333",
    437      },
    438    ],
    439  },
    440  {
    441    input: "color: #456789",
    442    expected: [
    443      {
    444        name: "color",
    445        value: "#456789",
    446        priority: "",
    447        offsets: [0, 14],
    448        declarationText: "color: #456789",
    449      },
    450    ],
    451  },
    452  {
    453    input: "wat: #XYZ",
    454    expected: [
    455      {
    456        name: "wat",
    457        value: "#XYZ",
    458        priority: "",
    459        offsets: [0, 9],
    460        declarationText: "wat: #XYZ",
    461      },
    462    ],
    463  },
    464  // Test string/url quotes escaping
    465  {
    466    input: "content: \"this is a 'string'\"",
    467    expected: [
    468      {
    469        name: "content",
    470        value: "\"this is a 'string'\"",
    471        priority: "",
    472        offsets: [0, 29],
    473        declarationText: "content: \"this is a 'string'\"",
    474      },
    475    ],
    476  },
    477  {
    478    input: 'content: "this is a \\"string\\""',
    479    expected: [
    480      {
    481        name: "content",
    482        value: '"this is a \\"string\\""',
    483        priority: "",
    484        offsets: [0, 31],
    485        declarationText: 'content: "this is a \\"string\\""',
    486      },
    487    ],
    488  },
    489  {
    490    input: "content: 'this is a \"string\"'",
    491    expected: [
    492      {
    493        name: "content",
    494        value: "'this is a \"string\"'",
    495        priority: "",
    496        offsets: [0, 29],
    497        declarationText: "content: 'this is a \"string\"'",
    498      },
    499    ],
    500  },
    501  {
    502    input: "content: 'this is a \\'string\\''",
    503    expected: [
    504      {
    505        name: "content",
    506        value: "'this is a \\'string\\''",
    507        priority: "",
    508        offsets: [0, 31],
    509        declarationText: "content: 'this is a \\'string\\''",
    510      },
    511    ],
    512  },
    513  {
    514    input: "content: 'this \\' is a \" really strange string'",
    515    expected: [
    516      {
    517        name: "content",
    518        value: "'this \\' is a \" really strange string'",
    519        priority: "",
    520        offsets: [0, 47],
    521        declarationText: "content: 'this \\' is a \" really strange string'",
    522      },
    523    ],
    524  },
    525  {
    526    input: 'content: "a not s\\          o very long title"',
    527    expected: [
    528      {
    529        name: "content",
    530        value: '"a not s\\          o very long title"',
    531        priority: "",
    532        offsets: [0, 46],
    533        declarationText: 'content: "a not s\\          o very long title"',
    534      },
    535    ],
    536  },
    537  // Test calc with nested parentheses
    538  {
    539    input: "width: calc((100% - 3em) / 2)",
    540    expected: [
    541      {
    542        name: "width",
    543        value: "calc((100% - 3em) / 2)",
    544        priority: "",
    545        offsets: [0, 29],
    546        declarationText: "width: calc((100% - 3em) / 2)",
    547      },
    548    ],
    549  },
    550 
    551  // Simple embedded comment test.
    552  {
    553    parseComments: true,
    554    input: "width: 5; /* background: green; */ background: red;",
    555    expected: [
    556      {
    557        name: "width",
    558        value: "5",
    559        priority: "",
    560        offsets: [0, 9],
    561        declarationText: "width: 5;",
    562      },
    563      {
    564        name: "background",
    565        value: "green",
    566        priority: "",
    567        offsets: [13, 31],
    568        declarationText: "background: green;",
    569        commentOffsets: [10, 34],
    570      },
    571      {
    572        name: "background",
    573        value: "red",
    574        priority: "",
    575        offsets: [35, 51],
    576        declarationText: "background: red;",
    577      },
    578    ],
    579  },
    580 
    581  // Embedded comment where the parsing heuristic fails.
    582  {
    583    parseComments: true,
    584    input: "width: 5; /* background something: green; */ background: red;",
    585    expected: [
    586      {
    587        name: "width",
    588        value: "5",
    589        priority: "",
    590        offsets: [0, 9],
    591        declarationText: "width: 5;",
    592      },
    593      {
    594        name: "background",
    595        value: "red",
    596        priority: "",
    597        offsets: [45, 61],
    598        declarationText: "background: red;",
    599      },
    600    ],
    601  },
    602 
    603  // Embedded comment where the parsing heuristic is a bit funny.
    604  {
    605    parseComments: true,
    606    input: "width: 5; /* background: */ background: red;",
    607    expected: [
    608      {
    609        name: "width",
    610        value: "5",
    611        priority: "",
    612        offsets: [0, 9],
    613        declarationText: "width: 5;",
    614      },
    615      {
    616        name: "background",
    617        value: "",
    618        priority: "",
    619        offsets: [13, 24],
    620        commentOffsets: [10, 27],
    621      },
    622      {
    623        name: "background",
    624        value: "red",
    625        priority: "",
    626        offsets: [28, 44],
    627        declarationText: "background: red;",
    628      },
    629    ],
    630  },
    631 
    632  // Another case where the parsing heuristic says not to bother; note
    633  // that there is no ";" in the comment.
    634  {
    635    parseComments: true,
    636    input: "width: 5; /* background: yellow */ background: red;",
    637    expected: [
    638      {
    639        name: "width",
    640        value: "5",
    641        priority: "",
    642        offsets: [0, 9],
    643        declarationText: "width: 5;",
    644      },
    645      {
    646        name: "background",
    647        value: "yellow",
    648        priority: "",
    649        offsets: [13, 31],
    650        declarationText: "background: yellow",
    651        commentOffsets: [10, 34],
    652      },
    653      {
    654        name: "background",
    655        value: "red",
    656        priority: "",
    657        offsets: [35, 51],
    658        declarationText: "background: red;",
    659      },
    660    ],
    661  },
    662 
    663  // Parsing a comment should yield text that has been unescaped, and
    664  // the offsets should refer to the original text.
    665  {
    666    parseComments: true,
    667    input: "/* content: '*\\/'; */",
    668    expected: [
    669      {
    670        name: "content",
    671        value: "'*/'",
    672        priority: "",
    673        offsets: [3, 18],
    674        declarationText: "content: '*\\/';",
    675        commentOffsets: [0, 21],
    676      },
    677    ],
    678  },
    679 
    680  // Parsing a comment should yield text that has been unescaped, and
    681  // the offsets should refer to the original text.  This variant
    682  // tests the no-semicolon path.
    683  {
    684    parseComments: true,
    685    input: "/* content: '*\\/' */",
    686    expected: [
    687      {
    688        name: "content",
    689        value: "'*/'",
    690        priority: "",
    691        offsets: [3, 17],
    692        declarationText: "content: '*\\/'",
    693        commentOffsets: [0, 20],
    694      },
    695    ],
    696  },
    697 
    698  // A comment-in-a-comment should yield the correct offsets.
    699  {
    700    parseComments: true,
    701    input: "/* color: /\\* comment *\\/ red; */",
    702    expected: [
    703      {
    704        name: "color",
    705        value: "red",
    706        priority: "",
    707        offsets: [3, 30],
    708        declarationText: "color: /\\* comment *\\/ red;",
    709        commentOffsets: [0, 33],
    710      },
    711    ],
    712  },
    713 
    714  // HTML comments are not special -- they are just ordinary tokens.
    715  {
    716    parseComments: true,
    717    input: "<!-- color: red; --> color: blue;",
    718    expected: [
    719      { name: "<!-- color", value: "red", priority: "", offsets: [0, 16] },
    720      { name: "--> color", value: "blue", priority: "", offsets: [17, 33] },
    721    ],
    722  },
    723 
    724  // Don't error on an empty comment.
    725  {
    726    parseComments: true,
    727    input: "/**/",
    728    expected: [],
    729  },
    730 
    731  // Parsing our special comments skips the name-check heuristic.
    732  {
    733    parseComments: true,
    734    input: "/*! walrus: zebra; */",
    735    expected: [
    736      {
    737        name: "walrus",
    738        value: "zebra",
    739        priority: "",
    740        offsets: [4, 18],
    741        declarationText: "walrus: zebra;",
    742        commentOffsets: [0, 21],
    743      },
    744    ],
    745  },
    746 
    747  // Regression test for bug 1287620.
    748  {
    749    input: "color: blue \\9 no\\_need",
    750    expected: [
    751      {
    752        name: "color",
    753        value: "blue \\9 no\\_need",
    754        priority: "",
    755        offsets: [0, 23],
    756        declarationText: "color: blue \\9 no\\_need",
    757      },
    758    ],
    759  },
    760 
    761  // Regression test for bug 1297890 - don't paste tokens.
    762  {
    763    parseComments: true,
    764    input: "stroke-dasharray: 1/*ThisIsAComment*/2;",
    765    expected: [
    766      {
    767        name: "stroke-dasharray",
    768        value: "1 2",
    769        priority: "",
    770        offsets: [0, 39],
    771        declarationText: "stroke-dasharray: 1/*ThisIsAComment*/2;",
    772      },
    773    ],
    774  },
    775 
    776  // Regression test for bug 1384463 - don't trim significant
    777  // whitespace.
    778  {
    779    // \u00a0 is non-breaking space.
    780    input: "\u00a0vertical-align: top",
    781    expected: [
    782      {
    783        name: "\u00a0vertical-align",
    784        value: "top",
    785        priority: "",
    786        offsets: [0, 20],
    787        declarationText: "\u00a0vertical-align: top",
    788      },
    789    ],
    790  },
    791 
    792  // Regression test for bug 1544223 - take CSS blocks into consideration before handling
    793  // ; and : (i.e. don't advance to the property name or value automatically).
    794  {
    795    input: `--foo: (
    796      :doodle {
    797        @grid: 30x1 / 18vmin;
    798      }
    799      :container {
    800        perspective: 30vmin;
    801      }
    802 
    803      @place-cell: center;
    804      @size: 100%;
    805 
    806      :after, :before {
    807        content: '';
    808        @size: 100%;
    809        position: absolute;
    810        border: 2.4vmin solid var(--color);
    811        border-image: radial-gradient(
    812          var(--color), transparent 60%
    813        );
    814        border-image-width: 4;
    815        transform: rotate(@pn(0, 5deg));
    816      }
    817 
    818      will-change: transform, opacity;
    819      animation: scale-up 15s linear infinite;
    820      animation-delay: calc(-15s / @size() * @i());
    821      box-shadow: inset 0 0 1em var(--color);
    822      border-radius: 50%;
    823      filter: var(--filter);
    824 
    825      @keyframes scale-up {
    826        0%, 100% {
    827          transform: translateZ(0) scale(0) rotate(0);
    828          opacity: 0;
    829        }
    830        50% {
    831          opacity: 1;
    832        }
    833        99% {
    834          transform:
    835            translateZ(30vmin)
    836            scale(1)
    837            rotate(-270deg);
    838        }
    839      }
    840    );`,
    841    expected: [
    842      {
    843        name: "--foo",
    844        value:
    845          "( :doodle { @grid: 30x1 / 18vmin; } :container { perspective: 30vmin; } " +
    846          "@place-cell: center; @size: 100%; :after, :before { content: ''; @size: " +
    847          "100%; position: absolute; border: 2.4vmin solid var(--color); " +
    848          "border-image: radial-gradient( var(--color), transparent 60% ); " +
    849          "border-image-width: 4; transform: rotate(@pn(0, 5deg)); } will-change: " +
    850          "transform, opacity; animation: scale-up 15s linear infinite; " +
    851          "animation-delay: calc(-15s / @size() * @i()); box-shadow: inset 0 0 1em " +
    852          "var(--color); border-radius: 50%; filter: var(--filter); @keyframes " +
    853          "scale-up { 0%, 100% { transform: translateZ(0) scale(0) rotate(0); " +
    854          "opacity: 0; } 50% { opacity: 1; } 99% { transform: translateZ(30vmin) " +
    855          "scale(1) rotate(-270deg); } } )",
    856        priority: "",
    857        offsets: [0, 1036],
    858        isCustomProperty: true,
    859      },
    860    ],
    861  },
    862 
    863  /***** Testing nested rules *****/
    864 
    865  // Testing basic nesting with tagname selector
    866  {
    867    input: `
    868      color: red;
    869      div {
    870        background: blue;
    871      }
    872    `,
    873    expected: [
    874      {
    875        name: "color",
    876        value: "red",
    877        priority: "",
    878        offsets: [7, 18],
    879        declarationText: "color: red;",
    880      },
    881    ],
    882  },
    883 
    884  // Testing basic nesting with tagname + pseudo selector
    885  {
    886    input: `
    887      color: red;
    888      div:hover {
    889        background: blue;
    890      }
    891    `,
    892    expected: [
    893      {
    894        name: "color",
    895        value: "red",
    896        priority: "",
    897        offsets: [7, 18],
    898        declarationText: "color: red;",
    899      },
    900    ],
    901  },
    902 
    903  // Testing basic nesting with id selector
    904  {
    905    input: `
    906      color: red;
    907      #myEl {
    908        background: blue;
    909      }
    910    `,
    911    expected: [
    912      {
    913        name: "color",
    914        value: "red",
    915        priority: "",
    916        offsets: [7, 18],
    917        declarationText: "color: red;",
    918      },
    919    ],
    920  },
    921 
    922  // Testing basic nesting with class selector
    923  {
    924    input: `
    925      color: red;
    926      .myEl {
    927        background: blue;
    928      }
    929    `,
    930    expected: [
    931      {
    932        name: "color",
    933        value: "red",
    934        priority: "",
    935        offsets: [7, 18],
    936        declarationText: "color: red;",
    937      },
    938    ],
    939  },
    940 
    941  // Testing basic nesting with & + ident selector
    942  {
    943    input: `
    944      color: red;
    945      & div {
    946        background: blue;
    947      }
    948    `,
    949    expected: [
    950      {
    951        name: "color",
    952        value: "red",
    953        priority: "",
    954        offsets: [7, 18],
    955        declarationText: "color: red;",
    956      },
    957    ],
    958  },
    959 
    960  // Testing basic nesting with direct child selector
    961  {
    962    input: `
    963      color: red;
    964      > div {
    965        background: blue;
    966      }
    967    `,
    968    expected: [
    969      {
    970        name: "color",
    971        value: "red",
    972        priority: "",
    973        offsets: [7, 18],
    974        declarationText: "color: red;",
    975      },
    976    ],
    977  },
    978 
    979  // Testing basic nesting with & and :hover pseudo-class selector
    980  {
    981    input: `
    982      color: red;
    983      &:hover {
    984        background: blue;
    985      }
    986    `,
    987    expected: [
    988      {
    989        name: "color",
    990        value: "red",
    991        priority: "",
    992        offsets: [7, 18],
    993        declarationText: "color: red;",
    994      },
    995    ],
    996  },
    997 
    998  // Testing basic nesting with :not pseudo-class selector and non-leading &
    999  {
   1000    input: `
   1001      color: red;
   1002      :not(&) {
   1003        background: blue;
   1004      }
   1005    `,
   1006    expected: [
   1007      {
   1008        name: "color",
   1009        value: "red",
   1010        priority: "",
   1011        offsets: [7, 18],
   1012        declarationText: "color: red;",
   1013      },
   1014    ],
   1015  },
   1016 
   1017  // Testing basic nesting with attribute selector
   1018  {
   1019    input: `
   1020      color: red;
   1021      [class] {
   1022        background: blue;
   1023      }
   1024    `,
   1025    expected: [
   1026      {
   1027        name: "color",
   1028        value: "red",
   1029        priority: "",
   1030        offsets: [7, 18],
   1031        declarationText: "color: red;",
   1032      },
   1033    ],
   1034  },
   1035 
   1036  // Testing basic nesting with & compound selector
   1037  {
   1038    input: `
   1039      color: red;
   1040      &div {
   1041        background: blue;
   1042      }
   1043    `,
   1044    expected: [
   1045      {
   1046        name: "color",
   1047        value: "red",
   1048        priority: "",
   1049        offsets: [7, 18],
   1050        declarationText: "color: red;",
   1051      },
   1052    ],
   1053  },
   1054 
   1055  // Testing basic nesting with relative + selector
   1056  {
   1057    input: `
   1058      color: red;
   1059      + div {
   1060        background: blue;
   1061      }
   1062    `,
   1063    expected: [
   1064      {
   1065        name: "color",
   1066        value: "red",
   1067        priority: "",
   1068        offsets: [7, 18],
   1069        declarationText: "color: red;",
   1070      },
   1071    ],
   1072  },
   1073 
   1074  // Testing basic nesting with relative ~ selector
   1075  {
   1076    input: `
   1077      color: red;
   1078      ~ div {
   1079        background: blue;
   1080      }
   1081    `,
   1082    expected: [
   1083      {
   1084        name: "color",
   1085        value: "red",
   1086        priority: "",
   1087        offsets: [7, 18],
   1088        declarationText: "color: red;",
   1089      },
   1090    ],
   1091  },
   1092 
   1093  // Testing basic nesting with relative * selector
   1094  {
   1095    input: `
   1096      color: red;
   1097      * div {
   1098        background: blue;
   1099      }
   1100    `,
   1101    expected: [
   1102      {
   1103        name: "color",
   1104        value: "red",
   1105        priority: "",
   1106        offsets: [7, 18],
   1107        declarationText: "color: red;",
   1108      },
   1109    ],
   1110  },
   1111 
   1112  // Testing basic nesting after a property with !important
   1113  {
   1114    input: `
   1115      color: red !important;
   1116      & div {
   1117        background: blue;
   1118      }
   1119    `,
   1120    expected: [
   1121      {
   1122        name: "color",
   1123        value: "red",
   1124        priority: "important",
   1125        offsets: [7, 29],
   1126        declarationText: "color: red !important;",
   1127      },
   1128    ],
   1129  },
   1130 
   1131  // Testing basic nesting after a comment
   1132  {
   1133    input: `
   1134      color: red;
   1135      /* nested rules */
   1136      & div {
   1137        background: blue;
   1138      }
   1139    `,
   1140    expected: [
   1141      {
   1142        name: "color",
   1143        value: "red",
   1144        priority: "",
   1145        offsets: [7, 18],
   1146        declarationText: "color: red;",
   1147      },
   1148    ],
   1149  },
   1150 
   1151  // Testing at-rules (with condition) nesting
   1152  {
   1153    input: `
   1154      color: red;
   1155      @media (orientation: landscape) {
   1156        background: blue;
   1157      }
   1158    `,
   1159    expected: [
   1160      {
   1161        name: "color",
   1162        value: "red",
   1163        priority: "",
   1164        offsets: [7, 18],
   1165        declarationText: "color: red;",
   1166      },
   1167    ],
   1168  },
   1169 
   1170  // Testing at-rules (without condition) nesting
   1171  {
   1172    input: `
   1173      color: red;
   1174      @media screen {
   1175        background: blue;
   1176      }
   1177    `,
   1178    expected: [
   1179      {
   1180        name: "color",
   1181        value: "red",
   1182        priority: "",
   1183        offsets: [7, 18],
   1184        declarationText: "color: red;",
   1185      },
   1186    ],
   1187  },
   1188 
   1189  // Testing multi-level nesting
   1190  {
   1191    input: `
   1192      color: red;
   1193      &div {
   1194        &.active {
   1195          border: 1px;
   1196        }
   1197        padding: 10px;
   1198      }
   1199      background: gold;
   1200    `,
   1201    expected: [
   1202      {
   1203        name: "color",
   1204        value: "red",
   1205        priority: "",
   1206        offsets: [7, 18],
   1207        declarationText: "color: red;",
   1208      },
   1209    ],
   1210  },
   1211 
   1212  // Testing multi-level nesting with at-rules
   1213  {
   1214    input: `
   1215      color: red;
   1216      @layer {
   1217        background: yellow;
   1218        @media screen {
   1219          & {
   1220            border-color: blue;
   1221          }
   1222        }
   1223      }
   1224    `,
   1225    expected: [
   1226      {
   1227        name: "color",
   1228        value: "red",
   1229        priority: "",
   1230        offsets: [7, 18],
   1231        declarationText: "color: red;",
   1232      },
   1233    ],
   1234  },
   1235 
   1236  // Testing sibling nested rules
   1237  {
   1238    input: `
   1239      color: red;
   1240      .active {
   1241        border: 1px;
   1242      }
   1243      &div {
   1244        padding: 10px;
   1245      }
   1246      border-color: cyan
   1247    `,
   1248    expected: [
   1249      {
   1250        name: "color",
   1251        value: "red",
   1252        priority: "",
   1253        offsets: [7, 18],
   1254        declarationText: "color: red;",
   1255      },
   1256    ],
   1257  },
   1258 
   1259  // Testing nesting interwined between property declarations
   1260  {
   1261    input: `
   1262      color: red;
   1263      .active {
   1264        border: 1px;
   1265      }
   1266      background: gold;
   1267      &div {
   1268        padding: 10px;
   1269      }
   1270      border-color: cyan
   1271    `,
   1272    expected: [
   1273      {
   1274        name: "color",
   1275        value: "red",
   1276        priority: "",
   1277        offsets: [7, 18],
   1278        declarationText: "color: red;",
   1279      },
   1280    ],
   1281  },
   1282 
   1283  // Testing that "}" in content property does not impact the nested state
   1284  {
   1285    input: `
   1286      color: red;
   1287      &div {
   1288        content: "}"
   1289        color: blue;
   1290      }
   1291      background: gold;
   1292    `,
   1293    expected: [
   1294      {
   1295        name: "color",
   1296        value: "red",
   1297        priority: "",
   1298        offsets: [7, 18],
   1299        declarationText: "color: red;",
   1300      },
   1301    ],
   1302  },
   1303 
   1304  // Testing that "}" in attribute selector does not impact the nested state
   1305  {
   1306    input: `
   1307      color: red;
   1308      + .foo {
   1309        [class="}"] {
   1310          padding: 10px;
   1311        }
   1312      }
   1313      background: gold;
   1314    `,
   1315    expected: [
   1316      {
   1317        name: "color",
   1318        value: "red",
   1319        priority: "",
   1320        offsets: [7, 18],
   1321        declarationText: "color: red;",
   1322      },
   1323    ],
   1324  },
   1325 
   1326  // Testing that in function does not impact the nested state
   1327  {
   1328    input: `
   1329      color: red;
   1330      + .foo {
   1331        background: url("img.png?x=}")
   1332      }
   1333      background: gold;
   1334    `,
   1335    expected: [
   1336      {
   1337        name: "color",
   1338        value: "red",
   1339        priority: "",
   1340        offsets: [7, 18],
   1341        declarationText: "color: red;",
   1342      },
   1343    ],
   1344  },
   1345 
   1346  // Testing that "}" in comment does not impact the nested state
   1347  {
   1348    input: `
   1349      color: red;
   1350      + .foo {
   1351        /* Check } */
   1352        padding: 10px;
   1353      }
   1354      background: gold;
   1355    `,
   1356    expected: [
   1357      {
   1358        name: "color",
   1359        value: "red",
   1360        priority: "",
   1361        offsets: [7, 18],
   1362        declarationText: "color: red;",
   1363      },
   1364    ],
   1365  },
   1366 
   1367  // Testing that nested rules in comments aren't reported
   1368  {
   1369    parseComments: true,
   1370    input: "width: 5; /* div { color: cyan; } */ background: red;",
   1371    expected: [
   1372      {
   1373        name: "width",
   1374        value: "5",
   1375        priority: "",
   1376        offsets: [0, 9],
   1377        declarationText: "width: 5;",
   1378      },
   1379      {
   1380        name: "background",
   1381        value: "red",
   1382        priority: "",
   1383        offsets: [37, 53],
   1384        declarationText: "background: red;",
   1385      },
   1386    ],
   1387  },
   1388 
   1389  // Testing that declarations in comments are still handled while nested rule in same comment is ignored
   1390  {
   1391    parseComments: true,
   1392    input:
   1393      "width: 5; /* padding: 12px; div { color: cyan; } margin: 1em; */ background: red;",
   1394    expected: [
   1395      {
   1396        name: "width",
   1397        value: "5",
   1398        priority: "",
   1399        offsets: [0, 9],
   1400        declarationText: "width: 5;",
   1401      },
   1402      {
   1403        name: "padding",
   1404        value: "12px",
   1405        priority: "",
   1406        offsets: [13, 27],
   1407        declarationText: "padding: 12px;",
   1408      },
   1409      {
   1410        name: "margin",
   1411        value: "1em",
   1412        priority: "",
   1413        offsets: [49, 61],
   1414        declarationText: "margin: 1em;",
   1415      },
   1416      {
   1417        name: "background",
   1418        value: "red",
   1419        priority: "",
   1420        offsets: [65, 81],
   1421        declarationText: "background: red;",
   1422      },
   1423    ],
   1424  },
   1425 
   1426  // Testing nesting without closing bracket
   1427  {
   1428    input: `
   1429      color: red;
   1430      & div {
   1431        background: blue;
   1432    `,
   1433    expected: [
   1434      {
   1435        name: "color",
   1436        value: "red",
   1437        priority: "",
   1438        offsets: [7, 18],
   1439        declarationText: "color: red;",
   1440      },
   1441    ],
   1442  },
   1443 
   1444  /***** Testing Custom Properties rules *****/
   1445  {
   1446    input: `
   1447      --foo: 1;
   1448      --bar: 2;
   1449      --foobar: calc( var(--foo, 3) * var(--bar, 4));
   1450      --empty: ;
   1451      width: var(--foobar, var(--fallback));
   1452      -moz-appearance: none;
   1453      --: 5;
   1454    `,
   1455    expected: [
   1456      {
   1457        name: "--foo",
   1458        value: "1",
   1459        priority: "",
   1460        offsets: [7, 16],
   1461        declarationText: "--foo: 1;",
   1462        isCustomProperty: true,
   1463      },
   1464      {
   1465        name: "--bar",
   1466        value: "2",
   1467        priority: "",
   1468        offsets: [23, 32],
   1469        declarationText: "--bar: 2;",
   1470        isCustomProperty: true,
   1471      },
   1472      {
   1473        name: "--foobar",
   1474        value: "calc( var(--foo, 3) * var(--bar, 4))",
   1475        priority: "",
   1476        offsets: [39, 86],
   1477        declarationText: "--foobar: calc( var(--foo, 3) * var(--bar, 4));",
   1478        isCustomProperty: true,
   1479      },
   1480      {
   1481        name: "--empty",
   1482        value: "",
   1483        priority: "",
   1484        offsets: [93, 103],
   1485        declarationText: "--empty: ;",
   1486        isCustomProperty: true,
   1487      },
   1488      {
   1489        name: "width",
   1490        value: "var(--foobar, var(--fallback))",
   1491        priority: "",
   1492        offsets: [110, 148],
   1493        declarationText: "width: var(--foobar, var(--fallback));",
   1494      },
   1495      {
   1496        name: "-moz-appearance",
   1497        value: "none",
   1498        priority: "",
   1499        offsets: [155, 177],
   1500        declarationText: "-moz-appearance: none;",
   1501      },
   1502      {
   1503        name: "--",
   1504        value: "5",
   1505        priority: "",
   1506        offsets: [184, 190],
   1507        declarationText: "--: 5;",
   1508      },
   1509    ],
   1510  },
   1511 ];
   1512 
   1513 function run_test() {
   1514  run_basic_tests();
   1515  run_comment_tests();
   1516  run_named_tests();
   1517 }
   1518 
   1519 // Test parseDeclarations.
   1520 function run_basic_tests() {
   1521  for (const test of TEST_DATA) {
   1522    info("Test input string " + test.input);
   1523    let output;
   1524    try {
   1525      output = parseDeclarations(
   1526        isCssPropertyKnown,
   1527        test.input,
   1528        test.parseComments
   1529      );
   1530    } catch (e) {
   1531      info(
   1532        "parseDeclarations threw an exception with the given input " + "string"
   1533      );
   1534      if (test.throws) {
   1535        info("Exception expected");
   1536        Assert.ok(true);
   1537      } else {
   1538        info("Exception unexpected\n" + e);
   1539        Assert.ok(false);
   1540      }
   1541    }
   1542    if (output) {
   1543      assertDeclarations(test.input, output, test.expected);
   1544    }
   1545  }
   1546 }
   1547 
   1548 const COMMENT_DATA = [
   1549  {
   1550    input: "content: 'hi",
   1551    expected: [
   1552      {
   1553        name: "content",
   1554        value: "'hi",
   1555        priority: "",
   1556        terminator: "';",
   1557        offsets: [2, 14],
   1558        colonOffsets: [9, 11],
   1559        commentOffsets: [0, 16],
   1560      },
   1561    ],
   1562  },
   1563  {
   1564    input: "text that once confounded the parser;",
   1565    expected: [],
   1566  },
   1567 ];
   1568 
   1569 // Test parseCommentDeclarations.
   1570 function run_comment_tests() {
   1571  for (const test of COMMENT_DATA) {
   1572    info("Test input string " + test.input);
   1573    const output = _parseCommentDeclarations(
   1574      isCssPropertyKnown,
   1575      test.input,
   1576      0,
   1577      test.input.length + 4
   1578    );
   1579    deepEqual(output, test.expected);
   1580  }
   1581 }
   1582 
   1583 const NAMED_DATA = [
   1584  {
   1585    input: "position:absolute;top50px;height:50px;",
   1586    expected: [
   1587      {
   1588        name: "position",
   1589        value: "absolute",
   1590        priority: "",
   1591        terminator: "",
   1592        offsets: [0, 18],
   1593        colonOffsets: [8, 9],
   1594      },
   1595      {
   1596        name: "height",
   1597        value: "50px",
   1598        priority: "",
   1599        terminator: "",
   1600        offsets: [26, 38],
   1601        colonOffsets: [32, 33],
   1602      },
   1603    ],
   1604  },
   1605 ];
   1606 
   1607 // Test parseNamedDeclarations.
   1608 function run_named_tests() {
   1609  for (const test of NAMED_DATA) {
   1610    info("Test input string " + test.input);
   1611    const output = parseNamedDeclarations(isCssPropertyKnown, test.input, true);
   1612    info(JSON.stringify(output));
   1613    deepEqual(output, test.expected);
   1614  }
   1615 }
   1616 
   1617 function assertDeclarations(input, actualDeclarations, expectedDeclarations) {
   1618  if (actualDeclarations.length === expectedDeclarations.length) {
   1619    for (let i = 0; i < expectedDeclarations.length; i++) {
   1620      const actual = actualDeclarations[i];
   1621      const expected = expectedDeclarations[i];
   1622      Assert.ok(!!actual);
   1623      info(
   1624        "Check that the output item has the expected name, " +
   1625          "value and priority"
   1626      );
   1627      Assert.equal(actual.name, expected.name, "Expected name");
   1628      Assert.equal(actual.value, expected.value, "Expected value");
   1629      Assert.equal(actual.priority, expected.priority, "Expected priority");
   1630      deepEqual(actual.offsets, expected.offsets, "Expected offsets");
   1631      if ("commentOffsets" in expected) {
   1632        deepEqual(
   1633          actual.commentOffsets,
   1634          expected.commentOffsets,
   1635          "Expected commentOffsets"
   1636        );
   1637      }
   1638 
   1639      if (expected.declarationText) {
   1640        Assert.equal(
   1641          input.substring(expected.offsets[0], expected.offsets[1]),
   1642          expected.declarationText,
   1643          "Expected declarationText"
   1644        );
   1645      }
   1646      Assert.equal(
   1647        actual.isCustomProperty,
   1648        expected.isCustomProperty,
   1649        "Expected isCustomProperty"
   1650      );
   1651    }
   1652  } else {
   1653    for (const prop of actualDeclarations) {
   1654      info(
   1655        "Actual output contained: {name: " +
   1656          prop.name +
   1657          ", value: " +
   1658          prop.value +
   1659          ", priority: " +
   1660          prop.priority +
   1661          "}"
   1662      );
   1663    }
   1664    Assert.equal(actualDeclarations.length, expectedDeclarations.length);
   1665  }
   1666 }