Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/client/inspector/animation/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 27 kB image not shown  

Quelle  animation.js

  Sprache: JAVA
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


"use strict";

const {
  createElement,
  createFactory,
} = require("resource://devtools/client/shared/vendor/react.mjs");
const {
  Provider,
} = require("resource://devtools/client/shared/vendor/react-redux.js");

const EventEmitter = require("resource://devtools/shared/event-emitter.js");

const App = createFactory(
  require("resource://devtools/client/inspector/animation/components/App.js")
);
const CurrentTimeTimer = require("resource://devtools/client/inspector/animation/current-time-timer.js");

const animationsReducer = require("resource://devtools/client/inspector/animation/reducers/animations.js");
const {
  updateAnimations,
  updateDetailVisibility,
  updateElementPickerEnabled,
  updateHighlightedNode,
  updatePlaybackRateMultiplier,
  updateSelectedAnimation,
  updateSidebarSize,
} = require("resource://devtools/client/inspector/animation/actions/animations.js");
const {
  hasAnimationIterationCountInfinite,
  hasRunningAnimation,
} = require("resource://devtools/client/inspector/animation/utils/utils.js");

class AnimationInspector extends EventEmitter {
  constructor(inspector, win) {
    super();

    this.inspector = inspector;
    this.win = win;

    this.inspector.store.injectReducer("animations", animationsReducer);

    this.addAnimationsCurrentTimeListener =
      this.addAnimationsCurrentTimeListener.bind(this);
    this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
    this.getAnimationsCurrentTime = this.getAnimationsCurrentTime.bind(this);
    this.getComputedStyle = this.getComputedStyle.bind(this);
    this.getNodeFromActor = this.getNodeFromActor.bind(this);
    this.removeAnimationsCurrentTimeListener =
      this.removeAnimationsCurrentTimeListener.bind(this);
    this.rewindAnimationsCurrentTime =
      this.rewindAnimationsCurrentTime.bind(this);
    this.selectAnimation = this.selectAnimation.bind(this);
    this.setAnimationsCurrentTime = this.setAnimationsCurrentTime.bind(this);
    this.setAnimationsPlaybackRateMultiplier =
      this.setAnimationsPlaybackRateMultiplier.bind(this);
    this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
    this.setDetailVisibility = this.setDetailVisibility.bind(this);
    this.setHighlightedNode = this.setHighlightedNode.bind(this);
    this.setSelectedNode = this.setSelectedNode.bind(this);
    this.simulateAnimation = this.simulateAnimation.bind(this);
    this.simulateAnimationForKeyframesProgressBar =
      this.simulateAnimationForKeyframesProgressBar.bind(this);
    this.toggleElementPicker = this.toggleElementPicker.bind(this);
    this.watchAnimationsForSelectedNode =
      this.watchAnimationsForSelectedNode.bind(this);
    this.unwatchAnimationsForSelectedNode =
      this.unwatchAnimationsForSelectedNode.bind(this);
    this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
    this.onAnimationsCurrentTimeUpdated =
      this.onAnimationsCurrentTimeUpdated.bind(this);
    this.onAnimationsMutation = this.onAnimationsMutation.bind(this);
    this.onCurrentTimeTimerUpdated = this.onCurrentTimeTimerUpdated.bind(this);
    this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
    this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
    this.onNewNodeFront = this.onNewNodeFront.bind(this);
    this.onSidebarResized = this.onSidebarResized.bind(this);
    this.onSidebarSelectionChanged = this.onSidebarSelectionChanged.bind(this);

    this.emitForTests = this.emitForTests.bind(this);

    this.initComponents();
    this.initListeners();
  }

  initComponents() {
    const {
      addAnimationsCurrentTimeListener,
      emitForTests: emitEventForTest,
      getAnimatedPropertyMap,
      getAnimationsCurrentTime,
      getComputedStyle,
      getNodeFromActor,
      isAnimationsRunning,
      removeAnimationsCurrentTimeListener,
      rewindAnimationsCurrentTime,
      selectAnimation,
      setAnimationsCurrentTime,
      setAnimationsPlaybackRateMultiplier,
      setAnimationsPlayState,
      setDetailVisibility,
      setHighlightedNode,
      setSelectedNode,
      simulateAnimation,
      simulateAnimationForKeyframesProgressBar,
      toggleElementPicker,
    } = this;

    const direction = this.win.document.dir;

    this.animationsCurrentTimeListeners = [];
    this.isCurrentTimeSet = false;

    const provider = createElement(
      Provider,
      {
        id: "animationinspector",
        key: "animationinspector",
        store: this.inspector.store,
      },
      App({
        addAnimationsCurrentTimeListener,
        direction,
        emitEventForTest,
        getAnimatedPropertyMap,
        getAnimationsCurrentTime,
        getComputedStyle,
        getNodeFromActor,
        isAnimationsRunning,
        removeAnimationsCurrentTimeListener,
        rewindAnimationsCurrentTime,
        selectAnimation,
        setAnimationsCurrentTime,
        setAnimationsPlaybackRateMultiplier,
        setAnimationsPlayState,
        setDetailVisibility,
        setHighlightedNode,
        setSelectedNode,
        simulateAnimation,
        simulateAnimationForKeyframesProgressBar,
        toggleElementPicker,
      })
    );
    this.provider = provider;
  }

