123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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;
|