Bug 1458268 - Avoid empty inspector when inspecting css transition;r=daisuke draft
authorJulian Descottes <jdescottes@mozilla.com>
Tue, 01 May 2018 20:01:09 +0200
changeset 791201 bdf300e9c6e5bee43b59b56a18b2bf79f6d6dc82
parent 791057 a5bc0a445c38c5151d5fe8e11bd05646d548f003
child 791610 8eb08db1edf3939abd2248450d9428aa58443c39
child 791614 84ebb00942df0714594438f98d4ad853a2f0a80f
child 791679 96208fbdd2564dfcd5b276c186e1cbe8a7abda82
push id108732
push userjdescottes@mozilla.com
push dateThu, 03 May 2018 18:51:39 +0000
reviewersdaisuke
bugs1458268
milestone61.0a1
Bug 1458268 - Avoid empty inspector when inspecting css transition;r=daisuke MozReview-Commit-ID: 2uUkAmYrr4V
devtools/client/inspector/animation/components/KeyframesProgressBar.js
devtools/client/inspector/animation/test/browser.ini
devtools/client/inspector/animation/test/browser_animation_css-transition-with-playstate-idle.js
--- a/devtools/client/inspector/animation/components/KeyframesProgressBar.js
+++ b/devtools/client/inspector/animation/components/KeyframesProgressBar.js
@@ -36,42 +36,50 @@ class KeyframesProgressBar extends PureC
     const { addAnimationsCurrentTimeListener } = this.props;
 
     this.element = ReactDOM.findDOMNode(this);
     this.setupAnimation(this.props);
     addAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
   }
 
   componentWillReceiveProps(nextProps) {
-    const { getAnimationsCurrentTime } = nextProps;
+    const { animation, getAnimationsCurrentTime, timeScale } = nextProps;
 
     this.setupAnimation(nextProps);
-    this.onCurrentTimeUpdated(getAnimationsCurrentTime());
+    this.updateOffset(getAnimationsCurrentTime(), animation, timeScale);
   }
 
   componentWillUnmount() {
     const { removeAnimationsCurrentTimeListener } = this.props;
 
     removeAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
     this.element = null;
     this.simulatedAnimation = null;
   }
 
   onCurrentTimeUpdated(currentTime) {
-    const {
-      animation,
-      timeScale,
-    } = this.props;
+    const { animation, timeScale } = this.props;
+    this.updateOffset(currentTime, animation, timeScale);
+  }
+
+  updateOffset(currentTime, animation, timeScale) {
     const {
       playbackRate,
       previousStartTime = 0,
     } = animation.state;
 
-    this.simulatedAnimation.currentTime =
+    const time =
       (timeScale.minStartTime + currentTime - previousStartTime) * playbackRate;
+    if (isNaN(time)) {
+      // Setting an invalid currentTime will throw so bail out if time is not a number for
+      // any reason.
+      return;
+    }
+
+    this.simulatedAnimation.currentTime = time;
     const offset = this.element.offsetWidth *
                    this.simulatedAnimation.effect.getComputedTiming().progress;
 
     this.setState({ offset });
   }
 
   setupAnimation(props) {
     const {
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -22,16 +22,17 @@ support-files =
 [browser_animation_animation-detail_close-button.js]
 [browser_animation_animation-detail_title.js]
 [browser_animation_animation-detail_visibility.js]
 [browser_animation_animation-list.js]
 [browser_animation_animation-target.js]
 [browser_animation_animation-target_highlight.js]
 [browser_animation_animation-target_select.js]
 [browser_animation_animation-timeline-tick.js]
+[browser_animation_css-transition-with-playstate-idle.js]
 [browser_animation_current-time-label.js]
 [browser_animation_current-time-scrubber.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_keyframes-graph_computed-value-path.js]
 [browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
 [browser_animation_keyframes-graph_keyframe-marker.js]
 [browser_animation_keyframes-progress-bar.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_css-transition-with-playstate-idle.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that animation inspector does not fail when rendering an animation that
+// transitions from the playState "idle".
+
+const PAGE_URL = `data:text/html;charset=utf-8,
+<!DOCTYPE html>
+<html>
+<head>
+  <style type="text/css">
+    div {
+      opacity: 0;
+      transition-duration: 5000ms;
+      transition-property: opacity;
+    }
+
+    div.visible {
+      opacity: 1;
+    }
+  </style>
+</head>
+<body>
+  <div>test</div>
+</body>
+</html>`;
+
+add_task(async function() {
+  const tab = await addTab(PAGE_URL);
+  const { animationInspector, panel } = await openAnimationInspector();
+
+  info("Toggle the visible class to start the animation");
+  await toggleVisibleClass(tab);
+
+  info("Wait until the scrubber is displayed");
+  await waitUntil(() => panel.querySelector(".current-time-scrubber"));
+
+  const scrubberEl = panel.querySelector(".current-time-scrubber");
+
+  info("Wait until animations are paused");
+  await waitUntilAnimationsPaused(animationInspector);
+
+  // Check the initial position of the scrubber to detect the animation.
+  const scrubberX = scrubberEl.getBoundingClientRect().x;
+
+  info("Toggle the visible class to start the animation");
+  await toggleVisibleClass(tab);
+
+  info("scrubberX after: " + scrubberEl.getBoundingClientRect().x);
+
+  info("Wait until the scrubber starts moving");
+  await waitUntil(() => scrubberEl.getBoundingClientRect().x != scrubberX);
+
+  info("Wait until animations are paused");
+  await waitUntilAnimationsPaused(animationInspector);
+
+  // Query the scrubber element again to check that the UI is still rendered.
+  ok(!!panel.querySelector(".current-time-scrubber"),
+    "The scrubber element is still rendered in the animation inspector panel");
+
+  info("Wait for the keyframes graph to be updated before ending the test.");
+  await waitForAnimationDetail(animationInspector);
+});
+
+/**
+ * Local helper to toggle the "visible" class on the element with a transition defined.
+ */
+async function toggleVisibleClass(tab) {
+  await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
+    let win = content.wrappedJSObject;
+    win.document.querySelector("div").classList.toggle("visible");
+  });
+}
+
+async function waitUntilAnimationsPaused(animationInspector) {
+  await waitUntil(() => {
+    const animations = animationInspector.state.animations;
+    return animations.every(animation => {
+      const state = animation.state.playState;
+      return state === "paused" || state === "finished";
+    });
+  });
+}