tor-browser

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

SummaryGraphPath.js (8152B)


      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  Component,
      9  createFactory,
     10 } = require("resource://devtools/client/shared/vendor/react.mjs");
     11 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     12 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     13 const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.mjs");
     14 
     15 const ComputedTimingPath = createFactory(
     16  require("resource://devtools/client/inspector/animation/components/graph/ComputedTimingPath.js")
     17 );
     18 const EffectTimingPath = createFactory(
     19  require("resource://devtools/client/inspector/animation/components/graph/EffectTimingPath.js")
     20 );
     21 const NegativeDelayPath = createFactory(
     22  require("resource://devtools/client/inspector/animation/components/graph/NegativeDelayPath.js")
     23 );
     24 const NegativeEndDelayPath = createFactory(
     25  require("resource://devtools/client/inspector/animation/components/graph/NegativeEndDelayPath.js")
     26 );
     27 const {
     28  DEFAULT_GRAPH_HEIGHT,
     29 } = require("resource://devtools/client/inspector/animation/utils/graph-helper.js");
     30 
     31 // Minimum opacity for semitransparent fill color for keyframes's easing graph.
     32 const MIN_KEYFRAMES_EASING_OPACITY = 0.5;
     33 
     34 class SummaryGraphPath extends Component {
     35  static get propTypes() {
     36    return {
     37      animation: PropTypes.object.isRequired,
     38      getAnimatedPropertyMap: PropTypes.object.isRequired,
     39      simulateAnimation: PropTypes.func.isRequired,
     40      timeScale: PropTypes.object.isRequired,
     41    };
     42  }
     43 
     44  constructor(props) {
     45    super(props);
     46 
     47    this.state = {
     48      // Duration which can display in one pixel.
     49      durationPerPixel: 0,
     50      // To avoid rendering while the state is updating
     51      // since we call an async function in updateState.
     52      isStateUpdating: false,
     53      // List of keyframe which consists by only offset and easing.
     54      keyframesList: [],
     55    };
     56  }
     57 
     58  componentDidMount() {
     59    // No need to set isStateUpdating state since paint sequence is finish here.
     60    this.updateState(this.props);
     61  }
     62 
     63  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
     64  UNSAFE_componentWillReceiveProps(nextProps) {
     65    this.setState({ isStateUpdating: true });
     66    this.updateState(nextProps);
     67  }
     68 
     69  shouldComponentUpdate(nextProps, nextState) {
     70    return !nextState.isStateUpdating;
     71  }
     72 
     73  /**
     74   * Return animatable keyframes list which has only offset and easing.
     75   * Also, this method remove duplicate keyframes.
     76   * For example, if the given animatedPropertyMap is,
     77   * [
     78   *   {
     79   *     key: "color",
     80   *     values: [
     81   *       {
     82   *         offset: 0,
     83   *         easing: "ease",
     84   *         value: "rgb(255, 0, 0)",
     85   *       },
     86   *       {
     87   *         offset: 1,
     88   *         value: "rgb(0, 255, 0)",
     89   *       },
     90   *     ],
     91   *   },
     92   *   {
     93   *     key: "opacity",
     94   *     values: [
     95   *       {
     96   *         offset: 0,
     97   *         easing: "ease",
     98   *         value: 0,
     99   *       },
    100   *       {
    101   *         offset: 1,
    102   *         value: 1,
    103   *       },
    104   *     ],
    105   *   },
    106   * ]
    107   *
    108   * then this method returns,
    109   * [
    110   *   [
    111   *     {
    112   *       offset: 0,
    113   *       easing: "ease",
    114   *     },
    115   *     {
    116   *       offset: 1,
    117   *     },
    118   *   ],
    119   * ]
    120   *
    121   * @param {Map} animated property map
    122   *        which can get form getAnimatedPropertyMap in animation.js
    123   * @return {Array} list of keyframes which has only easing and offset.
    124   */
    125  getOffsetAndEasingOnlyKeyframes(animatedPropertyMap) {
    126    return [...animatedPropertyMap.values()]
    127      .filter((keyframes1, i, self) => {
    128        return (
    129          i !==
    130          self.findIndex((keyframes2, j) => {
    131            return this.isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2)
    132              ? j
    133              : -1;
    134          })
    135        );
    136      })
    137      .map(keyframes => {
    138        return keyframes.map(keyframe => {
    139          return { easing: keyframe.easing, offset: keyframe.offset };
    140        });
    141      });
    142  }
    143 
    144  /**
    145   * Return true if given keyframes have same length, offset and easing.
    146   *
    147   * @param {Array} keyframes1
    148   * @param {Array} keyframes2
    149   * @return {boolean} true: equals
    150   */
    151  isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2) {
    152    if (keyframes1.length !== keyframes2.length) {
    153      return false;
    154    }
    155 
    156    for (let i = 0; i < keyframes1.length; i++) {
    157      const keyframe1 = keyframes1[i];
    158      const keyframe2 = keyframes2[i];
    159 
    160      if (
    161        keyframe1.offset !== keyframe2.offset ||
    162        keyframe1.easing !== keyframe2.easing
    163      ) {
    164        return false;
    165      }
    166    }
    167 
    168    return true;
    169  }
    170 
    171  updateState(props) {
    172    const { animation, getAnimatedPropertyMap, timeScale } = props;
    173 
    174    let animatedPropertyMap = null;
    175    let thisEl = null;
    176 
    177    try {
    178      animatedPropertyMap = getAnimatedPropertyMap(animation);
    179      thisEl = ReactDOM.findDOMNode(this);
    180    } catch (e) {
    181      // Expected if we've already been destroyed or other node have been selected
    182      // in the meantime.
    183      console.error(e);
    184      return;
    185    }
    186 
    187    const keyframesList =
    188      this.getOffsetAndEasingOnlyKeyframes(animatedPropertyMap);
    189    const totalDuration =
    190      timeScale.getDuration() * Math.abs(animation.state.playbackRate);
    191    const durationPerPixel = totalDuration / thisEl.parentNode.clientWidth;
    192 
    193    this.setState({
    194      durationPerPixel,
    195      isStateUpdating: false,
    196      keyframesList,
    197    });
    198  }
    199 
    200  render() {
    201    const { durationPerPixel, keyframesList } = this.state;
    202    const { animation, simulateAnimation, timeScale } = this.props;
    203 
    204    if (!durationPerPixel || !animation.state.type) {
    205      // Undefined animation.state.type means that the animation had been removed already.
    206      // Even if the animation was removed, we still need the empty svg since the
    207      // component might be re-used.
    208      return dom.svg();
    209    }
    210 
    211    const { playbackRate } = animation.state;
    212    const { createdTime } = animation.state.absoluteValues;
    213    const absPlaybackRate = Math.abs(playbackRate);
    214 
    215    // Absorb the playbackRate in viewBox of SVG and offset of child path elements
    216    // in order to each graph path components can draw without considering to the
    217    // playbackRate.
    218    const offset = createdTime * absPlaybackRate;
    219    const startTime = timeScale.minStartTime * absPlaybackRate;
    220    const totalDuration = timeScale.getDuration() * absPlaybackRate;
    221    const opacity = Math.max(
    222      1 / keyframesList.length,
    223      MIN_KEYFRAMES_EASING_OPACITY
    224    );
    225 
    226    return dom.svg(
    227      {
    228        className: "animation-summary-graph-path",
    229        preserveAspectRatio: "none",
    230        viewBox:
    231          `${startTime} -${DEFAULT_GRAPH_HEIGHT} ` +
    232          `${totalDuration} ${DEFAULT_GRAPH_HEIGHT}`,
    233      },
    234      keyframesList.map(keyframes =>
    235        ComputedTimingPath({
    236          animation,
    237          durationPerPixel,
    238          keyframes,
    239          offset,
    240          opacity,
    241          simulateAnimation,
    242          totalDuration,
    243        })
    244      ),
    245      animation.state.easing !== "linear"
    246        ? EffectTimingPath({
    247            animation,
    248            durationPerPixel,
    249            offset,
    250            simulateAnimation,
    251            totalDuration,
    252          })
    253        : null,
    254      animation.state.delay < 0
    255        ? keyframesList.map(keyframes => {
    256            return NegativeDelayPath({
    257              animation,
    258              durationPerPixel,
    259              keyframes,
    260              offset,
    261              simulateAnimation,
    262              totalDuration,
    263            });
    264          })
    265        : null,
    266      animation.state.iterationCount && animation.state.endDelay < 0
    267        ? keyframesList.map(keyframes => {
    268            return NegativeEndDelayPath({
    269              animation,
    270              durationPerPixel,
    271              keyframes,
    272              offset,
    273              simulateAnimation,
    274              totalDuration,
    275            });
    276          })
    277        : null
    278    );
    279  }
    280 }
    281 
    282 module.exports = SummaryGraphPath;