Bug 1229340 - Move animation inspector scrollbar to timeline container r=pbro
authorRicky Chien <ricky060709@gmail.com>
Fri, 27 May 2016 18:01:19 +0800
changeset 330029 17ba38d33d7b2dc38ac300006ceac88776b36d7d
parent 330028 46231d51ba66ebc18acb965e9106f58a228e3906
child 330030 49ef0b8c3cfa522e35ef2fb937da43cecba6b99c
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1229340
milestone50.0a1
Bug 1229340 - Move animation inspector scrollbar to timeline container r=pbro MozReview-Commit-ID: DT37WGBXTiS
devtools/client/animationinspector/components/animation-timeline.js
devtools/client/animationinspector/test/browser_animation_timeline_header.js
devtools/client/animationinspector/test/browser_animation_timeline_ui.js
devtools/client/themes/animationinspector.css
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -68,17 +68,17 @@ AnimationsTimeline.prototype = {
       parent: containerEl,
       attributes: {
         "class": "animation-timeline"
       }
     });
 
     let scrubberContainer = createNode({
       parent: this.rootWrapperEl,
-      attributes: {"class": "scrubber-wrapper track-container"}
+      attributes: {"class": "scrubber-wrapper"}
     });
 
     this.scrubberEl = createNode({
       parent: scrubberContainer,
       attributes: {
         "class": "scrubber"
       }
     });
@@ -87,25 +87,40 @@ AnimationsTimeline.prototype = {
       parent: this.scrubberEl,
       attributes: {
         "class": "scrubber-handle"
       }
     });
     this.scrubberHandleEl.addEventListener("mousedown",
       this.onScrubberMouseDown);
 
+    this.headerWrapper = createNode({
+      parent: this.rootWrapperEl,
+      attributes: {
+        "class": "header-wrapper"
+      }
+    });
+
     this.timeHeaderEl = createNode({
-      parent: this.rootWrapperEl,
+      parent: this.headerWrapper,
       attributes: {
         "class": "time-header track-container"
       }
     });
+
     this.timeHeaderEl.addEventListener("mousedown",
       this.onScrubberMouseDown);
 
+    this.timeTickEl = createNode({
+      parent: this.rootWrapperEl,
+      attributes: {
+        "class": "time-body track-container"
+      }
+    });
+
     this.animationsEl = createNode({
       parent: this.rootWrapperEl,
       nodeType: "ul",
       attributes: {
         "class": "animations"
       }
     });
 
@@ -449,24 +464,39 @@ AnimationsTimeline.prototype = {
     let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
     let minTimeInterval = TIME_GRADUATION_MIN_SPACING *
                           animationDuration / width;
     let intervalLength = findOptimalTimeInterval(minTimeInterval);
     let intervalWidth = intervalLength * width / animationDuration;
 
     // And the time graduation header.
     this.timeHeaderEl.innerHTML = "";
+    this.timeTickEl.innerHTML = "";
 
     for (let i = 0; i <= width / intervalWidth; i++) {
       let pos = 100 * i * intervalWidth / width;
 
+      // This element is the header of time tick for displaying animation
+      // duration time.
       createNode({
         parent: this.timeHeaderEl,
         nodeType: "span",
         attributes: {
-          "class": "time-tick",
+          "class": "header-item",
           "style": `left:${pos}%`
         },
         textContent: TimeScale.formatTime(TimeScale.distanceToRelativeTime(pos))
       });
+
+      // This element is displayed as a vertical line separator corresponding
+      // the header of time tick for indicating time slice for animation
+      // iterations.
+      createNode({
+        parent: this.timeTickEl,
+        nodeType: "span",
+        attributes: {
+          "class": "time-tick",
+          "style": `left:${pos}%`
+        }
+      });
     }
   }
 };
--- a/devtools/client/animationinspector/test/browser_animation_timeline_header.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_header.js
@@ -11,33 +11,39 @@ requestLongerTimeout(2);
 const {findOptimalTimeInterval, TimeScale} = require("devtools/client/animationinspector/utils");
 
 // Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
 // animation-timeline.js
 const TIME_GRADUATION_MIN_SPACING = 40;
 
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
+
+  // System scrollbar is enabled by default on our testing envionment and it
+  // would shrink width of inspector and affect number of time-ticks causing
+  // unexpected results. So, we set it wider to avoid this kind of edge case.
+  yield pushPref("devtools.toolsidebar-width.inspector", 350);
+
   let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let headerEl = timeline.timeHeaderEl;
 
   info("Find out how many time graduations should there be");
   let width = headerEl.offsetWidth;
 
   let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
   let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
 
   // Note that findOptimalTimeInterval is tested separately in xpcshell test
   // test_findOptimalTimeInterval.js, so we assume that it works here.
   let interval = findOptimalTimeInterval(minTimeInterval);
   let nb = Math.ceil(animationDuration / interval);
 
