tor-browser

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

ComputedStylePath.js (6710B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  PureComponent,
      9 } = require("resource://devtools/client/shared/vendor/react.mjs");
     10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     12 
     13 const {
     14  createPathSegments,
     15  DEFAULT_DURATION_RESOLUTION,
     16  getPreferredProgressThresholdByKeyframes,
     17  toPathString,
     18 } = require("resource://devtools/client/inspector/animation/utils/graph-helper.js");
     19 
     20 /**
     21 * This class is an abstraction for computed style path of keyframes.
     22 * Subclass of this should implement the following methods:
     23 *
     24 * getPropertyName()
     25 *   Returns property name which will be animated.
     26 *
     27 *   @return {string}
     28 *           e.g. opacity
     29 *
     30 * getPropertyValue(keyframe)
     31 *   Returns value which uses as animated keyframe value from given parameter.
     32 *
     33 *   @param {object} keyframe
     34 *   @return {String||Number}
     35 *           e.g. 0
     36 *
     37 * toSegmentValue(computedStyle)
     38 *   Convert computed style to segment value of graph.
     39 *
     40 *   @param {String||Number}
     41 *          e.g. 0
     42 *   @return {number}
     43 *          e.g. 0 (should be 0 - 1.0)
     44 */
     45 class ComputedStylePath extends PureComponent {
     46  static get propTypes() {
     47    return {
     48      componentWidth: PropTypes.number.isRequired,
     49      easingHintStrokeWidth: PropTypes.number.isRequired,
     50      graphHeight: PropTypes.number.isRequired,
     51      keyframes: PropTypes.array.isRequired,
     52      simulateAnimation: PropTypes.func.isRequired,
     53      totalDuration: PropTypes.number.isRequired,
     54    };
     55  }
     56 
     57  /**
     58   * Return an array containing the path segments between the given start and
     59   * end keyframe values.
     60   *
     61   * @param {object} startKeyframe
     62   *        Starting keyframe.
     63   * @param {object} endKeyframe
     64   *        Ending keyframe.
     65   * @return {Array}
     66   *         Array of path segment.
     67   *         [{x: {Number} time, y: {Number} segment value}, ...]
     68   */
     69  getPathSegments(startKeyframe, endKeyframe) {
     70    const { componentWidth, simulateAnimation, totalDuration } = this.props;
     71 
     72    const propertyName = this.getPropertyName();
     73    const offsetDistance = endKeyframe.offset - startKeyframe.offset;
     74    const duration = offsetDistance * totalDuration;
     75 
     76    const keyframes = [startKeyframe, endKeyframe].map((keyframe, index) => {
     77      return {
     78        offset: index,
     79        easing: keyframe.easing,
     80        [getJsPropertyName(propertyName)]: this.getPropertyValue(keyframe),
     81      };
     82    });
     83    const effect = {
     84      duration,
     85      fill: "forwards",
     86    };
     87 
     88    const simulatedAnimation = simulateAnimation(keyframes, effect, true);
     89 
     90    if (!simulatedAnimation) {
     91      return null;
     92    }
     93 
     94    const simulatedElement = simulatedAnimation.effect.target;
     95    const win = simulatedElement.ownerGlobal;
     96    const threshold = getPreferredProgressThresholdByKeyframes(keyframes);
     97 
     98    const getSegment = time => {
     99      simulatedAnimation.currentTime = time;
    100      const computedStyle = win
    101        .getComputedStyle(simulatedElement)
    102        .getPropertyValue(propertyName);
    103 
    104      return {
    105        computedStyle,
    106        x: time,
    107        y: this.toSegmentValue(computedStyle),
    108      };
    109    };
    110 
    111    const segments = createPathSegments(
    112      0,
    113      duration,
    114      duration / componentWidth,
    115      threshold,
    116      DEFAULT_DURATION_RESOLUTION,
    117      getSegment
    118    );
    119    const offset = startKeyframe.offset * totalDuration;
    120 
    121    for (const segment of segments) {
    122      segment.x += offset;
    123    }
    124 
    125    return segments;
    126  }
    127 
    128  /**
    129   * Render easing hint from given path segments.
    130   *
    131   * @param {Array} segments
    132   *        Path segments.
    133   * @return {Element}
    134   *         Element which represents easing hint.
    135   */
    136  renderEasingHint(segments) {
    137    const { easingHintStrokeWidth, keyframes, totalDuration } = this.props;
    138 
    139    const hints = [];
    140 
    141    for (let i = 0, indexOfSegments = 0; i < keyframes.length - 1; i++) {
    142      const startKeyframe = keyframes[i];
    143      const endKeyframe = keyframes[i + 1];
    144      const endTime = endKeyframe.offset * totalDuration;
    145      const hintSegments = [];
    146 
    147      for (; indexOfSegments < segments.length; indexOfSegments++) {
    148        const segment = segments[indexOfSegments];
    149        hintSegments.push(segment);
    150 
    151        if (startKeyframe.offset === endKeyframe.offset) {
    152          hintSegments.push(segments[++indexOfSegments]);
    153          break;
    154        } else if (segment.x === endTime) {
    155          break;
    156        }
    157      }
    158 
    159      const g = dom.g(
    160        {
    161          className: "hint",
    162        },
    163        dom.title({}, startKeyframe.easing),
    164        dom.path({
    165          d:
    166            `M${hintSegments[0].x},${hintSegments[0].y} ` +
    167            toPathString(hintSegments),
    168          style: {
    169            "stroke-width": easingHintStrokeWidth,
    170          },
    171        })
    172      );
    173 
    174      hints.push(g);
    175    }
    176 
    177    return hints;
    178  }
    179 
    180  /**
    181   * Render graph. This method returns React dom.
    182   *
    183   * @return {Element}
    184   */
    185  renderGraph() {
    186    const { keyframes } = this.props;
    187 
    188    const segments = [];
    189 
    190    for (let i = 0; i < keyframes.length - 1; i++) {
    191      const startKeyframe = keyframes[i];
    192      const endKeyframe = keyframes[i + 1];
    193      const keyframesSegments = this.getPathSegments(
    194        startKeyframe,
    195        endKeyframe
    196      );
    197 
    198      if (!keyframesSegments) {
    199        return null;
    200      }
    201 
    202      segments.push(...keyframesSegments);
    203    }
    204 
    205    return [this.renderPathSegments(segments), this.renderEasingHint(segments)];
    206  }
    207 
    208  /**
    209   * Return react dom fron given path segments.
    210   *
    211   * @param {Array} segments
    212   * @param {object} style
    213   * @return {Element}
    214   */
    215  renderPathSegments(segments, style) {
    216    const { graphHeight } = this.props;
    217 
    218    for (const segment of segments) {
    219      segment.y *= graphHeight;
    220    }
    221 
    222    let d = `M${segments[0].x},0 `;
    223    d += toPathString(segments);
    224    d += `L${segments[segments.length - 1].x},0 Z`;
    225 
    226    return dom.path({ d, style });
    227  }
    228 }
    229 
    230 /**
    231 * Convert given CSS property name to JavaScript CSS name.
    232 *
    233 * @param {string} cssPropertyName
    234 *        CSS property name (e.g. background-color).
    235 * @return {string}
    236 *         JavaScript CSS property name (e.g. backgroundColor).
    237 */
    238 function getJsPropertyName(cssPropertyName) {
    239  if (cssPropertyName == "float") {
    240    return "cssFloat";
    241  }
    242  // https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
    243  return cssPropertyName.replace(/-([a-z])/gi, (str, group) => {
    244    return group.toUpperCase();
    245  });
    246 }
    247 
    248 module.exports = ComputedStylePath;