diff --git a/browser/devtools/shared/test/browser.ini b/browser/devtools/shared/test/browser.ini index 89cf5dd3b67..71bfda0caa1 100644 --- a/browser/devtools/shared/test/browser.ini +++ b/browser/devtools/shared/test/browser.ini @@ -19,6 +19,7 @@ support-files = [browser_flame-graph-02.js] [browser_flame-graph-03a.js] [browser_flame-graph-03b.js] +[browser_flame-graph-03c.js] [browser_flame-graph-04.js] [browser_flame-graph-utils-01.js] [browser_flame-graph-utils-02.js] diff --git a/browser/devtools/shared/test/browser_flame-graph-03a.js b/browser/devtools/shared/test/browser_flame-graph-03a.js index f42cf2ff8db..117413f7b3c 100644 --- a/browser/devtools/shared/test/browser_flame-graph-03a.js +++ b/browser/devtools/shared/test/browser_flame-graph-03a.js @@ -24,6 +24,8 @@ function* performTest() { let graph = new FlameGraph(doc.body, 1); graph.fixedWidth = TEST_WIDTH; graph.fixedHeight = TEST_HEIGHT; + graph.horizontalPanThreshold = 0; + graph.verticalPanThreshold = 0; yield graph.ready(); diff --git a/browser/devtools/shared/test/browser_flame-graph-03c.js b/browser/devtools/shared/test/browser_flame-graph-03c.js new file mode 100644 index 00000000000..120de003e2f --- /dev/null +++ b/browser/devtools/shared/test/browser_flame-graph-03c.js @@ -0,0 +1,139 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that vertical panning in the flame graph widget works properly. + +let TEST_DATA = [{ color: "#f00", blocks: [{ x: 0, y: 0, width: 50, height: 20, text: "FOO" }, { x: 50, y: 0, width: 100, height: 20, text: "BAR" }] }, { color: "#00f", blocks: [{ x: 0, y: 30, width: 30, height: 20, text: "BAZ" }] }]; +let TEST_BOUNDS = { startTime: 0, endTime: 150 }; +let TEST_WIDTH = 200; +let TEST_HEIGHT = 100; +let TEST_DPI_DENSITIY = 2; + +let {FlameGraph} = Cu.import("resource:///modules/devtools/FlameGraph.jsm", {}); +let {Promise} = devtools.require("resource://gre/modules/Promise.jsm"); + +add_task(function*() { + yield promiseTab("about:blank"); + yield performTest(); + gBrowser.removeCurrentTab(); +}); + +function* performTest() { + let [host, win, doc] = yield createHost(); + doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;"); + + let graph = new FlameGraph(doc.body, TEST_DPI_DENSITIY); + graph.fixedWidth = TEST_WIDTH; + graph.fixedHeight = TEST_HEIGHT; + + yield graph.ready(); + + testGraph(graph); + + yield graph.destroy(); + host.destroy(); +} + +function testGraph(graph) { + graph.setData({ data: TEST_DATA, bounds: TEST_BOUNDS }); + + // Drag up vertically only. + + dragStart(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (1)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (1)."); + is(graph.getViewRange().verticalOffset | 0, 0, + "The vertical offset is correct (1)."); + + hover(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2 - 50); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (2)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (2)."); + is(graph.getViewRange().verticalOffset | 0, 17, + "The vertical offset is correct (2)."); + + dragStop(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2 - 100); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (3)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (3)."); + is(graph.getViewRange().verticalOffset | 0, 42, + "The vertical offset is correct (3)."); + + // Drag down strongly vertically and slightly horizontally. + + dragStart(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (4)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (4)."); + is(graph.getViewRange().verticalOffset | 0, 42, + "The vertical offset is correct (4)."); + + hover(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2 + 50); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (5)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (5)."); + is(graph.getViewRange().verticalOffset | 0, 25, + "The vertical offset is correct (5)."); + + dragStop(graph, TEST_WIDTH / 2 + 100, TEST_HEIGHT / 2 + 500); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (6)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (6)."); + is(graph.getViewRange().verticalOffset | 0, 0, + "The vertical offset is correct (6)."); + + // Drag up slightly vertically and strongly horizontally. + + dragStart(graph, TEST_WIDTH / 2, TEST_HEIGHT / 2); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (7)."); + is(graph.getViewRange().endTime | 0, 150, + "The selection end boundary is correct (7)."); + is(graph.getViewRange().verticalOffset | 0, 0, + "The vertical offset is correct (7)."); + + hover(graph, TEST_WIDTH / 2 + 50, TEST_HEIGHT / 2); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (8)."); + is(graph.getViewRange().endTime | 0, 116, + "The selection end boundary is correct (8)."); + is(graph.getViewRange().verticalOffset | 0, 0, + "The vertical offset is correct (8)."); + + dragStop(graph, TEST_WIDTH / 2 + 500, TEST_HEIGHT / 2 + 100); + is(graph.getViewRange().startTime | 0, 0, + "The selection start boundary is correct (9)."); + is(graph.getViewRange().endTime | 0, 0, + "The selection end boundary is correct (9)."); + is(graph.getViewRange().verticalOffset | 0, 0, + "The vertical offset is correct (9)."); +} + +// EventUtils just doesn't work! + +function hover(graph, x, y = 1) { + x /= window.devicePixelRatio; + y /= window.devicePixelRatio; + graph._onMouseMove({ clientX: x, clientY: y }); +} + +function dragStart(graph, x, y = 1) { + x /= window.devicePixelRatio; + y /= window.devicePixelRatio; + graph._onMouseMove({ clientX: x, clientY: y }); + graph._onMouseDown({ clientX: x, clientY: y }); +} + +function dragStop(graph, x, y = 1) { + x /= window.devicePixelRatio; + y /= window.devicePixelRatio; + graph._onMouseMove({ clientX: x, clientY: y }); + graph._onMouseUp({ clientX: x, clientY: y }); +} diff --git a/browser/devtools/shared/widgets/FlameGraph.jsm b/browser/devtools/shared/widgets/FlameGraph.jsm index ab1b283fc8a..1d05cfe4b6a 100644 --- a/browser/devtools/shared/widgets/FlameGraph.jsm +++ b/browser/devtools/shared/widgets/FlameGraph.jsm @@ -26,11 +26,15 @@ const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00035; const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.5; const GRAPH_MIN_SELECTION_WIDTH = 0.001; // ms +const GRAPH_HORIZONTAL_PAN_THRESHOLD = 10; // px +const GRAPH_VERTICAL_PAN_THRESHOLD = 30; // px + const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100; const TIMELINE_TICKS_MULTIPLE = 5; // ms const TIMELINE_TICKS_SPACING_MIN = 75; // px const OVERVIEW_HEADER_HEIGHT = 16; // px +const OVERVIEW_HEADER_BACKGROUND = "rgba(255,255,255,0.7)"; const OVERVIEW_HEADER_TEXT_COLOR = "#18191a"; const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif"; @@ -121,9 +125,11 @@ function FlameGraph(parent, sharpness) { this._height = canvas.height = bounds.height * this._pixelRatio; this._ctx = canvas.getContext("2d"); - this._bounds = new GraphSelection(); - this._selection = new GraphSelection(); - this._selectionDragger = new GraphSelectionDragger(); + this._bounds = new GraphArea(); + this._selection = new GraphArea(); + this._selectionDragger = new GraphAreaDragger(); + this._verticalOffset = 0; + this._verticalOffsetDragger = new GraphAreaDragger(0); // Calculating text widths is necessary to trim the text inside the blocks // while the scaling changes (e.g. via scrolling). This is very expensive, @@ -198,6 +204,8 @@ FlameGraph.prototype = { this._bounds = null; this._selection = null; this._selectionDragger = null; + this._verticalOffset = null; + this._verticalOffsetDragger = null; this._textWidthsCache = null; this._data = null; @@ -208,6 +216,7 @@ FlameGraph.prototype = { /** * Rendering options. Subclasses should override these. */ + overviewHeaderBackgroundColor: OVERVIEW_HEADER_BACKGROUND, overviewHeaderTextColor: OVERVIEW_HEADER_TEXT_COLOR, overviewTimelineStrokes: OVERVIEW_TIMELINE_STROKES, blockTextColor: FLAME_GRAPH_BLOCK_TEXT_COLOR, @@ -219,6 +228,12 @@ FlameGraph.prototype = { fixedWidth: null, fixedHeight: null, + /** + * How much preliminar drag is necessary to determine the panning direction. + */ + horizontalPanThreshold: GRAPH_HORIZONTAL_PAN_THRESHOLD, + verticalPanThreshold: GRAPH_VERTICAL_PAN_THRESHOLD, + /** * The units used in the overhead ticks. Could be "ms", for example. * Overwrite this with your own localized format. @@ -278,12 +293,13 @@ FlameGraph.prototype = { }, /** - * Sets the selection (i.e. the 'view range') bounds. + * Sets the selection and vertical offset (i.e. the 'view range'). * @return number */ - setViewRange: function({ startTime, endTime }) { + setViewRange: function({ startTime, endTime }, verticalOffset = 0) { this._selection.start = startTime * this._pixelRatio; this._selection.end = endTime * this._pixelRatio; + this._verticalOffset = verticalOffset * this._pixelRatio; this._shouldRedraw = true; }, @@ -299,13 +315,14 @@ FlameGraph.prototype = { }, /** - * Gets the current selection (i.e. the 'view range'). + * Gets the current selection and vertical offset (i.e. the 'view range'). * @return number */ getViewRange: function() { return { startTime: this._selection.start / this._pixelRatio, - endTime: this._selection.end / this._pixelRatio + endTime: this._selection.end / this._pixelRatio, + verticalOffset: this._verticalOffset / this._pixelRatio }; }, @@ -366,8 +383,8 @@ FlameGraph.prototype = { let selection = this._selection; let selectionWidth = selection.end - selection.start; let selectionScale = canvasWidth / selectionWidth; + this._drawPyramid(this._data, this._verticalOffset, selection.start, selectionScale); this._drawTicks(selection.start, selectionScale); - this._drawPyramid(this._data, selection.start, selectionScale); this._shouldRedraw = false; }, @@ -385,6 +402,9 @@ FlameGraph.prototype = { let canvasHeight = this._height; let scaledOffset = dataOffset * dataScale; + ctx.fillStyle = this.overviewHeaderBackgroundColor; + ctx.fillRect(0, 0, canvasWidth, OVERVIEW_HEADER_HEIGHT * this._pixelRatio); + let fontSize = OVERVIEW_HEADER_TEXT_FONT_SIZE * this._pixelRatio; let fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY; let textPaddingLeft = OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio; @@ -415,48 +435,50 @@ FlameGraph.prototype = { * * @param object dataSource * The data source. See the constructor for more information. + * @param number verticalOffset + * Offsets the drawing vertically by the specified amount. * @param number dataOffset, dataScale * Offsets and scales the data source by the specified amount. * This is used for scrolling the visualization. */ - _drawPyramid: function(dataSource, dataOffset, dataScale) { + _drawPyramid: function(dataSource, verticalOffset, dataOffset, dataScale) { let ctx = this._ctx; let fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE * this._pixelRatio; let fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY; - let visibleBlocks = this._drawPyramidFill(dataSource, dataOffset, dataScale); + let visibleBlocksInfo = this._drawPyramidFill(dataSource, verticalOffset, dataOffset, dataScale); ctx.textBaseline = "middle"; ctx.font = fontSize + "px " + fontFamily; ctx.fillStyle = this.blockTextColor; - this._drawPyramidText(visibleBlocks, dataOffset, dataScale); + this._drawPyramidText(visibleBlocksInfo, verticalOffset, dataOffset, dataScale); }, /** * Fills all block inside this graph's pyramid. * @see FlameGraph.prototype._drawPyramid */ - _drawPyramidFill: function(dataSource, dataOffset, dataScale) { - let visibleBlocksStore = []; + _drawPyramidFill: function(dataSource, verticalOffset, dataOffset, dataScale) { + let visibleBlocksInfoStore = []; let minVisibleBlockWidth = this._overflowCharWidth; for (let { color, blocks } of dataSource) { this._drawBlocksFill( - color, blocks, dataOffset, dataScale, - visibleBlocksStore, minVisibleBlockWidth); + color, blocks, verticalOffset, dataOffset, dataScale, + visibleBlocksInfoStore, minVisibleBlockWidth); } - return visibleBlocksStore; + return visibleBlocksInfoStore; }, /** * Adds the text for all block inside this graph's pyramid. * @see FlameGraph.prototype._drawPyramid */ - _drawPyramidText: function(blocks, dataOffset, dataScale) { - for (let block of blocks) { - this._drawBlockText(block, dataOffset, dataScale); + _drawPyramidText: function(blocksInfo, verticalOffset, dataOffset, dataScale) { + for (let { block, rect } of blocksInfo) { + this._drawBlockText(block, rect, verticalOffset, dataOffset, dataScale); } }, @@ -468,19 +490,22 @@ FlameGraph.prototype = { * @param array blocks * A list of { x, y, width, height } objects visually representing * all the blocks sharing this particular style. + * @param number verticalOffset + * Offsets the drawing vertically by the specified amount. * @param number dataOffset, dataScale * Offsets and scales the data source by the specified amount. * This is used for scrolling the visualization. - * @param array visibleBlocksStore - * An array to store all the visible blocks into, after drawing them. + * @param array visibleBlocksInfoStore + * An array to store all the visible blocks into, along with the + * final baked coordinates and dimensions, after drawing them. * The provided array will be populated. * @param number minVisibleBlockWidth * The minimum width of the blocks that will be added into - * the `visibleBlocksStore`. + * the `visibleBlocksInfoStore`. */ _drawBlocksFill: function( - color, blocks, dataOffset, dataScale, - visibleBlocksStore, minVisibleBlockWidth) + color, blocks, verticalOffset, dataOffset, dataScale, + visibleBlocksInfoStore, minVisibleBlockWidth) { let ctx = this._ctx; let canvasWidth = this._width; @@ -493,13 +518,14 @@ FlameGraph.prototype = { for (let block of blocks) { let { x, y, width, height } = block; let rectLeft = x * this._pixelRatio * dataScale - scaledOffset; - let rectTop = (y + OVERVIEW_HEADER_HEIGHT) * this._pixelRatio; + let rectTop = (y - verticalOffset + OVERVIEW_HEADER_HEIGHT) * this._pixelRatio; let rectWidth = width * this._pixelRatio * dataScale; let rectHeight = height * this._pixelRatio; if (rectLeft > canvasWidth || // Too far right. rectLeft < -rectWidth || // Too far left. - rectTop > canvasHeight) { // Too far bottom. + rectTop > canvasHeight || // Too far bottom. + rectTop < -rectHeight) { // Too far top. continue; } @@ -524,7 +550,10 @@ FlameGraph.prototype = { // Populate the visible blocks store with this block if the width // is longer than a given threshold. if (rectWidth > minVisibleBlockWidth) { - visibleBlocksStore.push(block); + visibleBlocksInfoStore.push({ + block: block, + rect: { rectLeft, rectTop, rectWidth, rectHeight } + }); } } @@ -537,24 +566,29 @@ FlameGraph.prototype = { * @param object block * A single { x, y, width, height, text } object visually representing * the block containing the text. + * @param object rect + * A single { rectLeft, rectTop, rectWidth, rectHeight } object + * representing the final baked coordinates of the drawn rectangle. + * Think of them as screen-space values, vs. object-space values. These + * differ from the scalars in `block` when the graph is scaled/panned. + * @param number verticalOffset + * Offsets the drawing vertically by the specified amount. * @param number dataOffset, dataScale * Offsets and scales the data source by the specified amount. * This is used for scrolling the visualization. */ - _drawBlockText: function(block, dataOffset, dataScale) { + _drawBlockText: function(block, rect, verticalOffset, dataOffset, dataScale) { let ctx = this._ctx; let scaledOffset = dataOffset * dataScale; let { x, y, width, height, text } = block; + let { rectLeft, rectTop, rectWidth, rectHeight } = rect; let paddingTop = FLAME_GRAPH_BLOCK_TEXT_PADDING_TOP * this._pixelRatio; let paddingLeft = FLAME_GRAPH_BLOCK_TEXT_PADDING_LEFT * this._pixelRatio; let paddingRight = FLAME_GRAPH_BLOCK_TEXT_PADDING_RIGHT * this._pixelRatio; let totalHorizontalPadding = paddingLeft + paddingRight; - let rectLeft = x * this._pixelRatio * dataScale - scaledOffset; - let rectWidth = width * this._pixelRatio * dataScale; - // Clamp the blocks position to start at 0. Avoid negative X coords, // to properly place the text inside the blocks. if (rectLeft < 0) { @@ -563,7 +597,7 @@ FlameGraph.prototype = { } let textLeft = rectLeft + paddingLeft; - let textTop = (y + height / 2 + OVERVIEW_HEADER_HEIGHT) * this._pixelRatio + paddingTop; + let textTop = rectTop + rectHeight / 2 + paddingTop; let textAvailableWidth = rectWidth - totalHorizontalPadding; // Massage the text to fit inside a given width. This clamps the string @@ -672,6 +706,7 @@ FlameGraph.prototype = { _onMouseMove: function(e) { let offset = this._getContainerOffset(); let mouseX = (e.clientX - offset.left) * this._pixelRatio; + let mouseY = (e.clientY - offset.top) * this._pixelRatio; let canvasWidth = this._width; let canvasHeight = this._height; @@ -680,14 +715,41 @@ FlameGraph.prototype = { let selectionWidth = selection.end - selection.start; let selectionScale = canvasWidth / selectionWidth; - let dragger = this._selectionDragger; - if (dragger.origin != null) { - selection.start = dragger.anchor.start + (dragger.origin - mouseX) / selectionScale; - selection.end = dragger.anchor.end + (dragger.origin - mouseX) / selectionScale; + let horizDrag = this._selectionDragger; + let vertDrag = this._verticalOffsetDragger; + + // Avoid dragging both horizontally and vertically at the same time, + // as this doesn't feel natural. Based on a minimum distance, enable either + // one, and remember the drag direction to offset the mouse coords later. + if (!this._horizontalDragEnabled && !this._verticalDragEnabled) { + let horizDiff = Math.abs(horizDrag.origin - mouseX); + if (horizDiff > this.horizontalPanThreshold) { + this._horizontalDragDirection = Math.sign(horizDrag.origin - mouseX); + this._horizontalDragEnabled = true; + } + let vertDiff = Math.abs(vertDrag.origin - mouseY); + if (vertDiff > this.verticalPanThreshold) { + this._verticalDragDirection = Math.sign(vertDrag.origin - mouseY); + this._verticalDragEnabled = true; + } + } + + if (horizDrag.origin != null && this._horizontalDragEnabled) { + let relativeX = mouseX + this._horizontalDragDirection * this.horizontalPanThreshold; + selection.start = horizDrag.anchor.start + (horizDrag.origin - relativeX) / selectionScale; + selection.end = horizDrag.anchor.end + (horizDrag.origin - relativeX) / selectionScale; this._normalizeSelectionBounds(); this._shouldRedraw = true; this.emit("selecting"); } + + if (vertDrag.origin != null && this._verticalDragEnabled) { + let relativeY = mouseY + this._verticalDragDirection * this.verticalPanThreshold; + this._verticalOffset = vertDrag.anchor + (vertDrag.origin - relativeY) / this._pixelRatio; + this._normalizeVerticalOffset(); + this._shouldRedraw = true; + this.emit("panning-vertically"); + } }, /** @@ -696,11 +758,19 @@ FlameGraph.prototype = { _onMouseDown: function(e) { let offset = this._getContainerOffset(); let mouseX = (e.clientX - offset.left) * this._pixelRatio; + let mouseY = (e.clientY - offset.top) * this._pixelRatio; this._selectionDragger.origin = mouseX; this._selectionDragger.anchor.start = this._selection.start; this._selectionDragger.anchor.end = this._selection.end; - this._canvas.setAttribute("input", "adjusting-selection-boundary"); + + this._verticalOffsetDragger.origin = mouseY; + this._verticalOffsetDragger.anchor = this._verticalOffset; + + this._horizontalDragEnabled = false; + this._verticalDragEnabled = false; + + this._canvas.setAttribute("input", "adjusting-view-area"); }, /** @@ -708,6 +778,11 @@ FlameGraph.prototype = { */ _onMouseUp: function() { this._selectionDragger.origin = null; + this._verticalOffsetDragger.origin = null; + this._horizontalDragEnabled = false; + this._horizontalDragDirection = 0; + this._verticalDragEnabled = false; + this._verticalDragDirection = 0; this._canvas.removeAttribute("input"); }, @@ -782,6 +857,14 @@ FlameGraph.prototype = { this._selection.end = selectionEnd; }, + /** + * Makes sure that the current vertical offset is within the allowed + * panning range. + */ + _normalizeVerticalOffset: function() { + this._verticalOffset = Math.max(this._verticalOffset, 0); + }, + /** * * Finds the optimal tick interval between time markers in this graph. diff --git a/browser/devtools/shared/widgets/Graphs.jsm b/browser/devtools/shared/widgets/Graphs.jsm index 2dbc791a91c..41d5c5bf0bc 100644 --- a/browser/devtools/shared/widgets/Graphs.jsm +++ b/browser/devtools/shared/widgets/Graphs.jsm @@ -12,9 +12,9 @@ const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter. this.EXPORTED_SYMBOLS = [ "GraphCursor", - "GraphSelection", - "GraphSelectionDragger", - "GraphSelectionResizer", + "GraphArea", + "GraphAreaDragger", + "GraphAreaResizer", "AbstractCanvasGraph", "LineGraphWidget", "BarGraphWidget", @@ -101,17 +101,17 @@ this.GraphCursor = function() { this.y = null; }; -this.GraphSelection = function() { +this.GraphArea = function() { this.start = null; this.end = null; }; -this.GraphSelectionDragger = function() { +this.GraphAreaDragger = function(anchor = new GraphArea()) { this.origin = null; - this.anchor = new GraphSelection(); + this.anchor = anchor; }; -this.GraphSelectionResizer = function() { +this.GraphAreaResizer = function() { this.margin = null; }; @@ -178,9 +178,9 @@ this.AbstractCanvasGraph = function(parent, name, sharpness) { this._ctx.mozImageSmoothingEnabled = false; this._cursor = new GraphCursor(); - this._selection = new GraphSelection(); - this._selectionDragger = new GraphSelectionDragger(); - this._selectionResizer = new GraphSelectionResizer(); + this._selection = new GraphArea(); + this._selectionDragger = new GraphAreaDragger(); + this._selectionResizer = new GraphAreaResizer(); this._onAnimationFrame = this._onAnimationFrame.bind(this); this._onMouseMove = this._onMouseMove.bind(this); diff --git a/browser/themes/shared/devtools/widgets.inc.css b/browser/themes/shared/devtools/widgets.inc.css index 7e4e7707d8a..1bdd308cd30 100644 --- a/browser/themes/shared/devtools/widgets.inc.css +++ b/browser/themes/shared/devtools/widgets.inc.css @@ -864,6 +864,10 @@ cursor: col-resize; } +.graph-widget-canvas[input=adjusting-view-area] { + cursor: grabbing; +} + .graph-widget-canvas[input=hovering-selection-contents] { cursor: grab; }