-  is(headerEl.querySelectorAll(".time-tick").length, nb,
+  is(headerEl.querySelectorAll(".header-item").length, nb,
      "The expected number of time ticks were found");
 
   info("Make sure graduations are evenly distributed and show the right times");
   [...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => {
     let left = parseFloat(tick.style.left);
     let expectedPos = i * interval * 100 / animationDuration;
     is(Math.round(left), Math.round(expectedPos),
       `Graduation ${i} is positioned correctly`);
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -12,17 +12,17 @@ add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let el = timeline.rootWrapperEl;
 
   ok(el.querySelector(".time-header"),
      "The header element is in the DOM of the timeline");
-  ok(el.querySelectorAll(".time-header .time-tick").length,
+  ok(el.querySelectorAll(".time-header .header-item").length,
      "The header has some time graduations");
 
   ok(el.querySelector(".animations"),
      "The animations container is in the DOM of the timeline");
   is(el.querySelectorAll(".animations .animation").length,
      timeline.animations.length,
      "The number of animations displayed matches the number of animations");
 
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -102,17 +102,18 @@ body {
 [timeline] #timeline-toolbar {
   display: flex;
 }
 
 /* The main animations container */
 
 #players {
   height: calc(100% - var(--toolbar-height));
-  overflow: auto;
+  overflow-x: hidden;
+  overflow-y: auto;
 }
 
 [empty] #players {
   display: none;
 }
 
 /* The error message, shown when an invalid/unanimated element is selected */
 
@@ -195,18 +196,16 @@ body {
 #timeline-rate {
   position: relative;
   width: 4.5em;
 }
 
 /* Animation timeline component */
 
 .animation-timeline {
-  height: 100%;
-  overflow: hidden;
   position: relative;
   display: flex;
   flex-direction: column;
 }
 
 /* Useful for positioning animations or keyframes in the timeline */
 .animation-timeline .track-container {
   position: absolute;
@@ -214,77 +213,104 @@ body {
   left: var(--timeline-sidebar-width);
   /* Leave the width of a marker right of a track so the 100% markers can be
      selected easily */
   right: var(--keyframes-marker-size);
   height: var(--timeline-animation-height);
 }
 
 .animation-timeline .scrubber-wrapper {
-  z-index: 2;
-  pointer-events: none;
+  position: absolute;
+  left: var(--timeline-sidebar-width);
+  /* Leave the width of a marker right of a track so the 100% markers can be
+     selected easily */
+  right: var(--keyframes-marker-size);
   height: 100%;
 }
 
 .animation-timeline .scrubber {
+  z-index: 5;
+  pointer-events: none;
   position: absolute;
-  height: 100%;
+  /* Make the scrubber as tall as the viewport minus the toolbar height and the
+     header-wrapper's borders */
+  height: calc(100vh - var(--toolbar-height) - 1px);
+  min-height: 100%;
   width: 0;
   border-right: 1px solid red;
   box-sizing: border-box;
 }
 
-.animation-timeline .scrubber::before {
-  content: "";
-  position: absolute;
-  top: 0;
-  width: 1px;
-  right: -6px;
-  border-top: 5px solid red;
-  border-left: 5px solid transparent;
-  border-right: 5px solid transparent;
-}
-
 /* The scrubber handle is a transparent element displayed on top of the scrubber
    line that allows users to drag it */
 .animation-timeline .scrubber .scrubber-handle {
   position: absolute;
   height: 100%;
-  top: 0;
   /* Make it thick enough for easy dragging */
   width: 6px;
-  right: -3px;
+  right: -1.5px;
   cursor: col-resize;
   pointer-events: all;
 }
 
+.animation-timeline .scrubber .scrubber-handle::before {
+  content: "";
+  position: sticky;
+  top: 0;
+  width: 1px;
+  border-top: 5px solid red;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+}
+
 .animation-timeline .time-header {
-  min-height: var(--toolbar-height);
+  min-height: var(--timeline-animation-height);
   cursor: col-resize;
   -moz-user-select: none;
 }
 
-.animation-timeline .time-header .time-tick {
+.animation-timeline .time-header .header-item {
   position: absolute;
+  height: 100%;
+  padding-top: 3px;
+  border-left: 0.5px solid var(--time-graduation-border-color);
+}
+
+.animation-timeline .header-wrapper {
+  position: sticky;
   top: 0;
-  height: 100vh;
-  padding-top: 3px;
+  background-color: var(--theme-body-background);
+  border-bottom: 1px solid var(--time-graduation-border-color);
+  z-index: 3;
+  height: var(--timeline-animation-height);
+  overflow: hidden;
+}
+
+.animation-timeline .time-body {
+  height: 100%;
+}
+
+.animation-timeline .time-body .time-tick {
+  -moz-user-select: none;
+  position: absolute;
+  width: 0;
+  /* When scroll bar is shown, make it covers entire time-body */
+  height: 100%;
+  /* When scroll bar is hidden, make it as tall as the viewport minus the
+     timeline animation height and the header-wrapper's borders */
+  min-height: calc(100vh - var(--timeline-animation-height) - 1px);
   border-left: 0.5px solid var(--time-graduation-border-color);
 }
 
 .animation-timeline .animations {
   width: 100%;
   height: 100%;
-  overflow-y: auto;
-  overflow-x: hidden;
-  /* Leave some space for the header */
-  margin-top: var(--timeline-animation-height);
   padding: 0;
   list-style-type: none;
-  border-top: 1px solid var(--time-graduation-border-color);
+  margin-top: 0;
 }
 
 /* Animation block widgets */
 
 .animation-timeline .animation {
   margin: 2px 0;
   height: var(--timeline-animation-height);
   position: relative;