  async initListeners() {
    await this.watchAnimationsForSelectedNode({
      // During the initialization of the panel, this.isPanelVisible returns false,
      // since it's not ready yet.
      // We need to bypass the check in order to retrieve the animationsFront and fetch
      // the animations for the selected node.
      force: true,
    });

    this.inspector.selection.on("new-node-front"this.onNewNodeFront);
    this.inspector.sidebar.on("select"this.onSidebarSelectionChanged);
    this.inspector.toolbox.on("select"this.onSidebarSelectionChanged);
    this.inspector.toolbox.on(
      "inspector-sidebar-resized",
      this.onSidebarResized
    );
    this.inspector.toolbox.nodePicker.on(
      "picker-started",
      this.onElementPickerStarted
    );
    this.inspector.toolbox.nodePicker.on(
      "picker-stopped",
      this.onElementPickerStopped
    );
  }

  destroy() {
    this.setAnimationStateChangedListenerEnabled(false);
    this.inspector.selection.off(
      "new-node-front",
      this.watchAnimationsForSelectedNode
    );
    this.inspector.sidebar.off("select"this.onSidebarSelectionChanged);
    this.inspector.toolbox.off(
      "inspector-sidebar-resized",
      this.onSidebarResized
    );
    this.inspector.toolbox.nodePicker.off(
      "picker-started",
      this.onElementPickerStarted
    );
    this.inspector.toolbox.nodePicker.off(
      "picker-stopped",
      this.onElementPickerStopped
    );
    this.inspector.toolbox.off("select"this.onSidebarSelectionChanged);

    if (this.animationsFront) {
      this.animationsFront.off("mutations"this.onAnimationsMutation);
    }

    if (this.simulatedAnimation) {
      this.simulatedAnimation.cancel();
      this.simulatedAnimation = null;
    }

    if (this.simulatedElement) {
      this.simulatedElement.remove();
      this.simulatedElement = null;
    }

    if (this.simulatedAnimationForKeyframesProgressBar) {
      this.simulatedAnimationForKeyframesProgressBar.cancel();
      this.simulatedAnimationForKeyframesProgressBar = null;
    }

    this.stopAnimationsCurrentTimeTimer();

    this.inspector = null;
    this.win = null;
  }

  get state() {
    return this.inspector.store.getState().animations;
  }

  addAnimationsCurrentTimeListener(listener) {
    this.animationsCurrentTimeListeners.push(listener);
  }

  /**
   * This function calls AnimationsFront.setCurrentTimes with considering the createdTime.
   *
   * @param {number} currentTime
   */

  async doSetCurrentTimes(currentTime) {
    // If we don't have an animationsFront, it means that we don't have visible animations
    // so we can safely bail here.
    if (!this.animationsFront) {
      return;
    }

    const { animations, timeScale } = this.state;
    currentTime = currentTime + timeScale.minStartTime;
    await this.animationsFront.setCurrentTimes(animations, currentTime, true);
  }

  /**
   * Return a map of animated property from given animation actor.
   *
   * @param {object} animation
   * @return {Map} A map of animated property
   *         key: {String} Animated property name
   *         value: {Array} Array of keyframe object
   *         Also, the keyframe object is consisted as following.
   *         {
   *           value: {String} style,
   *           offset: {Number} offset of keyframe,
   *           easing: {String} easing from this keyframe to next keyframe,
   *           distance: {Number} use as y coordinate in graph,
   *         }
   */

  getAnimatedPropertyMap(animation) {
    const properties = animation.state.properties;
    const animatedPropertyMap = new Map();

    for (const { name, values } of properties) {
      const keyframes = values.map(
        ({ value, offset, easing, distance = 0 }) => {
          offset = parseFloat(offset.toFixed(3));
          return { value, offset, easing, distance };
        }
      );

      animatedPropertyMap.set(name, keyframes);
    }

    return animatedPropertyMap;
  }

