tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WaterfallBackground.js (5353B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  getCssVariableColor,
      9 } = require("resource://devtools/client/shared/theme.js");
     10 const {
     11  REQUESTS_WATERFALL,
     12 } = require("resource://devtools/client/netmonitor/src/constants.js");
     13 
     14 const HTML_NS = "http://www.w3.org/1999/xhtml";
     15 const STATE_KEYS = [
     16  "firstRequestStartedMs",
     17  "scale",
     18  "timingMarkers",
     19  "waterfallWidth",
     20 ];
     21 
     22 /**
     23 * Creates the background displayed on each waterfall view in this container.
     24 */
     25 class WaterfallBackground {
     26  constructor() {
     27    this.canvas = document.createElementNS(HTML_NS, "canvas");
     28    this.ctx = this.canvas.getContext("2d");
     29    this.prevState = {};
     30  }
     31 
     32  /**
     33   * Changes the element being used as the CSS background for a background
     34   * with a given background element ID.
     35   *
     36   * The funtion wrap the Firefox only API. Waterfall Will not draw the
     37   * vertical line when running on non-firefox browser.
     38   * Could be fixed by Bug 1308695
     39   */
     40  setImageElement(imageElementId, imageElement) {
     41    if (document.mozSetImageElement) {
     42      document.mozSetImageElement(imageElementId, imageElement);
     43    }
     44  }
     45 
     46  draw(state) {
     47    // Do a shallow compare of the previous and the new state
     48    const shouldUpdate = STATE_KEYS.some(
     49      key => this.prevState[key] !== state[key]
     50    );
     51    if (!shouldUpdate) {
     52      return;
     53    }
     54 
     55    this.prevState = state;
     56 
     57    if (state.waterfallWidth === null || state.scale === null) {
     58      this.setImageElement("waterfall-background", null);
     59      return;
     60    }
     61 
     62    // Nuke the context.
     63    const canvasWidth = (this.canvas.width = Math.max(
     64      state.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH,
     65      1
     66    ));
     67    // Awww yeah, 1px, repeats on Y axis.
     68    const canvasHeight = (this.canvas.height = 1);
     69 
     70    // Start over.
     71    const imageData = this.ctx.createImageData(canvasWidth, canvasHeight);
     72    const pixelArray = imageData.data;
     73 
     74    const buf = new ArrayBuffer(pixelArray.length);
     75    const view8bit = new Uint8ClampedArray(buf);
     76    const view32bit = new Uint32Array(buf);
     77 
     78    // Build new millisecond tick lines...
     79    let timingStep = REQUESTS_WATERFALL.BACKGROUND_TICKS_MULTIPLE;
     80    let optimalTickIntervalFound = false;
     81    let scaledStep;
     82 
     83    while (!optimalTickIntervalFound) {
     84      // Ignore any divisions that would end up being too close to each other.
     85      scaledStep = state.scale * timingStep;
     86      if (scaledStep < REQUESTS_WATERFALL.BACKGROUND_TICKS_SPACING_MIN) {
     87        timingStep <<= 1;
     88        continue;
     89      }
     90      optimalTickIntervalFound = true;
     91    }
     92 
     93    const isRTL = document.dir === "rtl";
     94    const [r, g, b] = REQUESTS_WATERFALL.BACKGROUND_TICKS_COLOR_RGB;
     95    let alphaComponent = REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_MIN;
     96 
     97    function drawPixelAt(offset, color) {
     98      const position = (isRTL ? canvasWidth - offset : offset - 1) | 0;
     99      const [rc, gc, bc, ac] = color;
    100      view32bit[position] = (ac << 24) | (bc << 16) | (gc << 8) | rc;
    101    }
    102 
    103    // Insert one pixel for each division on each scale.
    104    for (let i = 1; i <= REQUESTS_WATERFALL.BACKGROUND_TICKS_SCALES; i++) {
    105      const increment = scaledStep * Math.pow(2, i);
    106      for (let x = 0; x < canvasWidth; x += increment) {
    107        drawPixelAt(x, [r, g, b, alphaComponent]);
    108      }
    109      alphaComponent += REQUESTS_WATERFALL.BACKGROUND_TICKS_OPACITY_ADD;
    110    }
    111 
    112    function drawTimestamp(timestamp, color) {
    113      if (timestamp === -1) {
    114        return;
    115      }
    116 
    117      const delta = Math.floor(
    118        (timestamp - state.firstRequestStartedMs) * state.scale
    119      );
    120      drawPixelAt(delta, color);
    121    }
    122 
    123    const { DOMCONTENTLOADED_TICKS_COLOR, LOAD_TICKS_COLOR } =
    124      REQUESTS_WATERFALL;
    125    drawTimestamp(
    126      state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
    127      this.getThemeColorAsRgba(DOMCONTENTLOADED_TICKS_COLOR)
    128    );
    129 
    130    drawTimestamp(
    131      state.timingMarkers.firstDocumentLoadTimestamp,
    132      this.getThemeColorAsRgba(LOAD_TICKS_COLOR)
    133    );
    134 
    135    // Flush the image data and cache the waterfall background.
    136    pixelArray.set(view8bit);
    137    try {
    138      this.ctx.putImageData(imageData, 0, 0);
    139    } catch (e) {
    140      console.error("WaterfallBackground crash error", e);
    141    }
    142 
    143    this.setImageElement("waterfall-background", this.canvas);
    144  }
    145 
    146  /**
    147   * Retrieve a color defined for the provided theme as a rgba array.
    148   *
    149   * @param {string} colorVariableName
    150   *        The name of the variable defining the color
    151   * @return {Array} RGBA array for the color.
    152   */
    153  getThemeColorAsRgba(colorVariableName) {
    154    const colorStr = getCssVariableColor(
    155      colorVariableName,
    156      document.ownerGlobal
    157    );
    158    const { r, g, b, a } =
    159      InspectorUtils.colorToRGBA(colorStr) ||
    160      // In theory we shouldn't get null as a result, but we got reports that it was in
    161      // some cases (Bug 1924882, Bug 1973307).
    162      // Until we actually get to the cause of this, let's use a default color that works
    163      // for both light and dark themes.
    164      InspectorUtils.colorToRGBA("#888");
    165    return [r, g, b, a * 255];
    166  }
    167 
    168  destroy() {
    169    this.setImageElement("waterfall-background", null);
    170  }
    171 }
    172 
    173 module.exports = WaterfallBackground;