Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/client/inspector/animation/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 24 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.js");
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,
  updatePlaybackRates,
  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 {
  constructor(inspector, win) {
    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.setAnimationsPlaybackRate = this.setAnimationsPlaybackRate.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.update = this.update.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.onNavigate = this.onNavigate.bind(this);
    this.onSidebarResized = this.onSidebarResized.bind(this);
    this.onSidebarSelectionChanged = this.onSidebarSelectionChanged.bind(this);
    this.onTargetAvailable = this.onTargetAvailable.bind(this);

    EventEmitter.decorate(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,
      setAnimationsPlaybackRate,
      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,
        setAnimationsPlaybackRate,
        setAnimationsPlayState,
        setDetailVisibility,
        setHighlightedNode,
        setSelectedNode,
        simulateAnimation,
        simulateAnimationForKeyframesProgressBar,
        toggleElementPicker,
      })
    );
    this.provider = provider;
  }

  async initListeners() {
    await this.inspector.commands.targetCommand.watchTargets({
      types: [this.inspector.commands.targetCommand.TYPES.FRAME],
      onAvailable: this.onTargetAvailable,
    });

    this.inspector.on("new-root"this.onNavigate);
    this.inspector.selection.on("new-node-front"this.update);
    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.off("new-root"this.onNavigate);
    this.inspector.selection.off("new-node-front"this.update);
    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) {
    const { animations, timeScale } = this.state;
    currentTime = currentTime + timeScale.minStartTime;
    await this.animationsFront.setCurrentTimes(animations, currentTime, true, {
      relativeToCreatedTime: 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");
    }

    return this.inspector.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));
  }

  onNavigate() {
    if (!this.isPanelVisible()) {
      return;
    }

    this.inspector.store.dispatch(updatePlaybackRates());
  }

  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.update();
      this.onSidebarResized(nullthis.inspector.getSidebarSize());
    } else {
      this.stopAnimationsCurrentTimeTimer();
      this.setAnimationStateChangedListenerEnabled(false);
    }
  }

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

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

  async onTargetAvailable({ targetFront }) {
    if (targetFront.isTopLevel) {
      this.animationsFront = await targetFront.getFront("animations");
      this.animationsFront.setWalkerActor(this.inspector.walker);
      this.animationsFront.on("mutations"this.onAnimationsMutation);

      await this.update();
    }
  }

  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 setAnimationsPlaybackRate(playbackRate) {
    if (!this.inspector) {
      return// Already destroyed or another node selected.
    }

    let animations = this.state.animations;
    // "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);
    try {
      await this.animationsFront.setPlaybackRates(animations, playbackRate);
      animations = await this.refreshAnimationsState(animations);
    } catch (e) {
      // Expected if we've already been destroyed or another node has been
      // selected in the meantime.
      console.error(e);
      return;
    } finally {
      this.setAnimationStateChangedListenerEnabled(true);
    }

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

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

    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();
  }

  async update() {
    if (!this.isPanelVisible()) {
      return;
    }

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

    const selection = this.inspector.selection;
    const animations =
      selection.isConnected() && selection.isElementNode()
        ? await this.animationsFront.getAnimationPlayersForNode(
            selection.nodeFront
          )
        : [];
    this.fireUpdateAction(animations);
    this.setAnimationStateChangedListenerEnabled(true);

    done();
  }

  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
C=93 H=97 G=94

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

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.