  getAnimationsCurrentTime() {
    return this.currentTime;
  }

  /**
   * Return the computed style of the specified property after setting the given styles
   * to the simulated element.
   *
   * @param {string} property
   *        CSS property name (e.g. text-align).
   * @param {object} styles
   *        Map of CSS property name and value.
   * @return {string}
   *         Computed style of property.
   */

  getComputedStyle(property, styles) {
    this.simulatedElement.style.cssText = "";

    for (const propertyName in styles) {
      this.simulatedElement.style.setProperty(
        propertyName,
        styles[propertyName]
      );
    }

    return this.win
      .getComputedStyle(this.simulatedElement)
      .getPropertyValue(property);
  }

  getNodeFromActor(actorID) {
    if (!this.inspector) {
      return Promise.reject("Animation inspector already destroyed");
    }

    if (!this.animationsFront?.walker) {
      return Promise.reject("No animations front walker");
    }

    return this.animationsFront.walker.getNodeFromActor(actorID, ["node"]);
  }

  isPanelVisible() {
    return (
      this.inspector &&
      this.inspector.toolbox &&
      this.inspector.sidebar &&
      this.inspector.toolbox.currentToolId === "inspector" &&
      this.inspector.sidebar.getCurrentTabID() === "animationinspector"
    );
  }

  onAnimationStateChanged() {
    // Simply update the animations since the state has already been updated.
    this.fireUpdateAction([...this.state.animations]);
  }

  /**
   * This method should call when the current time is changed.
   * Then, dispatches the current time to listeners that are registered
   * by addAnimationsCurrentTimeListener.
   *
   * @param {number} currentTime
   */

  onAnimationsCurrentTimeUpdated(currentTime) {
    this.currentTime = currentTime;

    for (const listener of this.animationsCurrentTimeListeners) {
      listener(currentTime);
    }
  }

  /**
   * This method is called when the current time proceed by CurrentTimeTimer.
   *
   * @param {number} currentTime
   * @param {Bool} shouldStop
   */

  onCurrentTimeTimerUpdated(currentTime, shouldStop) {
    if (shouldStop) {
      this.setAnimationsCurrentTime(currentTime, true);
    } else {
      this.onAnimationsCurrentTimeUpdated(currentTime);
    }
  }

  async onAnimationsMutation(changes) {
    let animations = [...this.state.animations];
    const addedAnimations = [];

    for (const { type, player: animation } of changes) {
      if (type === "added") {
        if (!animation.state.type) {
          // This animation was added but removed immediately.
          continue;
        }

        addedAnimations.push(animation);
        animation.on("changed"this.onAnimationStateChanged);
      } else if (type === "removed") {
        const index = animations.indexOf(animation);

        if (index < 0) {
          // This animation was added but removed immediately.
          continue;
        }

        animations.splice(index, 1);
        animation.off("changed"this.onAnimationStateChanged);
      }
    }

    // Update existing other animations as well since the currentTime would be proceeded
    // sice the scrubber position is related the currentTime.
    // Also, don't update the state of removed animations since React components
    // may refer to the same instance still.
    try {
      animations = await this.refreshAnimationsState(animations);
    } catch (_) {
      console.error(`Updating Animations failed`);
      return;
    }

    this.fireUpdateAction(animations.concat(addedAnimations));
  }

  onElementPickerStarted() {
    this.inspector.store.dispatch(updateElementPickerEnabled(true));
  }

  onElementPickerStopped() {
    this.inspector.store.dispatch(updateElementPickerEnabled(false));
  }

  async onSidebarSelectionChanged() {
    const isPanelVisibled = this.isPanelVisible();

    if (this.wasPanelVisibled === isPanelVisibled) {
      // onSidebarSelectionChanged is called some times even same state
      // from sidebar and toolbar.
      return;
    }

    this.wasPanelVisibled = isPanelVisibled;

    if (this.isPanelVisible()) {
      await this.watchAnimationsForSelectedNode();
      this.onSidebarResized(nullthis.inspector.getSidebarSize());
    } else {
      await this.unwatchAnimationsForSelectedNode();
      this.stopAnimationsCurrentTimeTimer();
    }
  }

  onSidebarResized(size) {
    if (!this.isPanelVisible()) {
      return;
    }

    this.inspector.store.dispatch(updateSidebarSize(size));
  }

