Bug 1229340 - Move animation inspector scrollbar to timeline container r?pbro
MozReview-Commit-ID: DT37WGBXTiS
--- 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
@@ -111,17 +111,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 */
@@ -218,18 +219,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;
@@ -237,77 +236,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;