summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/widgets/WaterfallBackground.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/netmonitor/src/widgets/WaterfallBackground.js')
-rw-r--r--devtools/client/netmonitor/src/widgets/WaterfallBackground.js163
1 files changed, 163 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/widgets/WaterfallBackground.js b/devtools/client/netmonitor/src/widgets/WaterfallBackground.js
new file mode 100644
index 0000000000..e2be7f5715
--- /dev/null
+++ b/devtools/client/netmonitor/src/widgets/WaterfallBackground.js
@@ -0,0 +1,163 @@
+/* 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,
+} = require("resource://devtools/client/netmonitor/src/constants.js");
+
+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);
+ this.ctx.putImageData(imageData, 0, 0);
+
+ 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;