  removeAnimationsCurrentTimeListener(listener) {
    this.animationsCurrentTimeListeners =
      this.animationsCurrentTimeListeners.filter(l => l !== listener);
  }

  async rewindAnimationsCurrentTime() {
    const { timeScale } = this.state;
    await this.setAnimationsCurrentTime(timeScale.zeroPositionTime, true);
  }

  selectAnimation(animation) {
    this.inspector.store.dispatch(updateSelectedAnimation(animation));
  }

  async setSelectedNode(nodeFront) {
    if (this.inspector.selection.nodeFront === nodeFront) {
      return;
    }

    await this.inspector
      .getCommonComponentProps()
      .setSelectedNode(nodeFront, { reason: "animation-panel" });
  }

  async setAnimationsCurrentTime(currentTime, shouldRefresh) {
    this.stopAnimationsCurrentTimeTimer();
    this.onAnimationsCurrentTimeUpdated(currentTime);

    if (!shouldRefresh && this.isCurrentTimeSet) {
      return;
    }

    let animations = this.state.animations;
    this.isCurrentTimeSet = true;

    try {
      await this.doSetCurrentTimes(currentTime);
      animations = await this.refreshAnimationsState(animations);
    } catch (e) {
      // Expected if we've already been destroyed or other node have been selected
      // in the meantime.
      console.error(e);
      return;
    }

    this.isCurrentTimeSet = false;

    if (shouldRefresh) {
      this.fireUpdateAction(animations);
    }
  }

  async setAnimationsPlaybackRateMultiplier(multiplier) {
    if (!this.inspector) {
      return// Already destroyed or another node selected.
    }

    let { animations } = this.state;

    // "changed" event on each animation will fire respectively when the playback
    // rate changed. Since for each occurrence of event, change of UI is urged.
    // To avoid this, disable the listeners once in order to not capture the event.
    this.setAnimationStateChangedListenerEnabled(false);

    const wasRunning = hasRunningAnimation(animations);

    // We only have an animationsFront if the selected node can have animations in its
    // subtree (that excludes doctype, comment or text nodes for example)
    if (this.animationsFront) {
      // Pause the animations so we have a clean slate to set the multiplier.
      // If the animation was running, we'll resume it after setting the multiplier.
      // TODO: Eventually this should be handled by the platform, where the currentTime
      // should be adjusted, but that requires more work and we want to make this feature
      // available as soon as possible.
      await this.animationsFront.pauseSome(animations);
    }

    try {
      await this.inspector.commands.targetConfigurationCommand.updateConfiguration(
        {
          animationsPlayBackRateMultiplier: multiplier,
        }
      );

      if (wasRunning) {
        await this.animationsFront.playSome(animations);
      }
      this.inspector.store.dispatch(updatePlaybackRateMultiplier(multiplier));
      animations = await this.refreshAnimationsState(animations);
    } catch (e) {
      // Expected if we've already been destroyed (e.g. this.inspector is null)
      if (!this.inspector) {
        console.error(e);
        return;
      }

      // Actually throw the error if the animation panel isn't destroyed.
      throw new Error(e);
    } finally {
      this.setAnimationStateChangedListenerEnabled(true);
    }

    if (animations) {
      await this.fireUpdateAction(animations);
    }

    this.emitForTests("playbackrate-multiplier-updated");
  }

  async setAnimationsPlayState(doPlay) {
    if (!this.inspector) {
      return// Already destroyed or another node selected.
    }

    // If we don't have an animationsFront, it means that we don't have visible animations
    // so we can safely bail here.
    if (!this.animationsFront) {
      return;
    }

    let { animations, timeScale } = this.state;

    try {
      if (
        doPlay &&
        animations.every(
          animation =>
            timeScale.getEndTime(animation) <= animation.state.currentTime
        )
      ) {
        await this.doSetCurrentTimes(timeScale.zeroPositionTime);
      }

      if (doPlay) {
        await this.animationsFront.playSome(animations);
      } else {
        await this.animationsFront.pauseSome(animations);
      }

      animations = await this.refreshAnimationsState(animations);
    } catch (e) {
      // Expected if we've already been destroyed or other node have been selected
      // in the meantime.
      console.error(e);
      return;
    }

    await this.fireUpdateAction(animations);
  }

  /**
   * Enable/disable the animation state change listener.
   * If set true, observe "changed" event on current animations.
   * Otherwise, quit observing the "changed" event.
   *
   * @param {Bool} isEnabled
   */

