summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/components/tree-map/canvas-utils.js
blob: c7d67a0bf0c78fbadd2721349a85bd6dcef83cba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* 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/. */

/* eslint-env browser */

"use strict";

/**
 * Create 2 canvases and contexts for drawing onto, 1 main canvas, and 1 zoom
 * canvas. The main canvas dimensions match the parent div, but the CSS can be
 * transformed to be zoomed and dragged around (potentially creating a blurry
 * canvas once zoomed in). The zoom canvas is a zoomed in section that matches
 * the parent div's dimensions and is kept in place through CSS. A zoomed in
 * view of the visualization is drawn onto this canvas, providing a crisp zoomed
 * in view of the tree map.
 */
const { debounce } = require("sdk/lang/functional");
const EventEmitter = require("devtools/shared/event-emitter");

const HTML_NS = "http://www.w3.org/1999/xhtml";
const FULLSCREEN_STYLE = {
  width: "100%",
  height: "100%",
  position: "absolute",
};

/**
 * Create the canvases, resize handlers, and return references to them all
 *
 * @param  {HTMLDivElement} parentEl
 * @param  {Number} debounceRate
 * @return {Object}
 */
function Canvases(parentEl, debounceRate) {
  EventEmitter.decorate(this);
  this.container = createContainingDiv(parentEl);

  // This canvas contains all of the treemap
  this.main = createCanvas(this.container, "main");
  // This canvas contains only the zoomed in portion, overlaying the main canvas
  this.zoom = createCanvas(this.container, "zoom");

  this.removeHandlers = handleResizes(this, debounceRate);
}

Canvases.prototype = {

  /**
   * Remove the handlers and elements
   *
   * @return {type}  description
   */
  destroy : function () {
    this.removeHandlers();
    this.container.removeChild(this.main.canvas);
    this.container.removeChild(this.zoom.canvas);
  }
};

module.exports = Canvases;

/**
 * Create the containing div
 *
 * @param  {HTMLDivElement} parentEl
 * @return {HTMLDivElement}
 */
function createContainingDiv(parentEl) {
  let div = parentEl.ownerDocument.createElementNS(HTML_NS, "div");
  Object.assign(div.style, FULLSCREEN_STYLE);
  parentEl.appendChild(div);
  return div;
}

/**
 * Create a canvas and context
 *
 * @param  {HTMLDivElement} container
 * @param  {String} className
 * @return {Object} { canvas, ctx }
 */
function createCanvas(container, className) {
  let window = container.ownerDocument.defaultView;
  let canvas = container.ownerDocument.createElementNS(HTML_NS, "canvas");
  container.appendChild(canvas);
  canvas.width = container.offsetWidth * window.devicePixelRatio;
  canvas.height = container.offsetHeight * window.devicePixelRatio;
  canvas.className = className;

  Object.assign(canvas.style, FULLSCREEN_STYLE, {
    pointerEvents: "none"
  });

  let ctx = canvas.getContext("2d");

  return { canvas, ctx };
}

/**
 * Resize the canvases' resolutions, and fires out the onResize callback
 *
 * @param  {HTMLDivElement} container
 * @param  {Object} canvases
 * @param  {Number} debounceRate
 */
function handleResizes(canvases, debounceRate) {
  let { container, main, zoom } = canvases;
  let window = container.ownerDocument.defaultView;

  function resize() {
    let width = container.offsetWidth * window.devicePixelRatio;
    let height = container.offsetHeight * window.devicePixelRatio;

    main.canvas.width = width;
    main.canvas.height = height;
    zoom.canvas.width = width;
    zoom.canvas.height = height;

    canvases.emit("resize");
  }

  // Tests may not need debouncing
  let debouncedResize = debounceRate > 0
    ? debounce(resize, debounceRate)
    : resize;

  window.addEventListener("resize", debouncedResize, false);
  resize();

  return function removeResizeHandlers() {
    window.removeEventListener("resize", debouncedResize, false);
  };
}