diff options
Diffstat (limited to 'devtools/client/netmonitor/src/widgets/WaterfallBackground.js')
-rw-r--r-- | devtools/client/netmonitor/src/widgets/WaterfallBackground.js | 163 |
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; |