  setAnimationStateChangedListenerEnabled(isEnabled) {
    if (!this.inspector) {
      return// Already destroyed.
    }
    if (isEnabled) {
      for (const animation of this.state.animations) {
        animation.on("changed"this.onAnimationStateChanged);
      }
    } else {
      for (const animation of this.state.animations) {
        animation.off("changed"this.onAnimationStateChanged);
      }
    }
  }

  setDetailVisibility(isVisible) {
    this.inspector.store.dispatch(updateDetailVisibility(isVisible));
  }

  /**
   * Persistently highlight the given node identified with a unique selector.
   * If no node is provided, hide any persistent highlighter.
   *
   * @param {NodeFront} nodeFront
   */

  async setHighlightedNode(nodeFront) {
    await this.inspector.highlighters.hideHighlighterType(
      this.inspector.highlighters.TYPES.SELECTOR
    );

    if (nodeFront) {
      const selector = await nodeFront.getUniqueSelector();
      if (!selector) {
        console.warn(
          `Couldn't get unique selector for NodeFront: ${nodeFront.actorID}`
        );
        return;
      }

      /**
       * NOTE: Using a Selector Highlighter here because only one Box Model Highlighter
       * can be visible at a time. The Box Model Highlighter is shown when hovering nodes
       * which would cause this persistent highlighter to be hidden unexpectedly.
       * This limitation of one highlighter type a time should be solved by switching
       * to a highlighter by role approach (Bug 1663443).
       */

      await this.inspector.highlighters.showHighlighterTypeForNode(
        this.inspector.highlighters.TYPES.SELECTOR,
        nodeFront,
        {
          hideInfoBar: true,
          hideGuides: true,
          selector,
        }
      );
    }

    this.inspector.store.dispatch(updateHighlightedNode(nodeFront));
  }

  /**
   * Returns simulatable animation by given parameters.
   * The returned animation is implementing Animation interface of Web Animation API.
   * https://drafts.csswg.org/web-animations/#the-animation-interface
   *
   * @param {Array} keyframes
   *        e.g. [{ opacity: 0 }, { opacity: 1 }]
   * @param {object} effectTiming
   *        e.g. { duration: 1000, fill: "both" }
   * @param {boolean} isElementNeeded
   *        true:  create animation with an element.
   *               If want to know computed value of the element, turn on.
   *        false: create animation without an element,
   *               If need to know only timing progress.
   * @return {Animation}
   *         https://drafts.csswg.org/web-animations/#the-animation-interface
   */

  simulateAnimation(keyframes, effectTiming, isElementNeeded) {
    // Don't simulate animation if the animation inspector is already destroyed.
    if (!this.win) {
      return null;
    }

    let targetEl = null;

    if (isElementNeeded) {
      if (!this.simulatedElement) {
        this.simulatedElement = this.win.document.createElement("div");
        this.win.document.documentElement.appendChild(this.simulatedElement);
      } else {
        // Reset styles.
        this.simulatedElement.style.cssText = "";
      }

      targetEl = this.simulatedElement;
    }

    if (!this.simulatedAnimation) {
      this.simulatedAnimation = new this.win.Animation();
    }

    this.simulatedAnimation.effect = new this.win.KeyframeEffect(
      targetEl,
      keyframes,
      effectTiming
    );

    return this.simulatedAnimation;
  }

  /**
   * Returns a simulatable efect timing animation for the keyframes progress bar.
   * The returned animation is implementing Animation interface of Web Animation API.
   * https://drafts.csswg.org/web-animations/#the-animation-interface
   *
   * @param {object} effectTiming
   *        e.g. { duration: 1000, fill: "both" }
   * @return {Animation}
   *         https://drafts.csswg.org/web-animations/#the-animation-interface
   */

  simulateAnimationForKeyframesProgressBar(effectTiming) {
    if (!this.simulatedAnimationForKeyframesProgressBar) {
      this.simulatedAnimationForKeyframesProgressBar = new this.win.Animation();
    }

    this.simulatedAnimationForKeyframesProgressBar.effect =
      new this.win.KeyframeEffect(nullnull, effectTiming);

    return this.simulatedAnimationForKeyframesProgressBar;
  }

  stopAnimationsCurrentTimeTimer() {
    if (this.currentTimeTimer) {
      this.currentTimeTimer.destroy();
      this.currentTimeTimer = null;
    }
  }

