tor-browser

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

at-property.html (12611B)


      1 <!DOCTYPE html>
      2 <link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule">
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script src="./resources/utils.js"></script>
      6 <div id="outer">
      7  <div id="target"></div>
      8 </div>
      9 <script>
     10 
     11 // Parsing:
     12 
     13 let uppercase_first = (x) => x.charAt(0).toUpperCase() + x.slice(1);
     14 let to_camel_case = (x) => x.split('-')[0] + x.split('-').slice(1).map(uppercase_first).join('');
     15 
     16 function get_cssom_descriptor_value(rule, descriptor) {
     17  switch (descriptor) {
     18    case 'syntax':
     19      return rule.syntax;
     20    case 'inherits':
     21      return rule.inherits;
     22    case 'initial-value':
     23      return rule.initialValue;
     24    default:
     25      assert_true(false, 'Should not reach here');
     26      return null;
     27  }
     28 }
     29 
     30 // Test that for the given descriptor (e.g. 'syntax'), the specified value
     31 // will yield the expected_value when observed using CSSOM. If the expected_value
     32 // is omitted, it is the same as the specified value.
     33 function test_descriptor(descriptor, specified_value, expected_value, other_descriptors) {
     34  // Try and build a valid @property form the specified descriptor.
     35  let at_property = { [to_camel_case(descriptor)]: specified_value };
     36 
     37  // If extra values are specified in other_descriptors, just use them.
     38  if (typeof(other_descriptors) !== 'unspecified') {
     39    for (let name in other_descriptors) {
     40      if (other_descriptors.hasOwnProperty(name)) {
     41        if (name == descriptor) {
     42          throw `Unexpected ${name} in other_descriptors`;
     43        }
     44        at_property[to_camel_case(name)] = other_descriptors[name];
     45      }
     46    }
     47  }
     48 
     49  if (!('syntax' in at_property)) {
     50    // The syntax descriptor is required. Use the universal one as a fallback.
     51    // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor
     52    at_property.syntax = '"*"';
     53  }
     54  if (!('inherits' in at_property)) {
     55    // The inherits descriptor is required. Make it true as a fallback.
     56    // https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor
     57    at_property.inherits = true;
     58  }
     59  if (!at_property.syntax.match(/^"\s*\*\s*"$/) &&
     60      !('initialValue' in at_property)) {
     61    // The initial-value is required for non-universal syntax.
     62    // Pick a computationally independent value that follows specified syntax.
     63    // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor
     64    at_property.initialValue = (() => {
     65      let first_syntax_component = specified_value
     66        .replace(/^"(.*)"$/, '$1') // unquote
     67        .replace(/[\s\uFEFF\xA0]+/g, ' ') // collapse whitespaces
     68        .match(/^[^|\#\+]*/)[0] // pick first component
     69        .trim();
     70       switch (first_syntax_component) {
     71         case '<color>': return 'blue';
     72         case '<length>': return '42px';
     73         default:
     74           if (first_syntax_component.startsWith('<')) {
     75             throw `Unsupported data type name '${first_syntax_component}'`;
     76           }
     77           return first_syntax_component; // <custom-ident>
     78       }
     79    })();
     80  }
     81 
     82  if (expected_value === null) {
     83    test_with_at_property(at_property, (name, rule) => {
     84      assert_true(!rule);
     85    }, `Attribute '${descriptor}' makes the @property rule invalid for [${specified_value}]`);
     86  } else {
     87    if (typeof(expected_value) === 'undefined')
     88      expected_value = specified_value;
     89    test_with_at_property(at_property, (name, rule) => {
     90      assert_equals(get_cssom_descriptor_value(rule, descriptor), expected_value);
     91    }, `Attribute '${descriptor}' returns expected value for [${specified_value}]`);
     92  }
     93 }
     94 
     95 // syntax
     96 test_descriptor('syntax', '"<color>"', '<color>');
     97 test_descriptor('syntax', '"<color> | none"', '<color> | none');
     98 test_descriptor('syntax', '"<color># | <image> | none"', '<color># | <image> | none');
     99 test_descriptor('syntax', '"foo | <length>#"', 'foo | <length>#');
    100 test_descriptor('syntax', '"foo | bar | baz"', 'foo | bar | baz');
    101 test_descriptor('syntax', '"notasyntax"', 'notasyntax');
    102 
    103 // syntax: universal
    104 for (const syntax of ["*", " * ", "* ", "\t*\t"]) {
    105  test_descriptor('syntax', `"${syntax}"`, syntax);
    106 }
    107 
    108 // syntax: <color> value
    109 test_descriptor('syntax', '"red"', "red"); // treated as <custom-ident>.
    110 test_descriptor('syntax', '"rgb(255, 0, 0)"', null);
    111 
    112 // syntax: missing quotes
    113 test_descriptor('syntax', '<color>', null);
    114 test_descriptor('syntax', 'foo | bar', null);
    115 
    116 // syntax: invalid <custom-ident>
    117 // https://drafts.csswg.org/css-values-4/#custom-idents
    118 for (const syntax of
    119  ["default",
    120   "initial",
    121   "inherit",
    122   "unset",
    123   "revert",
    124   "revert-layer",
    125  ]) {
    126  test_descriptor('syntax', `"${syntax}"`, null);
    127  test_descriptor('syntax', `"${uppercase_first(syntax)}"`, null);
    128 }
    129 
    130 // syntax: pipe between components
    131 test_descriptor('syntax', '"foo bar"', null, {'initial-value': 'foo bar'});
    132 test_descriptor('syntax', '"Foo <length>"', null, {'initial-value': 'Foo 42px'});
    133 test_descriptor('syntax', '"foo, bar"', null, {'initial-value': 'foo, bar'});
    134 test_descriptor('syntax', '"<length> <percentage>"', null, {'initial-value': '42px 100%'});
    135 
    136 // syntax: leading bar
    137 test_descriptor('syntax', '"|<length>"', null, {'initial-value': '42px'});
    138 
    139 // initial-value
    140 test_descriptor('initial-value', '10px');
    141 test_descriptor('initial-value', 'rgb(1, 2, 3)');
    142 test_descriptor('initial-value', 'red');
    143 test_descriptor('initial-value', 'foo');
    144 test_descriptor('initial-value', 'foo(){}');
    145 
    146 // initial-value: not computationally independent
    147 test_descriptor('initial-value', '3em', null, {'syntax': '"<length>"'});
    148 test_descriptor('initial-value', 'var(--x)', null);
    149 
    150 // inherits
    151 test_descriptor('inherits', 'true', true);
    152 test_descriptor('inherits', 'false', false);
    153 
    154 test_descriptor('inherits', 'none', null);
    155 test_descriptor('inherits', '0', null);
    156 test_descriptor('inherits', '1', null);
    157 test_descriptor('inherits', '"true"', null);
    158 test_descriptor('inherits', '"false"', null);
    159 test_descriptor('inherits', 'calc(0)', null);
    160 
    161 test_with_style_node('@property foo { }', (node) => {
    162  assert_equals(node.sheet.rules.length, 0);
    163 }, 'Invalid property name does not parse [foo]');
    164 
    165 test_with_style_node('@property -foo { }', (node) => {
    166  assert_equals(node.sheet.rules.length, 0);
    167 }, 'Invalid property name does not parse [-foo]');
    168 
    169 // Applying @property rules
    170 
    171 function test_applied(syntax, initial, inherits, expected) {
    172  test_with_at_property({
    173    syntax: `"${syntax}"`,
    174    initialValue: initial,
    175    inherits: inherits
    176  }, (name, rule) => {
    177    let actual = getComputedStyle(target).getPropertyValue(name);
    178    assert_equals(actual, expected);
    179  }, `Rule applied [${syntax}, ${initial}, ${inherits}]`);
    180 }
    181 
    182 function test_not_applied(syntax, initial, inherits) {
    183  test_with_at_property({
    184    syntax: `"${syntax}"`,
    185    initialValue: initial,
    186    inherits: inherits
    187  }, (name, rule) => {
    188    let actual = getComputedStyle(target).getPropertyValue(name);
    189    assert_equals(actual, '');
    190  }, `Rule not applied [${syntax}, ${initial}, ${inherits}]`);
    191 }
    192 
    193 // syntax, initialValue, inherits, expected
    194 test_applied('*', 'foo(){}', false, 'foo(){}');
    195 test_applied('<angle>', '42deg', false, '42deg');
    196 test_applied('<angle>', '1turn', false, '360deg');
    197 test_applied('<color>', 'green', false, 'rgb(0, 128, 0)');
    198 test_applied('<color>', 'rgb(1, 2, 3)', false, 'rgb(1, 2, 3)');
    199 test_applied('<image>', 'url("http://a/")', false, 'url("http://a/")');
    200 test_applied('<integer>', '5', false, '5');
    201 test_applied('<length-percentage>', '10px', false, '10px');
    202 test_applied('<length-percentage>', '10%', false, '10%');
    203 test_applied('<length-percentage>', 'calc(10% + 10px)', false, 'calc(10% + 10px)');
    204 test_applied('<length>', '10px', false, '10px');
    205 test_applied('<number>', '2.5', false, '2.5');
    206 test_applied('<percentage>', '10%', false, '10%');
    207 test_applied('<resolution>', '50dppx', false, '50dppx');
    208 test_applied('<resolution>', '96dpi', false, '1dppx');
    209 test_applied('<time>', '10s', false, '10s');
    210 test_applied('<time>', '1000ms', false, '1s');
    211 test_applied('<transform-function>', 'rotateX(0deg)', false, 'rotateX(0deg)');
    212 test_applied('<transform-list>', 'rotateX(0deg)', false, 'rotateX(0deg)');
    213 test_applied('<transform-list>', 'rotateX(0deg) translateX(10px)', false, 'rotateX(0deg) translateX(10px)');
    214 test_applied('<url>', 'url("http://a/")', false, 'url("http://a/")');
    215 
    216 test_applied("<string>", "'foo bar'", false, '"foo bar"');
    217 test_applied("<string>", " 'foo bar' ", false, '"foo bar"');
    218 test_applied("<string>", `'"foo" bar'`, false, '"\\"foo\\" bar"');
    219 test_applied("<string>", '"bar baz"', false, '"bar baz"');
    220 test_applied("<string>", `"bar 'baz'"`, false, `"bar 'baz'"`);
    221 test_applied("<string>+", "'foo' 'bar'", false, '"foo" "bar"');
    222 test_applied("<string>#", "'foo', 'bar'", false, '"foo", "bar"');
    223 test_applied("<string>+ | <string>#", "'foo' 'bar'", false, '"foo" "bar"');
    224 test_applied("<string>+ | <string>#", " 'foo' 'bar'", false, '"foo" "bar"');
    225 test_applied("<string>+ | <string>#", `'foo' "bar"`, false, '"foo" "bar"');
    226 test_applied("<string># | <string>+", "'foo', 'bar'", false, '"foo", "bar"');
    227 test_applied("<string># | <string>+", "'foo', 'bar' ", false, '"foo", "bar"');
    228 test_applied("<string># | <string>+", `"foo", 'bar'`, false, '"foo", "bar"');
    229 
    230 test_not_applied("<string>", "1px", false);
    231 test_not_applied("<string>", "foo", false);
    232 test_not_applied("<string>", "calc(1 + 2)", false);
    233 test_not_applied("<string>", "rgb(255, 99, 71)", false);
    234 test_not_applied("<string>", "'foo' 2px", false);
    235 
    236 // inherits: true/false
    237 test_applied('<color>', 'tomato', false, 'rgb(255, 99, 71)');
    238 test_applied('<color>', 'tomato', true, 'rgb(255, 99, 71)');
    239 
    240 test_with_at_property({ syntax: '"*"', inherits: true }, (name, rule) => {
    241  try {
    242    outer.style.setProperty(name, 'foo');
    243    let actual = getComputedStyle(target).getPropertyValue(name);
    244    assert_equals(actual, 'foo');
    245  } finally {
    246    outer.style = '';
    247  }
    248 }, 'Rule applied for "*", even with no initial value');
    249 
    250 test_not_applied(undefined, 'green', false);
    251 test_not_applied('<color>', undefined, false);
    252 test_not_applied('<color>', 'green', undefined);
    253 test_not_applied('<gandalf>', 'grey', false);
    254 test_not_applied('gandalf', 'grey', false);
    255 test_not_applied('<color>', 'notacolor', false);
    256 test_not_applied('<length>', '10em', false);
    257 
    258 test_not_applied('<transform-function>', 'translateX(1em)', false);
    259 test_not_applied('<transform-function>', 'translateY(1lh)', false);
    260 test_not_applied('<transform-list>', 'rotate(10deg) translateX(1em)', false);
    261 test_not_applied('<transform-list>', 'rotate(10deg) translateY(1lh)', false);
    262 
    263 // Inheritance
    264 
    265 test_with_at_property({
    266  syntax: '"<length>"',
    267  inherits: false,
    268  initialValue: '0px'
    269 }, (name, rule) => {
    270  try {
    271    outer.style = `${name}: 40px`;
    272    assert_equals(getComputedStyle(outer).getPropertyValue(name), '40px');
    273    assert_equals(getComputedStyle(target).getPropertyValue(name), '0px');
    274  } finally {
    275    outer.style = '';
    276  }
    277 }, 'Non-inherited properties do not inherit');
    278 
    279 test_with_at_property({
    280  syntax: '"<length>"',
    281  inherits: true,
    282  initialValue: '0px'
    283 }, (name, rule) => {
    284  try {
    285    outer.style = `${name}: 40px`;
    286    assert_equals(getComputedStyle(outer).getPropertyValue(name), '40px');
    287    assert_equals(getComputedStyle(target).getPropertyValue(name), '40px');
    288  } finally {
    289    outer.style = '';
    290  }
    291 }, 'Inherited properties inherit');
    292 
    293 // Initial values
    294 
    295 test_with_at_property({
    296  syntax: '"<color>"',
    297  inherits: true,
    298  initialValue: 'green'
    299 }, (name, rule) => {
    300  try {
    301    target.style = `--x:var(${name})`;
    302    assert_equals(getComputedStyle(target).getPropertyValue(name), 'rgb(0, 128, 0)');
    303  } finally {
    304    target.style = '';
    305  }
    306 }, 'Initial values substituted as computed value');
    307 
    308 test_with_at_property({
    309  syntax: '"<length>"',
    310  inherits: false,
    311  initialValue: undefined
    312 }, (name, rule) => {
    313  try {
    314    target.style = `${name}: calc(1px + 1px);`;
    315    assert_equals(getComputedStyle(target).getPropertyValue(name), 'calc(1px + 1px)');
    316  } finally {
    317    target.style = '';
    318  }
    319 }, 'Non-universal registration are invalid without an initial value');
    320 
    321 test_with_at_property({
    322  syntax: '"*"',
    323  inherits: false,
    324  initialValue: undefined
    325 }, (name, rule) => {
    326  try {
    327    // If the registration suceeded, ${name} does *not* inherit, and hence
    328    // the computed value on 'target' should be empty.
    329    outer.style = `${name}: calc(1px + 1px);`;
    330    assert_equals(getComputedStyle(target).getPropertyValue(name), '');
    331  } finally {
    332    outer.style = '';
    333  }
    334 }, 'Initial value may be omitted for universal registration');
    335 
    336 </script>