Source code

Revision control

Copy as Markdown

Other Tools

/* 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";
const { getColor } = require("resource://devtools/client/shared/theme.js");
const { colorUtils } = require("resource://devtools/shared/css/color.js");
const {
REQUESTS_WATERFALL,
const HTML_NS = "http://www.w3.org/1999/xhtml";
const STATE_KEYS = [
"firstRequestStartedMs",
"scale",
"timingMarkers",
"waterfallWidth",
];
/**
* Creates the background displayed on each waterfall view in this container.
*/
class WaterfallBackground {
constructor() {
this.canvas = document.createElementNS(HTML_NS, "canvas");
this.ctx = this.canvas.getContext("2d");
this.prevState = {};
}
/**
* Changes the element being used as the CSS background for a background
* with a given background element ID.
*
* The funtion wrap the Firefox only API. Waterfall Will not draw the
* vertical line when running on non-firefox browser.
* Could be fixed by Bug 1308695
*/
setImageElement(imageElementId, imageElement) {
if (document.mozSetImageElement) {
document.mozSetImageElement(imageElementId, imageElement);
}
}
draw(state) {
// Do a shallow compare of the previous and the new state
const shouldUpdate = STATE_KEYS.some(
key => this.prevState[key] !== state[key]
);
if (!shouldUpdate) {
return;
}
this.prevState = state;
if (state.waterfallWidth === null || state.scale === null) {
this.setImageElement("waterfall-background", null);
return;
}
// Nuke the context.
const canvasWidth = (this.canvas.width = Math.max(
state.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH,
1
));
// Awww yeah, 1px, repeats on Y axis.
const canvasHeight = (this.canvas.height = 1);
// Start over.
const imageData = this.ctx.createImageData(canvasWidth, canvasHeight);
const pixelArray = imageData.data;
const buf = new ArrayBuffer(pixelArray.length);
const view8bit = new Uint8ClampedArray(buf);
const view32bit = new Uint32Array(buf);
// Build new millisecond tick lines...
let timingStep = REQUESTS_WATERFALL.BACKGROUND_TICKS_MULTIPLE;
let optimalTickIntervalFound = false;
let scaledStep;
while (!optimalTickIntervalFound) {
// Ignore any divisions that would end up being too close to each other.
scaledStep = state.scale * timingStep;
if (scaledStep < REQUESTS_WATERFALL.BACKGROUND_TICKS_SPACING_MIN) {
timingStep <<= 1;
continue;
}
optimalTickIntervalFound = true;
}
const isRTL = document.dir === "rtl";
const [r, g, b] = REQUESTS_WATERFALL.BACKGROUND_TICKS_COLOR_RGB;
let alphaComponent = REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_MIN;
function drawPixelAt(offset, color) {
const position = (isRTL ? canvasWidth - offset : offset - 1) | 0;
const [rc, gc, bc, ac] = color;
view32bit[position] = (ac << 24) | (bc << 16) | (gc << 8) | rc;
}
// Insert one pixel for each division on each scale.
for (let i = 1; i <= REQUESTS_WATERFALL.BACKGROUND_TICKS_SCALES; i++) {
const increment = scaledStep * Math.pow(2, i);
for (let x = 0; x < canvasWidth; x += increment) {
drawPixelAt(x, [r, g, b, alphaComponent]);
}
alphaComponent += REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_ADD;
}
function drawTimestamp(timestamp, color) {
if (timestamp === -1) {
return;
}
const delta = Math.floor(
(timestamp - state.firstRequestStartedMs) * state.scale
);
drawPixelAt(delta, color);
}
const { DOMCONTENTLOADED_TICKS_COLOR, LOAD_TICKS_COLOR } =
REQUESTS_WATERFALL;
drawTimestamp(
state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
this.getThemeColorAsRgba(DOMCONTENTLOADED_TICKS_COLOR, state.theme)
);
drawTimestamp(
state.timingMarkers.firstDocumentLoadTimestamp,
this.getThemeColorAsRgba(LOAD_TICKS_COLOR, state.theme)
);
// Flush the image data and cache the waterfall background.
pixelArray.set(view8bit);
try {
this.ctx.putImageData(imageData, 0, 0);
} catch (e) {
console.error("WaterfallBackground crash error", e);
}
this.setImageElement("waterfall-background", this.canvas);
}
/**
* Retrieve a color defined for the provided theme as a rgba array. The alpha channel is
* forced to the waterfall constant TICKS_COLOR_OPACITY.
*
* @param {String} colorName
* The name of the theme color
* @param {String} theme
* The name of the theme
* @return {Array} RGBA array for the color.
*/
getThemeColorAsRgba(colorName, theme) {
const colorStr = getColor(colorName, theme);
const color = new colorUtils.CssColor(colorStr);
const { r, g, b } = color.getRGBATuple();
return [r, g, b, REQUESTS_WATERFALL.TICKS_COLOR_OPACITY];
}
destroy() {
this.setImageElement("waterfall-background", null);
}
}
module.exports = WaterfallBackground;