  startAnimationsCurrentTimeTimer() {
    const timeScale = this.state.timeScale;
    const shouldStopAfterEndTime = !hasAnimationIterationCountInfinite(
      this.state.animations
    );

    const currentTimeTimer = new CurrentTimeTimer(
      timeScale,
      shouldStopAfterEndTime,
      this.win,
      this.onCurrentTimeTimerUpdated
    );
    currentTimeTimer.start();
    this.currentTimeTimer = currentTimeTimer;
  }

  toggleElementPicker() {
    this.inspector.toolbox.nodePicker.togglePicker();
  }

  onNewNodeFront() {
    this.watchAnimationsForSelectedNode();
  }

  /**
   * Retrieve animations for the inspector selected node (and its subtree), add an event
   * listener for animations on the node (and its subtree) and update the panel.
   * If the panel is not visible (and `force` is not `true`), the panel won't be updated,
   * and this will remove the previous listener.
   *
   * @param {object} options
   * @param {boolean} options.force: Set to true to force updating the panel, even if
   *                  it is not visible.
   */

  async watchAnimationsForSelectedNode({ force = false } = {}) {
    this.unwatchAnimationsForSelectedNode();

    if (!this.isPanelVisible() && !force) {
      return;
    }

    const done = this.inspector.updating("animationinspector");
    const selection = this.inspector.selection;

    let animations;
    const shouldWatchAnimationForSelectedNode =
      selection && selection.isConnected() && selection.isElementNode();
    if (shouldWatchAnimationForSelectedNode) {
      // Since the panel only displays the animations for the selected node and its subtree,
      // we can get the animation front from the selected node target, so we can handle
      // animations in iframe for example
      this.animationsFront =
        await selection.nodeFront.targetFront.getFront("animations");
      // At this point, we have a selected node, so the target should have an inspector
      // and its walker, that we can pass to the animation front
      this.animationsFront.setWalkerActor(
        selection.nodeFront.inspectorFront.walker
      );
      // Then we can listen for future animations on the subtree
      this.animationsFront.on("mutations"this.onAnimationsMutation);
      // and directly retrieve the existing one, if there are some
      animations = await this.animationsFront.getAnimationPlayersForNode(
        selection.nodeFront
      );
    }

    this.fireUpdateAction(animations || []);
    this.setAnimationStateChangedListenerEnabled(true);

    done();
  }

  /**
   * Nullify animationsFront, remove the listener that might have been set on it, as well
   * as listeners on Animation fronts.
   *
   * @param {object} options
   * @param {boolean} options.force: Set to true to force updating the panel, even if
   *                  it is not visible.
   */

  unwatchAnimationsForSelectedNode() {
    if (this.animationsFront) {
      this.animationsFront.off("mutations"this.onAnimationsMutation);
      this.animationsFront = null;
    }
    this.setAnimationStateChangedListenerEnabled(false);
  }

  async refreshAnimationsState(animations) {
    let error = null;

    const promises = animations.map(animation => {
      return new Promise(resolve => {
        animation
          .refreshState()
          .catch(e => {
            error = e;
          })
          .finally(() => {
            resolve();
          });
      });
    });
    await Promise.all(promises);

    if (error) {
      throw new Error(error);
    }

    // Even when removal animation on inspected document, refreshAnimationsState
    // might be called before onAnimationsMutation due to the async timing.
    // Return the animations as result of refreshAnimationsState after getting rid of
    // the animations since they should not display.
    return animations.filter(anim => !!anim.state.type);
  }

  fireUpdateAction(animations) {
    // Animation inspector already destroyed
    if (!this.inspector) {
      return;
    }

    this.stopAnimationsCurrentTimeTimer();

    // Although it is not possible to set a delay or end delay of infinity using
    // the animation API, if the value passed exceeds the limit of our internal
    // representation of times, it will be treated as infinity. Rather than
    // adding special case code to represent this very rare case, we simply omit
    // such animations from the graph.
    animations = animations.filter(
      anim =>
        Math.abs(anim.state.delay) !== Infinity &&
        Math.abs(anim.state.endDelay) !== Infinity
    );

    this.inspector.store.dispatch(updateAnimations(animations));

    if (hasRunningAnimation(animations)) {
      this.startAnimationsCurrentTimeTimer();
    } else {
      // Even no running animations, update the current time once
      // so as to show the state.
      this.onCurrentTimeTimerUpdated(this.state.timeScale.getCurrentTime());
    }
  }
}

module.exports = AnimationInspector;

Messung V0.5 in Prozent
C=96 H=98 G=96

¤ Dauer der Verarbeitung: 0.17 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.