зеркало из https://github.com/mozilla/gecko-dev.git
276 строки
8.5 KiB
JavaScript
276 строки
8.5 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* 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";
|
|
|
|
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
|
|
|
const { LocalizationHelper } = require("devtools/shared/l10n");
|
|
const L10N =
|
|
new LocalizationHelper("devtools/client/locales/animationinspector.properties");
|
|
|
|
// How many times, maximum, can we loop before we find the optimal time
|
|
// interval in the timeline graph.
|
|
const OPTIMAL_TIME_INTERVAL_MAX_ITERS = 100;
|
|
// Time graduations should be multiple of one of these number.
|
|
const OPTIMAL_TIME_INTERVAL_MULTIPLES = [1, 2.5, 5];
|
|
|
|
const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
|
|
|
|
/**
|
|
* DOM node creation helper function.
|
|
* @param {Object} Options to customize the node to be created.
|
|
* - nodeType {String} Optional, defaults to "div",
|
|
* - attributes {Object} Optional attributes object like
|
|
* {attrName1:value1, attrName2: value2, ...}
|
|
* - parent {DOMNode} Mandatory node to append the newly created node to.
|
|
* - textContent {String} Optional text for the node.
|
|
* - namespace {String} Optional namespace
|
|
* @return {DOMNode} The newly created node.
|
|
*/
|
|
function createNode(options) {
|
|
if (!options.parent) {
|
|
throw new Error("Missing parent DOMNode to create new node");
|
|
}
|
|
|
|
let type = options.nodeType || "div";
|
|
let node =
|
|
options.namespace
|
|
? options.parent.ownerDocument.createElementNS(options.namespace, type)
|
|
: options.parent.ownerDocument.createElement(type);
|
|
|
|
for (let name in options.attributes || {}) {
|
|
let value = options.attributes[name];
|
|
node.setAttribute(name, value);
|
|
}
|
|
|
|
if (options.textContent) {
|
|
node.textContent = options.textContent;
|
|
}
|
|
|
|
options.parent.appendChild(node);
|
|
return node;
|
|
}
|
|
|
|
exports.createNode = createNode;
|
|
|
|
/**
|
|
* Find the optimal interval between time graduations in the animation timeline
|
|
* graph based on a minimum time interval
|
|
* @param {Number} minTimeInterval Minimum time in ms in one interval
|
|
* @return {Number} The optimal interval time in ms
|
|
*/
|
|
function findOptimalTimeInterval(minTimeInterval) {
|
|
let numIters = 0;
|
|
let multiplier = 1;
|
|
|
|
if (!minTimeInterval) {
|
|
return 0;
|
|
}
|
|
|
|
let interval;
|
|
while (true) {
|
|
for (let i = 0; i < OPTIMAL_TIME_INTERVAL_MULTIPLES.length; i++) {
|
|
interval = OPTIMAL_TIME_INTERVAL_MULTIPLES[i] * multiplier;
|
|
if (minTimeInterval <= interval) {
|
|
return interval;
|
|
}
|
|
}
|
|
if (++numIters > OPTIMAL_TIME_INTERVAL_MAX_ITERS) {
|
|
return interval;
|
|
}
|
|
multiplier *= 10;
|
|
}
|
|
}
|
|
|
|
exports.findOptimalTimeInterval = findOptimalTimeInterval;
|
|
|
|
/**
|
|
* Format a timestamp (in ms) as a mm:ss.mmm string.
|
|
* @param {Number} time
|
|
* @return {String}
|
|
*/
|
|
function formatStopwatchTime(time) {
|
|
// Format falsy values as 0
|
|
if (!time) {
|
|
return "00:00.000";
|
|
}
|
|
|
|
let milliseconds = parseInt(time % 1000, 10);
|
|
let seconds = parseInt((time / 1000) % 60, 10);
|
|
let minutes = parseInt((time / (1000 * 60)), 10);
|
|
|
|
let pad = (nb, max) => {
|
|
if (nb < max) {
|
|
return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
|
|
}
|
|
return nb;
|
|
};
|
|
|
|
minutes = pad(minutes, 10);
|
|
seconds = pad(seconds, 10);
|
|
milliseconds = pad(milliseconds, 100);
|
|
|
|
return `${minutes}:${seconds}.${milliseconds}`;
|
|
}
|
|
|
|
exports.formatStopwatchTime = formatStopwatchTime;
|
|
|
|
/**
|
|
* The TimeScale helper object is used to know which size should something be
|
|
* displayed with in the animation panel, depending on the animations that are
|
|
* currently displayed.
|
|
* If there are 5 animations displayed, and the first one starts at 10000ms and
|
|
* the last one ends at 20000ms, then this helper can be used to convert any
|
|
* time in this range to a distance in pixels.
|
|
*
|
|
* For the helper to know how to convert, it needs to know all the animations.
|
|
* Whenever a new animation is added to the panel, addAnimation(state) should be
|
|
* called. reset() can be called to start over.
|
|
*/
|
|
var TimeScale = {
|
|
minStartTime: Infinity,
|
|
maxEndTime: 0,
|
|
|
|
/**
|
|
* Add a new animation to time scale.
|
|
* @param {Object} state A PlayerFront.state object.
|
|
*/
|
|
addAnimation: function (state) {
|
|
let {previousStartTime, delay, duration, endDelay,
|
|
iterationCount, playbackRate} = state;
|
|
|
|
endDelay = typeof endDelay === "undefined" ? 0 : endDelay;
|
|
let toRate = v => v / playbackRate;
|
|
let minZero = v => Math.max(v, 0);
|
|
let rateRelativeDuration =
|
|
toRate(duration * (!iterationCount ? 1 : iterationCount));
|
|
// Negative-delayed animations have their startTimes set such that we would
|
|
// be displaying the delay outside the time window if we didn't take it into
|
|
// account here.
|
|
let relevantDelay = delay < 0 ? toRate(delay) : 0;
|
|
previousStartTime = previousStartTime || 0;
|
|
|
|
let startTime = toRate(minZero(delay)) +
|
|
rateRelativeDuration +
|
|
endDelay;
|
|
this.minStartTime = Math.min(
|
|
this.minStartTime,
|
|
previousStartTime +
|
|
relevantDelay +
|
|
Math.min(startTime, 0)
|
|
);
|
|
let length = toRate(delay) +
|
|
rateRelativeDuration +
|
|
toRate(minZero(endDelay));
|
|
let endTime = previousStartTime + length;
|
|
this.maxEndTime = Math.max(this.maxEndTime, endTime);
|
|
},
|
|
|
|
/**
|
|
* Reset the current time scale.
|
|
*/
|
|
reset: function () {
|
|
this.minStartTime = Infinity;
|
|
this.maxEndTime = 0;
|
|
},
|
|
|
|
/**
|
|
* Convert a startTime to a distance in %, in the current time scale.
|
|
* @param {Number} time
|
|
* @return {Number}
|
|
*/
|
|
startTimeToDistance: function (time) {
|
|
time -= this.minStartTime;
|
|
return this.durationToDistance(time);
|
|
},
|
|
|
|
/**
|
|
* Convert a duration to a distance in %, in the current time scale.
|
|
* @param {Number} time
|
|
* @return {Number}
|
|
*/
|
|
durationToDistance: function (duration) {
|
|
return duration * 100 / this.getDuration();
|
|
},
|
|
|
|
/**
|
|
* Convert a distance in % to a time, in the current time scale.
|
|
* @param {Number} distance
|
|
* @return {Number}
|
|
*/
|
|
distanceToTime: function (distance) {
|
|
return this.minStartTime + (this.getDuration() * distance / 100);
|
|
},
|
|
|
|
/**
|
|
* Convert a distance in % to a time, in the current time scale.
|
|
* The time will be relative to the current minimum start time.
|
|
* @param {Number} distance
|
|
* @return {Number}
|
|
*/
|
|
distanceToRelativeTime: function (distance) {
|
|
let time = this.distanceToTime(distance);
|
|
return time - this.minStartTime;
|
|
},
|
|
|
|
/**
|
|
* Depending on the time scale, format the given time as milliseconds or
|
|
* seconds.
|
|
* @param {Number} time
|
|
* @return {String} The formatted time string.
|
|
*/
|
|
formatTime: function (time) {
|
|
// Format in milliseconds if the total duration is short enough.
|
|
if (this.getDuration() <= MILLIS_TIME_FORMAT_MAX_DURATION) {
|
|
return L10N.getFormatStr("timeline.timeGraduationLabel", time.toFixed(0));
|
|
}
|
|
|
|
// Otherwise format in seconds.
|
|
return L10N.getFormatStr("player.timeLabel", (time / 1000).toFixed(1));
|
|
},
|
|
|
|
getDuration: function () {
|
|
return this.maxEndTime - this.minStartTime;
|
|
},
|
|
|
|
/**
|
|
* Given an animation, get the various dimensions (in %) useful to draw the
|
|
* animation in the timeline.
|
|
*/
|
|
getAnimationDimensions: function ({state}) {
|
|
let start = state.previousStartTime || 0;
|
|
let duration = state.duration;
|
|
let rate = state.playbackRate;
|
|
let count = state.iterationCount;
|
|
let delay = state.delay || 0;
|
|
let endDelay = state.endDelay || 0;
|
|
|
|
// The start position.
|
|
let x = this.startTimeToDistance(start + (delay / rate));
|
|
// The width for a single iteration.
|
|
let w = this.durationToDistance(duration / rate);
|
|
// The width for all iterations.
|
|
let iterationW = w * (count || 1);
|
|
// The start position of the delay.
|
|
let delayX = delay < 0 ? x : this.startTimeToDistance(start);
|
|
// The width of the delay.
|
|
let delayW = this.durationToDistance(Math.abs(delay) / rate);
|
|
// The width of the delay if it is negative, 0 otherwise.
|
|
let negativeDelayW = delay < 0 ? delayW : 0;
|
|
// The width of the endDelay.
|
|
let endDelayW = this.durationToDistance(Math.abs(endDelay) / rate);
|
|
// The start position of the endDelay.
|
|
let endDelayX = endDelay < 0 ? x + iterationW - endDelayW
|
|
: x + iterationW;
|
|
|
|
return {x, w, iterationW, delayX, delayW, negativeDelayW,
|
|
endDelayX, endDelayW};
|
|
}
|
|
};
|
|
|
|
exports.TimeScale = TimeScale;
|