mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-27 21:39:15 +00:00
620 lines
20 KiB
JavaScript
620 lines
20 KiB
JavaScript
/* 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";
|
|
|
|
/**
|
|
* This file contains the "waterfall" view, essentially a detailed list
|
|
* of all the markers in the timeline data.
|
|
*/
|
|
|
|
const {Ci, Cu} = require("chrome");
|
|
|
|
loader.lazyRequireGetter(this, "L10N",
|
|
"devtools/shared/timeline/global", true);
|
|
|
|
loader.lazyImporter(this, "setNamedTimeout",
|
|
"resource:///modules/devtools/ViewHelpers.jsm");
|
|
loader.lazyImporter(this, "clearNamedTimeout",
|
|
"resource:///modules/devtools/ViewHelpers.jsm");
|
|
loader.lazyRequireGetter(this, "EventEmitter",
|
|
"devtools/toolkit/event-emitter");
|
|
|
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|
|
|
const WATERFALL_SIDEBAR_WIDTH = 150; // px
|
|
|
|
const WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
|
|
const WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
|
|
|
|
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
|
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
|
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
|
|
|
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
|
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
|
const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
|
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
|
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
|
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
|
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
|
|
|
|
const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
|
|
|
|
/**
|
|
* A detailed waterfall view for the timeline data.
|
|
*
|
|
* @param nsIDOMNode parent
|
|
* The parent node holding the waterfall.
|
|
* @param nsIDOMNode container
|
|
* The container node that key events should be bound to.
|
|
* @param Object blueprint
|
|
* List of names and colors defining markers.
|
|
*/
|
|
function Waterfall(parent, container, blueprint) {
|
|
EventEmitter.decorate(this);
|
|
|
|
this._parent = parent;
|
|
this._document = parent.ownerDocument;
|
|
this._container = container;
|
|
this._fragment = this._document.createDocumentFragment();
|
|
this._outstandingMarkers = [];
|
|
|
|
this._headerContents = this._document.createElement("hbox");
|
|
this._headerContents.className = "waterfall-header-contents";
|
|
this._parent.appendChild(this._headerContents);
|
|
|
|
this._listContents = this._document.createElement("vbox");
|
|
this._listContents.className = "waterfall-list-contents";
|
|
this._listContents.setAttribute("flex", "1");
|
|
this._parent.appendChild(this._listContents);
|
|
|
|
this.setupKeys();
|
|
|
|
this._isRTL = this._getRTL();
|
|
|
|
// Lazy require is a bit slow, and these are hot objects.
|
|
this._l10n = L10N;
|
|
this._blueprint = blueprint;
|
|
this._setNamedTimeout = setNamedTimeout;
|
|
this._clearNamedTimeout = clearNamedTimeout;
|
|
|
|
// Selected row index. By default, we want the first
|
|
// row to be selected.
|
|
this._selectedRowIdx = 0;
|
|
|
|
// Default rowCount
|
|
this.rowCount = WATERFALL_ROWCOUNT_ONPAGEUPDOWN;
|
|
}
|
|
|
|
Waterfall.prototype = {
|
|
/**
|
|
* Removes any node references from this view.
|
|
*/
|
|
destroy: function() {
|
|
this._parent = this._document = this._container = null;
|
|
},
|
|
|
|
/**
|
|
* Populates this view with the provided data source.
|
|
*
|
|
* @param object data
|
|
* An object containing the following properties:
|
|
* - markers: a list of markers received from the controller
|
|
* - interval: the { startTime, endTime }, in milliseconds
|
|
*/
|
|
setData: function({ markers, interval }) {
|
|
this.clearView();
|
|
this._markers = markers;
|
|
this._interval = interval;
|
|
|
|
let { startTime, endTime } = interval;
|
|
let dataScale = this._waterfallWidth / (endTime - startTime);
|
|
this._drawWaterfallBackground(dataScale);
|
|
|
|
this._buildHeader(this._headerContents, startTime, dataScale);
|
|
this._buildMarkers(this._listContents, markers, startTime, endTime, dataScale);
|
|
this.selectRow(this._selectedRowIdx);
|
|
},
|
|
|
|
/**
|
|
* List of names and colors used to paint markers.
|
|
* @see TIMELINE_BLUEPRINT in timeline/widgets/global.js
|
|
*/
|
|
setBlueprint: function(blueprint) {
|
|
this._blueprint = blueprint;
|
|
},
|
|
|
|
/**
|
|
* Keybindings.
|
|
*/
|
|
setupKeys: function() {
|
|
let pane = this._container;
|
|
pane.addEventListener("keydown", e => {
|
|
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) {
|
|
e.preventDefault();
|
|
this.selectNearestRow(this._selectedRowIdx - 1);
|
|
}
|
|
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
|
|
e.preventDefault();
|
|
this.selectNearestRow(this._selectedRowIdx + 1);
|
|
}
|
|
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME) {
|
|
e.preventDefault();
|
|
this.selectNearestRow(0);
|
|
}
|
|
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END) {
|
|
e.preventDefault();
|
|
this.selectNearestRow(this._listContents.children.length);
|
|
}
|
|
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
|
|
e.preventDefault();
|
|
this.selectNearestRow(this._selectedRowIdx - this.rowCount);
|
|
}
|
|
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
|
e.preventDefault();
|
|
this.selectNearestRow(this._selectedRowIdx + this.rowCount);
|
|
}
|
|
}, true);
|
|
},
|
|
|
|
/**
|
|
* Depopulates this view.
|
|
*/
|
|
clearView: function() {
|
|
while (this._headerContents.hasChildNodes()) {
|
|
this._headerContents.firstChild.remove();
|
|
}
|
|
while (this._listContents.hasChildNodes()) {
|
|
this._listContents.firstChild.remove();
|
|
}
|
|
this._listContents.scrollTop = 0;
|
|
this._outstandingMarkers.length = 0;
|
|
this._clearNamedTimeout("flush-outstanding-markers");
|
|
},
|
|
|
|
/**
|
|
* Calculates and stores the available width for the waterfall.
|
|
* This should be invoked every time the container window is resized.
|
|
*/
|
|
recalculateBounds: function() {
|
|
let bounds = this._parent.getBoundingClientRect();
|
|
this._waterfallWidth = bounds.width - WATERFALL_SIDEBAR_WIDTH;
|
|
},
|
|
|
|
/**
|
|
* Creates the header part of this view.
|
|
*
|
|
* @param nsIDOMNode parent
|
|
* The parent node holding the header.
|
|
* @param number startTime
|
|
* @see Waterfall.prototype.setData
|
|
* @param number dataScale
|
|
* The time scale of the data source.
|
|
*/
|
|
_buildHeader: function(parent, startTime, dataScale) {
|
|
let container = this._document.createElement("hbox");
|
|
container.className = "waterfall-header-container";
|
|
container.setAttribute("flex", "1");
|
|
|
|
let sidebar = this._document.createElement("hbox");
|
|
sidebar.className = "waterfall-sidebar theme-sidebar";
|
|
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
|
sidebar.setAttribute("align", "center");
|
|
container.appendChild(sidebar);
|
|
|
|
let name = this._document.createElement("label");
|
|
name.className = "plain waterfall-header-name";
|
|
name.setAttribute("value", this._l10n.getStr("timeline.records"));
|
|
sidebar.appendChild(name);
|
|
|
|
let ticks = this._document.createElement("hbox");
|
|
ticks.className = "waterfall-header-ticks waterfall-background-ticks";
|
|
ticks.setAttribute("align", "center");
|
|
ticks.setAttribute("flex", "1");
|
|
container.appendChild(ticks);
|
|
|
|
let offset = this._isRTL ? this._waterfallWidth : 0;
|
|
let direction = this._isRTL ? -1 : 1;
|
|
let tickInterval = this._findOptimalTickInterval({
|
|
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
|
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
|
dataScale: dataScale
|
|
});
|
|
|
|
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
|
|
let left = x + direction * WATERFALL_HEADER_TEXT_PADDING;
|
|
let time = Math.round(x / dataScale + startTime);
|
|
let label = this._l10n.getFormatStr("timeline.tick", time);
|
|
|
|
let node = this._document.createElement("label");
|
|
node.className = "plain waterfall-header-tick";
|
|
node.style.transform = "translateX(" + (left - offset) + "px)";
|
|
node.setAttribute("value", label);
|
|
ticks.appendChild(node);
|
|
}
|
|
|
|
parent.appendChild(container);
|
|
},
|
|
|
|
/**
|
|
* Creates the markers part of this view.
|
|
*
|
|
* @param nsIDOMNode parent
|
|
* The parent node holding the markers.
|
|
* @param number startTime
|
|
* @see Waterfall.prototype.setData
|
|
* @param number dataScale
|
|
* The time scale of the data source.
|
|
*/
|
|
_buildMarkers: function(parent, markers, startTime, endTime, dataScale) {
|
|
let rowsCount = 0;
|
|
let markerIdx = -1;
|
|
|
|
for (let marker of markers) {
|
|
markerIdx++;
|
|
|
|
if (!isMarkerInRange(marker, startTime, endTime)) {
|
|
continue;
|
|
}
|
|
if (!(marker.name in this._blueprint)) {
|
|
continue;
|
|
}
|
|
|
|
// Only build and display a finite number of markers initially, to
|
|
// preserve a snappy UI. After a certain delay, continue building the
|
|
// outstanding markers while there's (hopefully) no user interaction.
|
|
let arguments_ = [this._fragment, marker, startTime, dataScale, markerIdx, rowsCount];
|
|
if (rowsCount++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
|
this._buildMarker.apply(this, arguments_);
|
|
} else {
|
|
this._outstandingMarkers.push(arguments_);
|
|
}
|
|
}
|
|
|
|
// If there are no outstanding markers, add a dummy "spacer" at the end
|
|
// to fill up any remaining available space in the UI.
|
|
if (!this._outstandingMarkers.length) {
|
|
this._buildMarker(this._fragment, null);
|
|
}
|
|
// Otherwise prepare flushing the outstanding markers after a small delay.
|
|
else {
|
|
let delay = WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY;
|
|
let func = () => this._buildOutstandingMarkers(parent);
|
|
this._setNamedTimeout("flush-outstanding-markers", delay, func);
|
|
}
|
|
|
|
parent.appendChild(this._fragment);
|
|
},
|
|
|
|
/**
|
|
* Finishes building the outstanding markers in this view.
|
|
* @see Waterfall.prototype._buildMarkers
|
|
*/
|
|
_buildOutstandingMarkers: function(parent) {
|
|
if (!this._outstandingMarkers.length) {
|
|
return;
|
|
}
|
|
for (let args of this._outstandingMarkers) {
|
|
this._buildMarker.apply(this, args);
|
|
}
|
|
this._outstandingMarkers.length = 0;
|
|
parent.appendChild(this._fragment);
|
|
this.selectRow(this._selectedRowIdx);
|
|
},
|
|
|
|
/**
|
|
* Creates a single marker in this view.
|
|
*
|
|
* @param nsIDOMNode parent
|
|
* The parent node holding the marker.
|
|
* @param object marker
|
|
* The { name, start, end } marker in the data source.
|
|
* @param startTime
|
|
* @see Waterfall.prototype.setData
|
|
* @param number dataScale
|
|
* @see Waterfall.prototype._buildMarkers
|
|
* @param number markerIdx
|
|
* Index of the marker in this._markers
|
|
* @param number rowIdx
|
|
* Index of current row
|
|
*/
|
|
_buildMarker: function(parent, marker, startTime, dataScale, markerIdx, rowIdx) {
|
|
let container = this._document.createElement("hbox");
|
|
container.setAttribute("markerIdx", markerIdx);
|
|
container.className = "waterfall-marker-container";
|
|
|
|
if (marker) {
|
|
this._buildMarkerSidebar(container, marker);
|
|
this._buildMarkerWaterfall(container, marker, startTime, dataScale, markerIdx);
|
|
container.onclick = () => this.selectRow(rowIdx);
|
|
} else {
|
|
this._buildMarkerSpacer(container);
|
|
container.setAttribute("flex", "1");
|
|
container.setAttribute("is-spacer", "");
|
|
}
|
|
|
|
parent.appendChild(container);
|
|
},
|
|
|
|
/**
|
|
* Select first row.
|
|
*/
|
|
resetSelection: function() {
|
|
this.selectRow(0);
|
|
},
|
|
|
|
/**
|
|
* Select a marker in the waterfall.
|
|
*
|
|
* @param number idx
|
|
* Index of the row to select. -1 clears the selection.
|
|
*/
|
|
selectRow: function(idx) {
|
|
let prev = this._listContents.children[this._selectedRowIdx];
|
|
if (prev) {
|
|
prev.classList.remove("selected");
|
|
}
|
|
|
|
this._selectedRowIdx = idx;
|
|
|
|
let row = this._listContents.children[idx];
|
|
if (row && !row.hasAttribute("is-spacer")) {
|
|
row.focus();
|
|
row.classList.add("selected");
|
|
|
|
let markerIdx = row.getAttribute("markerIdx");
|
|
this.emit("selected", this._markers[markerIdx]);
|
|
this.ensureRowIsVisible(row);
|
|
} else {
|
|
this.emit("unselected");
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Find a valid row to select.
|
|
*
|
|
* @param number idx
|
|
* Index of the row to select.
|
|
*/
|
|
selectNearestRow: function(idx) {
|
|
if (this._listContents.children.length == 0) {
|
|
return;
|
|
}
|
|
idx = Math.max(idx, 0);
|
|
idx = Math.min(idx, this._listContents.children.length - 1);
|
|
let row = this._listContents.children[idx];
|
|
if (row && row.hasAttribute("is-spacer")) {
|
|
if (idx > 0) {
|
|
return this.selectNearestRow(idx - 1);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
this.selectRow(idx);
|
|
},
|
|
|
|
/**
|
|
* Scroll waterfall to ensure row is in the viewport.
|
|
*
|
|
* @param number idx
|
|
* Index of the row to select.
|
|
*/
|
|
ensureRowIsVisible: function(row) {
|
|
let parent = row.parentNode;
|
|
let parentRect = parent.getBoundingClientRect();
|
|
let rowRect = row.getBoundingClientRect();
|
|
let yDelta = rowRect.top - parentRect.top;
|
|
if (yDelta < 0) {
|
|
parent.scrollTop += yDelta;
|
|
}
|
|
yDelta = parentRect.bottom - rowRect.bottom;
|
|
if (yDelta < 0) {
|
|
parent.scrollTop -= yDelta;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates the sidebar part of a marker in this view.
|
|
*
|
|
* @param nsIDOMNode container
|
|
* The container node representing the marker in this view.
|
|
* @param object marker
|
|
* @see Waterfall.prototype._buildMarker
|
|
*/
|
|
_buildMarkerSidebar: function(container, marker) {
|
|
let blueprint = this._blueprint[marker.name];
|
|
|
|
let sidebar = this._document.createElement("hbox");
|
|
sidebar.className = "waterfall-sidebar theme-sidebar";
|
|
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
|
sidebar.setAttribute("align", "center");
|
|
|
|
let bullet = this._document.createElement("hbox");
|
|
bullet.className = "waterfall-marker-bullet";
|
|
bullet.style.backgroundColor = blueprint.fill;
|
|
bullet.style.borderColor = blueprint.stroke;
|
|
bullet.setAttribute("type", marker.name);
|
|
sidebar.appendChild(bullet);
|
|
|
|
let name = this._document.createElement("label");
|
|
name.setAttribute("crop", "end");
|
|
name.setAttribute("flex", "1");
|
|
name.className = "plain waterfall-marker-name";
|
|
|
|
let label;
|
|
if (marker.causeName) {
|
|
label = this._l10n.getFormatStr("timeline.markerDetailFormat",
|
|
blueprint.label,
|
|
marker.causeName);
|
|
} else {
|
|
label = blueprint.label;
|
|
}
|
|
name.setAttribute("value", label);
|
|
name.setAttribute("tooltiptext", label);
|
|
sidebar.appendChild(name);
|
|
|
|
container.appendChild(sidebar);
|
|
},
|
|
|
|
/**
|
|
* Creates the waterfall part of a marker in this view.
|
|
*
|
|
* @param nsIDOMNode container
|
|
* The container node representing the marker.
|
|
* @param object marker
|
|
* @see Waterfall.prototype._buildMarker
|
|
* @param startTime
|
|
* @see Waterfall.prototype.setData
|
|
* @param number dataScale
|
|
* @see Waterfall.prototype._buildMarkers
|
|
*/
|
|
_buildMarkerWaterfall: function(container, marker, startTime, dataScale) {
|
|
let blueprint = this._blueprint[marker.name];
|
|
|
|
let waterfall = this._document.createElement("hbox");
|
|
waterfall.className = "waterfall-marker-item waterfall-background-ticks";
|
|
waterfall.setAttribute("align", "center");
|
|
waterfall.setAttribute("flex", "1");
|
|
|
|
let start = (marker.start - startTime) * dataScale;
|
|
let width = (marker.end - marker.start) * dataScale;
|
|
let offset = this._isRTL ? this._waterfallWidth : 0;
|
|
|
|
let bar = this._document.createElement("hbox");
|
|
bar.className = "waterfall-marker-bar";
|
|
bar.style.backgroundColor = blueprint.fill;
|
|
bar.style.borderColor = blueprint.stroke;
|
|
bar.style.transform = "translateX(" + (start - offset) + "px)";
|
|
// Save border color. It will change when marker is selected.
|
|
bar.setAttribute("borderColor", blueprint.stroke);
|
|
bar.setAttribute("type", marker.name);
|
|
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));
|
|
waterfall.appendChild(bar);
|
|
|
|
container.appendChild(waterfall);
|
|
},
|
|
|
|
/**
|
|
* Creates a dummy spacer as an empty marker.
|
|
*
|
|
* @param nsIDOMNode container
|
|
* The container node representing the marker.
|
|
*/
|
|
_buildMarkerSpacer: function(container) {
|
|
let sidebarSpacer = this._document.createElement("spacer");
|
|
sidebarSpacer.className = "waterfall-sidebar theme-sidebar";
|
|
sidebarSpacer.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
|
|
|
let waterfallSpacer = this._document.createElement("spacer");
|
|
waterfallSpacer.className = "waterfall-marker-item waterfall-background-ticks";
|
|
waterfallSpacer.setAttribute("flex", "1");
|
|
|
|
container.appendChild(sidebarSpacer);
|
|
container.appendChild(waterfallSpacer);
|
|
},
|
|
|
|
/**
|
|
* Creates the background displayed on the marker's waterfall.
|
|
*
|
|
* @param number dataScale
|
|
* @see Waterfall.prototype._buildMarkers
|
|
*/
|
|
_drawWaterfallBackground: function(dataScale) {
|
|
if (!this._canvas || !this._ctx) {
|
|
this._canvas = this._document.createElementNS(HTML_NS, "canvas");
|
|
this._ctx = this._canvas.getContext("2d");
|
|
}
|
|
let canvas = this._canvas;
|
|
let ctx = this._ctx;
|
|
|
|
// Nuke the context.
|
|
let canvasWidth = canvas.width = this._waterfallWidth;
|
|
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
|
|
|
|
// Start over.
|
|
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
|
let pixelArray = imageData.data;
|
|
|
|
let buf = new ArrayBuffer(pixelArray.length);
|
|
let view8bit = new Uint8ClampedArray(buf);
|
|
let view32bit = new Uint32Array(buf);
|
|
|
|
// Build new millisecond tick lines...
|
|
let [r, g, b] = WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
|
|
let alphaComponent = WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
|
|
let tickInterval = this._findOptimalTickInterval({
|
|
ticksMultiple: WATERFALL_BACKGROUND_TICKS_MULTIPLE,
|
|
ticksSpacingMin: WATERFALL_BACKGROUND_TICKS_SPACING_MIN,
|
|
dataScale: dataScale
|
|
});
|
|
|
|
// Insert one pixel for each division on each scale.
|
|
for (let i = 1; i <= WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
|
|
let increment = tickInterval * Math.pow(2, i);
|
|
for (let x = 0; x < canvasWidth; x += increment) {
|
|
let position = x | 0;
|
|
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
|
}
|
|
alphaComponent += WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
|
}
|
|
|
|
// Flush the image data and cache the waterfall background.
|
|
pixelArray.set(view8bit);
|
|
ctx.putImageData(imageData, 0, 0);
|
|
this._document.mozSetImageElement("waterfall-background", canvas);
|
|
},
|
|
|
|
/**
|
|
* Finds the optimal tick interval between time markers in this timeline.
|
|
*
|
|
* @param number ticksMultiple
|
|
* @param number ticksSpacingMin
|
|
* @param number dataScale
|
|
* @return number
|
|
*/
|
|
_findOptimalTickInterval: function({ ticksMultiple, ticksSpacingMin, dataScale }) {
|
|
let timingStep = ticksMultiple;
|
|
|
|
while (true) {
|
|
let scaledStep = dataScale * timingStep;
|
|
if (scaledStep < ticksSpacingMin) {
|
|
timingStep <<= 1;
|
|
continue;
|
|
}
|
|
return scaledStep;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if this is document is in RTL mode.
|
|
* @return boolean
|
|
*/
|
|
_getRTL: function() {
|
|
let win = this._document.defaultView;
|
|
let doc = this._document.documentElement;
|
|
return win.getComputedStyle(doc, null).direction == "rtl";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks if a given marker is in the specified time range.
|
|
*
|
|
* @param object e
|
|
* The marker containing the { start, end } timestamps.
|
|
* @param number start
|
|
* The earliest allowed time.
|
|
* @param number end
|
|
* The latest allowed time.
|
|
* @return boolean
|
|
* True if the marker fits inside the specified time range.
|
|
*/
|
|
function isMarkerInRange(e, start, end) {
|
|
return (e.start >= start && e.end <= end) || // bounds inside
|
|
(e.start < start && e.end > end) || // bounds outside
|
|
(e.start < start && e.end >= start && e.end <= end) || // overlap start
|
|
(e.end > end && e.start >= start && e.start <= end); // overlap end
|
|
}
|
|
|
|
exports.Waterfall = Waterfall;
|