tor-browser

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

utils.js (8496B)


      1 let next_property_id = 1;
      2 
      3 // Generate a unique property name on the form --prop-N.
      4 function generate_name() {
      5  return `--prop-${next_property_id++}`;
      6 }
      7 
      8 // Produce a compatible initial value for the specified syntax.
      9 function any_initial_value(syntax) {
     10  let components = syntax.split('|').map(x => x.trim())
     11  let first_component = components[0];
     12 
     13  if (first_component.endsWith('+') || first_component.endsWith('#'))
     14    first_component = first_component.slice(0, -1);
     15 
     16  switch (first_component) {
     17    case '*':
     18    case '<custom-ident>':
     19      return 'NULL';
     20    case '<angle>':
     21      return '0deg';
     22    case '<color>':
     23      return 'rgb(0, 0, 0)';
     24    case '<image>':
     25    case '<url>':
     26      return 'url(0)';
     27    case '<integer>':
     28    case '<length-percentage>':
     29    case '<length>':
     30    case '<number>':
     31      return '0';
     32    case '<percentage>':
     33      return '0%';
     34    case '<resolution>':
     35      return '0dpi';
     36    case '<time>':
     37      return '0s';
     38    case '<transform-function>':
     39    case '<transform-list>':
     40      return 'matrix(0, 0, 0, 0, 0, 0)';
     41    default:
     42      // We assume syntax is a specific custom ident.
     43      return first_component;
     44  }
     45 }
     46 
     47 // Registers a unique property on the form '--prop-N' and returns the name.
     48 // Any value except 'syntax' may be omitted, in which case the property will
     49 // not inherit, and some undefined (but compatible) initial value will be
     50 // generated. If a single string is used as the argument, it is assumed to be
     51 // the syntax.
     52 function generate_property(reg) {
     53  // Verify that only valid keys are specified. This prevents the caller from
     54  // accidentally supplying 'inherited' instead of 'inherits', for example.
     55  if (typeof(reg) === 'object') {
     56    const permitted = new Set(['name', 'syntax', 'initialValue', 'inherits']);
     57    if (!Object.keys(reg).every(k => permitted.has(k)))
     58      throw new Error('generate_property: invalid parameter');
     59  }
     60 
     61  let syntax = typeof(reg) === 'string' ? reg : reg.syntax;
     62  let initial = typeof(reg.initialValue) === 'undefined' ? any_initial_value(syntax)
     63                                                         : reg.initialValue;
     64  let inherits = typeof(reg.inherits) === 'undefined' ? false : reg.inherits;
     65 
     66  let name = generate_name();
     67  CSS.registerProperty({
     68    name: name,
     69    syntax: syntax,
     70    initialValue: initial,
     71    inherits: inherits
     72  });
     73  return name;
     74 }
     75 
     76 function all_syntaxes() {
     77  return [
     78    '*',
     79    '<angle>',
     80    '<color>',
     81    '<custom-ident>',
     82    '<image>',
     83    '<integer>',
     84    '<length-percentage>',
     85    '<length>',
     86    '<number>',
     87    '<percentage>',
     88    '<resolution>',
     89    '<time>',
     90    '<transform-function>',
     91    '<transform-list>',
     92    '<url>'
     93  ]
     94 }
     95 
     96 function with_style_node(text, fn) {
     97  let node = document.createElement('style');
     98  node.textContent = text;
     99  try {
    100    document.body.append(node);
    101    fn(node);
    102  } finally {
    103    node.remove();
    104  }
    105 }
    106 
    107 function with_at_property(desc, fn) {
    108  let name = typeof(desc.name) === 'undefined' ? generate_name() : desc.name;
    109  let text = `@property ${name} {`;
    110  if (typeof(desc.syntax) !== 'undefined')
    111    text += `syntax:${desc.syntax};`;
    112  if (typeof(desc.initialValue) !== 'undefined')
    113    text += `initial-value:${desc.initialValue};`;
    114  if (typeof(desc.inherits) !== 'undefined')
    115    text += `inherits:${desc.inherits};`;
    116  text += '}';
    117  with_style_node(text, (node) => fn(name, node.sheet.rules[0]));
    118 }
    119 
    120 function test_with_at_property(desc, fn, description) {
    121  test(() => with_at_property(desc, fn), description);
    122 }
    123 
    124 function test_with_style_node(text, fn, description) {
    125  test(() => with_style_node(text, fn), description);
    126 }
    127 
    128 function animation_test(property, values, description) {
    129  const name = generate_name();
    130  property.name = name;
    131  CSS.registerProperty(property);
    132 
    133  test(() => {
    134    const duration = 1000;
    135    const keyframes = {};
    136    keyframes[name] = values.keyframes;
    137 
    138    const iterations = 3;
    139    const composite = values.composite || "replace";
    140    const iterationComposite = values.iterationComposite || "replace";
    141    const animation = target.animate(keyframes, { composite, iterationComposite, iterations, duration });
    142    animation.pause();
    143    // We seek to the middle of the third iteration which will allow to test cases where
    144    // iterationComposite is set to something other than "replace".
    145    animation.currentTime = duration * 2.5;
    146 
    147    const assert_equals_function = values.assert_function || assert_equals;
    148    assert_equals_function(getComputedStyle(target).getPropertyValue(name), values.expected);
    149  }, description);
    150 };
    151 
    152 function discrete_animation_test(syntax, fromValue, toValue, description) {
    153  test(() => {
    154    const name = generate_name();
    155 
    156    CSS.registerProperty({
    157      name,
    158      syntax,
    159      inherits: false,
    160      initialValue: fromValue
    161    });
    162 
    163    const duration = 1000;
    164    const keyframes = [];
    165    keyframes[name] = toValue;
    166    const animation = target.animate(keyframes, duration);
    167    animation.pause();
    168 
    169    const checkAtProgress = (progress, expected) => {
    170      animation.currentTime = duration * 0.25;
    171      assert_equals(getComputedStyle(target).getPropertyValue(name), fromValue, `The correct value is used at progress = ${progress}`);
    172    };
    173 
    174    checkAtProgress(0, fromValue);
    175    checkAtProgress(0.25, fromValue);
    176    checkAtProgress(0.49, fromValue);
    177    checkAtProgress(0.5, toValue);
    178    checkAtProgress(0.75, toValue);
    179    checkAtProgress(1, toValue);
    180  }, description || `Animating a custom property of type ${syntax} is discrete`);
    181 }
    182 
    183 function transition_test(options, description) {
    184  promise_test(async () => {
    185    const customProperty = generate_name();
    186 
    187    options.transitionProperty ??= customProperty;
    188 
    189    CSS.registerProperty({
    190      name: customProperty,
    191      syntax: options.syntax,
    192      inherits: false,
    193      initialValue: options.from
    194    });
    195 
    196    assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.from, "Element has the expected initial value");
    197 
    198    const transitionEventPromise = new Promise(resolve => {
    199      let listener = event => {
    200          target.removeEventListener("transitionrun", listener);
    201          assert_equals(event.propertyName, customProperty, "TransitionEvent has the expected property name");
    202          resolve();
    203      };
    204      target.addEventListener("transitionrun", listener);
    205    });
    206 
    207    target.style.transition = `${options.transitionProperty} 1s -500ms linear`;
    208    if (options.behavior) {
    209      target.style.transitionBehavior = options.behavior;
    210    }
    211    target.style.setProperty(customProperty, options.to);
    212 
    213    const animations = target.getAnimations();
    214    assert_equals(animations.length, 1, "A single animation is running");
    215 
    216    const transition = animations[0];
    217    assert_class_string(transition, "CSSTransition", "A CSSTransition is running");
    218 
    219    transition.pause();
    220    assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.expected, "Element has the expected animated value");
    221 
    222    await transitionEventPromise;
    223  }, description);
    224 }
    225 
    226 function no_transition_test(options, description) {
    227  test(() => {
    228    const customProperty = generate_name();
    229 
    230    CSS.registerProperty({
    231      name: customProperty,
    232      syntax: options.syntax,
    233      inherits: false,
    234      initialValue: options.from
    235    });
    236 
    237    assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.from, "Element has the expected initial value");
    238 
    239    target.style.transition = `${customProperty} 1s -500ms linear`;
    240    target.style.setProperty(customProperty, options.to);
    241 
    242    assert_equals(target.getAnimations().length, 0, "No animation was created");
    243    assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.to, "Element has the expected final value");
    244  }, description);
    245 };
    246 
    247 function test_initial_value_valid(syntax, initialValue) {
    248    // No actual assertions, this just shouldn't throw
    249    test(() => {
    250        var name = generate_name();
    251        CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false});
    252    }, "syntax:'" + syntax + "', initialValue:'" + initialValue + "' is valid");
    253 }
    254 
    255 function test_initial_value_invalid(syntax, initialValue) {
    256    test(() =>{
    257        var name = generate_name();
    258        assert_throws_dom("SyntaxError",
    259            () => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false}));
    260    }, "syntax:'" + syntax + "', initialValue:'" + initialValue + "' is invalid");
    261 }