Bug 1175686 - Pull line and bar graph widgets outside of Graphs.js into their own files, r=jsantell, a=Mossop
authorVictor Porof <vporof@mozilla.com>
Fri, 19 Jun 2015 11:00:25 -0400
changeset 280601 96669be6be1fd49491e4790959fabb9a46e3c4fd
parent 280600 aa13a5de46046cf8e58d37b00a01e7d7d8bd5164
child 280602 84df534bc80ecf16d31530373ea0f278ce263d8d
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell, Mossop
bugs1175686
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1175686 - Pull line and bar graph widgets outside of Graphs.js into their own files, r=jsantell, a=Mossop
browser/devtools/performance/modules/widgets/graphs.js
browser/devtools/shared/moz.build
browser/devtools/shared/test/browser_graphs-01.js
browser/devtools/shared/test/browser_graphs-02.js
browser/devtools/shared/test/browser_graphs-03.js
browser/devtools/shared/test/browser_graphs-04.js
browser/devtools/shared/test/browser_graphs-05.js
browser/devtools/shared/test/browser_graphs-06.js
browser/devtools/shared/test/browser_graphs-07a.js
browser/devtools/shared/test/browser_graphs-07b.js
browser/devtools/shared/test/browser_graphs-07c.js
browser/devtools/shared/test/browser_graphs-07d.js
browser/devtools/shared/test/browser_graphs-07e.js
browser/devtools/shared/test/browser_graphs-08.js
browser/devtools/shared/test/browser_graphs-09a.js
browser/devtools/shared/test/browser_graphs-09b.js
browser/devtools/shared/test/browser_graphs-09c.js
browser/devtools/shared/test/browser_graphs-09d.js
browser/devtools/shared/test/browser_graphs-09e.js
browser/devtools/shared/test/browser_graphs-09f.js
browser/devtools/shared/test/browser_graphs-10a.js
browser/devtools/shared/test/browser_graphs-10b.js
browser/devtools/shared/test/browser_graphs-10c.js
browser/devtools/shared/test/browser_graphs-11a.js
browser/devtools/shared/test/browser_graphs-11b.js
browser/devtools/shared/test/browser_graphs-12.js
browser/devtools/shared/test/browser_graphs-13.js
browser/devtools/shared/test/browser_graphs-14.js
browser/devtools/shared/test/browser_graphs-15.js
browser/devtools/shared/widgets/BarGraphWidget.js
browser/devtools/shared/widgets/Graphs.js
browser/devtools/shared/widgets/LineGraphWidget.js
browser/devtools/webaudioeditor/includes.js
browser/themes/shared/devtools/widgets.inc.css
--- a/browser/devtools/performance/modules/widgets/graphs.js
+++ b/browser/devtools/performance/modules/widgets/graphs.js
@@ -5,18 +5,18 @@
 
 /**
  * This file contains the base line graph that all Performance line graphs use.
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
-const { LineGraphWidget } = require("devtools/shared/widgets/Graphs");
-const { BarGraphWidget } = require("devtools/shared/widgets/Graphs");
+const LineGraphWidget = require("devtools/shared/widgets/LineGraphWidget");
+const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget");
 const { CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 loader.lazyRequireGetter(this, "colorUtils",
   "devtools/css-color", true);
--- a/browser/devtools/shared/moz.build
+++ b/browser/devtools/shared/moz.build
@@ -43,20 +43,22 @@ EXTRA_JS_MODULES.devtools.shared += [
     'source-utils.js',
     'telemetry.js',
     'theme-switching.js',
     'theme.js',
     'undo.js'
 ]
 
 EXTRA_JS_MODULES.devtools.shared.widgets += [
+    'widgets/BarGraphWidget.js',
     'widgets/CubicBezierPresets.js',
     'widgets/CubicBezierWidget.js',
     'widgets/FastListWidget.js',
     'widgets/FilterWidget.js',
     'widgets/FlameGraph.js',
     'widgets/Graphs.js',
+    'widgets/LineGraphWidget.js',
     'widgets/MdnDocsWidget.js',
     'widgets/Spectrum.js',
     'widgets/TableWidget.js',
     'widgets/Tooltip.js',
     'widgets/TreeWidget.js',
 ]
--- a/browser/devtools/shared/test/browser_graphs-01.js
+++ b/browser/devtools/shared/test/browser_graphs-01.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets works properly.
 
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
   finish();
 });
--- a/browser/devtools/shared/test/browser_graphs-02.js
+++ b/browser/devtools/shared/test/browser_graphs-02.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets can properly add data, regions and highlights.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-03.js
+++ b/browser/devtools/shared/test/browser_graphs-03.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets can handle clients getting/setting the
 // selection or cursor.
 
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-04.js
+++ b/browser/devtools/shared/test/browser_graphs-04.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets can correctly compare selections and cursors.
 
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-05.js
+++ b/browser/devtools/shared/test/browser_graphs-05.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets can correctly determine which regions are hovered.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-06.js
+++ b/browser/devtools/shared/test/browser_graphs-06.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests if clicking on regions adds a selection spanning that region.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-07a.js
+++ b/browser/devtools/shared/test/browser_graphs-07a.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests if selecting, resizing, moving selections and zooming in/out works.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-07b.js
+++ b/browser/devtools/shared/test/browser_graphs-07b.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests if selections can't be added via clicking, while not allowed.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-07c.js
+++ b/browser/devtools/shared/test/browser_graphs-07c.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests if movement via event dispatching using screenX / screenY
 // works.  All of the other tests directly use the graph's mouse event
 // callbacks with textX / testY for convenience.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-07d.js
+++ b/browser/devtools/shared/test/browser_graphs-07d.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that selections are drawn onto the canvas.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-07e.js
+++ b/browser/devtools/shared/test/browser_graphs-07e.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that selections are drawn onto the canvas.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 let CURRENT_ZOOM = 1;
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
--- a/browser/devtools/shared/test/browser_graphs-08.js
+++ b/browser/devtools/shared/test/browser_graphs-08.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests if a selection is dropped when clicking outside of it.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-09a.js
+++ b/browser/devtools/shared/test/browser_graphs-09a.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that line graphs properly create the gutter and tooltips.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-09b.js
+++ b/browser/devtools/shared/test/browser_graphs-09b.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that line graphs properly use the tooltips configuration properties.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-09c.js
+++ b/browser/devtools/shared/test/browser_graphs-09c.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that line graphs hide the tooltips when there's no data available.
 
 const TEST_DATA = [];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-09d.js
+++ b/browser/devtools/shared/test/browser_graphs-09d.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that line graphs hide the 'max' tooltip when the distance between
 // the 'min' and 'max' tooltip is too small.
 
 const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 59.9 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-09e.js
+++ b/browser/devtools/shared/test/browser_graphs-09e.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that line graphs hide the gutter and tooltips when there's no data,
 // but show them when there is.
 
 const NO_DATA = [];
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-09f.js
+++ b/browser/devtools/shared/test/browser_graphs-09f.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests the constructor options for `min`, `max` and `avg` on displaying the
 // gutter/tooltips and lines.
 
 const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 1 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-10a.js
+++ b/browser/devtools/shared/test/browser_graphs-10a.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graphs properly handle resizing.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-10b.js
+++ b/browser/devtools/shared/test/browser_graphs-10b.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graphs aren't refreshed when the owner window resizes but
 // the graph dimensions stay the same.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-10c.js
+++ b/browser/devtools/shared/test/browser_graphs-10c.js
@@ -1,13 +1,13 @@
 
 // Tests that graphs properly handle resizing.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-11a.js
+++ b/browser/devtools/shared/test/browser_graphs-11a.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that bar graph create a legend as expected.
 
-let {BarGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 const CATEGORIES = [
   { color: "#46afe3", label: "Foo" },
   { color: "#eb5368", label: "Bar" },
   { color: "#70bf53", label: "Baz" }
 ];
 
--- a/browser/devtools/shared/test/browser_graphs-11b.js
+++ b/browser/devtools/shared/test/browser_graphs-11b.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that bar graph's legend items handle mouseover/mouseout.
 
-let {BarGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 const CATEGORIES = [
   { color: "#46afe3", label: "Foo" },
   { color: "#eb5368", label: "Bar" },
   { color: "#70bf53", label: "Baz" }
 ];
 
--- a/browser/devtools/shared/test/browser_graphs-12.js
+++ b/browser/devtools/shared/test/browser_graphs-12.js
@@ -1,14 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that canvas graphs can have their selection linked.
 
-let {LineGraphWidget,BarGraphWidget,CanvasGraphUtils} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
+let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
+let {CanvasGraphUtils} = devtools.require("devtools/shared/widgets/Graphs");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-13.js
+++ b/browser/devtools/shared/test/browser_graphs-13.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets may have a fixed width or height.
 
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-14.js
+++ b/browser/devtools/shared/test/browser_graphs-14.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets correctly emit mouse input events.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/devtools/shared/test/browser_graphs-15.js
+++ b/browser/devtools/shared/test/browser_graphs-15.js
@@ -1,30 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that graph widgets correctly emit mouse input events.
 
 const FAST_FPS = 60;
 const SLOW_FPS = 10;
+
 // Each element represents a second
 const FRAMES= [FAST_FPS, FAST_FPS, FAST_FPS, SLOW_FPS, FAST_FPS];
 const TEST_DATA = [];
 const INTERVAL = 100;
 const DURATION = 5000; // 5s
 let t = 0;
 for (let frameRate of FRAMES) {
   for (let i = 0; i < frameRate; i++) {
     let delta = Math.floor(1000 / frameRate); // Duration between frames at this rate
     t += delta;
     TEST_DATA.push(t);
   }
 }
 
-let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
+let LineGraphWidget = devtools.require("devtools/shared/widgets/LineGraphWidget");
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 
 add_task(function*() {
   yield promiseTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/BarGraphWidget.js
@@ -0,0 +1,476 @@
+"use strict";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+
+const { Heritage, setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
+const { AbstractCanvasGraph, CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+// Bar graph constants.
+
+const GRAPH_DAMPEN_VALUES_FACTOR = 0.75;
+const GRAPH_BARS_MARGIN_TOP = 1; // px
+const GRAPH_BARS_MARGIN_END = 1; // px
+const GRAPH_MIN_BARS_WIDTH = 5; // px
+const GRAPH_MIN_BLOCKS_HEIGHT = 1; // px
+
+const GRAPH_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.0)";
+const GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.25)";
+
+const GRAPH_CLIPHEAD_LINE_COLOR = "#666";
+const GRAPH_SELECTION_LINE_COLOR = "#555";
+const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(0,136,204,0.25)";
+const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
+const GRAPH_REGION_BACKGROUND_COLOR = "transparent";
+const GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
+
+const GRAPH_HIGHLIGHTS_MASK_BACKGROUND = "rgba(255,255,255,0.75)";
+const GRAPH_HIGHLIGHTS_MASK_STRIPES = "rgba(255,255,255,0.5)";
+
+const GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
+
+/**
+ * A bar graph, plotting tuples of values as rectangles.
+ *
+ * @see AbstractCanvasGraph for emitted events and other options.
+ *
+ * Example usage:
+ *   let graph = new BarGraphWidget(node);
+ *   graph.format = ...;
+ *   graph.once("ready", () => {
+ *     graph.setData(src);
+ *   });
+ *
+ * The `graph.format` traits are mandatory and will determine how the values
+ * are styled as "blocks" in every "bar":
+ *   [
+ *     { color: "#f00", label: "Foo" },
+ *     { color: "#0f0", label: "Bar" },
+ *     ...
+ *     { color: "#00f", label: "Baz" }
+ *   ]
+ *
+ * Data source format:
+ *   [
+ *     { delta: x1, values: [y11, y12, ... y1n] },
+ *     { delta: x2, values: [y21, y22, ... y2n] },
+ *     ...
+ *     { delta: xm, values: [ym1, ym2, ... ymn] }
+ *   ]
+ * where each item in the array represents a "bar", for which every value
+ * represents a "block" inside that "bar", plotted at the "delta" position.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the graph.
+ */
+this.BarGraphWidget = function(parent, ...args) {
+  AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
+
+  this.once("ready", () => {
+    this._onLegendMouseOver = this._onLegendMouseOver.bind(this);
+    this._onLegendMouseOut = this._onLegendMouseOut.bind(this);
+    this._onLegendMouseDown = this._onLegendMouseDown.bind(this);
+    this._onLegendMouseUp = this._onLegendMouseUp.bind(this);
+    this._createLegend();
+  });
+};
+
+BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
+  clipheadLineColor: GRAPH_CLIPHEAD_LINE_COLOR,
+  selectionLineColor: GRAPH_SELECTION_LINE_COLOR,
+  selectionBackgroundColor: GRAPH_SELECTION_BACKGROUND_COLOR,
+  selectionStripesColor: GRAPH_SELECTION_STRIPES_COLOR,
+  regionBackgroundColor: GRAPH_REGION_BACKGROUND_COLOR,
+  regionStripesColor: GRAPH_REGION_STRIPES_COLOR,
+
+  /**
+   * List of colors used to fill each block inside every bar, also
+   * corresponding to labels displayed in this graph's legend.
+   * @see constructor
+   */
+  format: null,
+
+  /**
+   * Optionally offsets the `delta` in the data source by this scalar.
+   */
+  dataOffsetX: 0,
+
+  /**
+   * Optionally uses this value instead of the last tick in the data source
+   * to compute the horizontal scaling.
+   */
+  dataDuration: 0,
+
+  /**
+   * The scalar used to multiply the graph values to leave some headroom
+   * on the top.
+   */
+  dampenValuesFactor: GRAPH_DAMPEN_VALUES_FACTOR,
+
+  /**
+   * Bars that are too close too each other in the graph will be combined.
+   * This scalar specifies the required minimum width of each bar.
+   */
+  minBarsWidth: GRAPH_MIN_BARS_WIDTH,
+
+  /**
+   * Blocks in a bar that are too thin inside the bar will not be rendered.
+   * This scalar specifies the required minimum height of each block.
+   */
+  minBlocksHeight: GRAPH_MIN_BLOCKS_HEIGHT,
+
+  /**
+   * Renders the graph's background.
+   * @see AbstractCanvasGraph.prototype.buildBackgroundImage
+   */
+  buildBackgroundImage: function() {
+    let { canvas, ctx } = this._getNamedCanvas("bar-graph-background");
+    let width = this._width;
+    let height = this._height;
+
+    let gradient = ctx.createLinearGradient(0, 0, 0, height);
+    gradient.addColorStop(0, GRAPH_BACKGROUND_GRADIENT_START);
+    gradient.addColorStop(1, GRAPH_BACKGROUND_GRADIENT_END);
+    ctx.fillStyle = gradient;
+    ctx.fillRect(0, 0, width, height);
+
+    return canvas;
+  },
+
+  /**
+   * Renders the graph's data source.
+   * @see AbstractCanvasGraph.prototype.buildGraphImage
+   */
+  buildGraphImage: function() {
+    if (!this.format || !this.format.length) {
+      throw "The graph format traits are mandatory to style the data source.";
+    }
+    let { canvas, ctx } = this._getNamedCanvas("bar-graph-data");
+    let width = this._width;
+    let height = this._height;
+
+    let totalTypes = this.format.length;
+    let totalTicks = this._data.length;
+    let lastTick = this._data[totalTicks - 1].delta;
+
+    let minBarsWidth = this.minBarsWidth * this._pixelRatio;
+    let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
+
+    let duration = this.dataDuration || lastTick;
+    let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
+    let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
+      data: this._data,
+      dataScaleX: dataScaleX,
+      minBarsWidth: minBarsWidth
+    }) * this.dampenValuesFactor;
+
+    // Draw the graph.
+
+    // Iterate over the blocks, then the bars, to draw all rectangles of
+    // the same color in a single pass. See the @constructor for more
+    // information about the data source, and how a "bar" contains "blocks".
+
+    this._blocksBoundingRects = [];
+    let prevHeight = [];
+    let scaledMarginEnd = GRAPH_BARS_MARGIN_END * this._pixelRatio;
+    let scaledMarginTop = GRAPH_BARS_MARGIN_TOP * this._pixelRatio;
+
+    for (let type = 0; type < totalTypes; type++) {
+      ctx.fillStyle = this.format[type].color || "#000";
+      ctx.beginPath();
+
+      let prevRight = 0;
+      let skippedCount = 0;
+      let skippedHeight = 0;
+
+      for (let tick = 0; tick < totalTicks; tick++) {
+        let delta = this._data[tick].delta;
+        let value = this._data[tick].values[type] || 0;
+        let blockRight = (delta - this.dataOffsetX) * dataScaleX;
+        let blockHeight = value * dataScaleY;
+
+        let blockWidth = blockRight - prevRight;
+        if (blockWidth < minBarsWidth) {
+          skippedCount++;
+          skippedHeight += blockHeight;
+          continue;
+        }
+
+        let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
+        if (averageHeight >= minBlocksHeight) {
+          let bottom = height - ~~prevHeight[tick];
+          ctx.moveTo(prevRight, bottom);
+          ctx.lineTo(prevRight, bottom - averageHeight);
+          ctx.lineTo(blockRight, bottom - averageHeight);
+          ctx.lineTo(blockRight, bottom);
+
+          // Remember this block's type and location.
+          this._blocksBoundingRects.push({
+            type: type,
+            start: prevRight,
+            end: blockRight,
+            top: bottom - averageHeight,
+            bottom: bottom
+          });
+
+          if (prevHeight[tick] === undefined) {
+            prevHeight[tick] = averageHeight + scaledMarginTop;
+          } else {
+            prevHeight[tick] += averageHeight + scaledMarginTop;
+          }
+        }
+
+        prevRight += blockWidth + scaledMarginEnd;
+        skippedHeight = 0;
+        skippedCount = 0;
+      }
+
+      ctx.fill();
+    }
+
+    // The blocks bounding rects isn't guaranteed to be sorted ascending by
+    // block location on the X axis. This should be the case, for better
+    // cache cohesion and a faster `buildMaskImage`.
+    this._blocksBoundingRects.sort((a, b) => a.start > b.start ? 1 : -1);
+
+    // Update the legend.
+
+    while (this._legendNode.hasChildNodes()) {
+      this._legendNode.firstChild.remove();
+    }
+    for (let { color, label } of this.format) {
+      this._createLegendItem(color, label);
+    }
+
+    return canvas;
+  },
+
+  /**
+   * Renders the graph's mask.
+   * Fades in only the parts of the graph that are inside the specified areas.
+   *
+   * @param array highlights
+   *        A list of { start, end } values. Optionally, each object
+   *        in the list may also specify { top, bottom } pixel values if the
+   *        highlighting shouldn't span across the full height of the graph.
+   * @param boolean inPixels
+   *        Set this to true if the { start, end } values in the highlights
+   *        list are pixel values, and not values from the data source.
+   * @param function unpack [optional]
+   *        @see AbstractCanvasGraph.prototype.getMappedSelection
+   */
+  buildMaskImage: function(highlights, inPixels = false, unpack = e => e.delta) {
+    // A null `highlights` array is used to clear the mask. An empty array
+    // will mask the entire graph.
+    if (!highlights) {
+      return null;
+    }
+
+    // Get a render target for the highlights. It will be overlaid on top of
+    // the existing graph, masking the areas that aren't highlighted.
+
+    let { canvas, ctx } = this._getNamedCanvas("graph-highlights");
+    let width = this._width;
+    let height = this._height;
+
+    // Draw the background mask.
+
+    let pattern = AbstractCanvasGraph.getStripePattern({
+      ownerDocument: this._document,
+      backgroundColor: GRAPH_HIGHLIGHTS_MASK_BACKGROUND,
+      stripesColor: GRAPH_HIGHLIGHTS_MASK_STRIPES
+    });
+    ctx.fillStyle = pattern;
+    ctx.fillRect(0, 0, width, height);
+
+    // Clear highlighted areas.
+
+    let totalTicks = this._data.length;
+    let firstTick = unpack(this._data[0]);
+    let lastTick = unpack(this._data[totalTicks - 1]);
+
+    for (let { start, end, top, bottom } of highlights) {
+      if (!inPixels) {
+        start = CanvasGraphUtils.map(start, firstTick, lastTick, 0, width);
+        end = CanvasGraphUtils.map(end, firstTick, lastTick, 0, width);
+      }
+      let firstSnap = findFirst(this._blocksBoundingRects, e => e.start >= start);
+      let lastSnap = findLast(this._blocksBoundingRects, e => e.start >= start && e.end <= end);
+
+      let x1 = firstSnap ? firstSnap.start : start;
+      let x2 = lastSnap ? lastSnap.end : firstSnap ? firstSnap.end : end;
+      let y1 = top || 0;
+      let y2 = bottom || height;
+      ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
+    }
+
+    return canvas;
+  },
+
+  /**
+   * A list storing the bounding rectangle for each drawn block in the graph.
+   * Created whenever `buildGraphImage` is invoked.
+   */
+  _blocksBoundingRects: null,
+
+  /**
+   * Calculates the height of the tallest bar that would eventially be rendered
+   * in this graph.
+   *
+   * Bars that are too close too each other in the graph will be combined.
+   * @see `minBarsWidth`
+   *
+   * @return number
+   *         The tallest bar height in this graph.
+   */
+  _calcMaxHeight: function({ data, dataScaleX, minBarsWidth }) {
+    let maxHeight = 0;
+    let prevRight = 0;
+    let skippedCount = 0;
+    let skippedHeight = 0;
+    let scaledMarginEnd = GRAPH_BARS_MARGIN_END * this._pixelRatio;
+
+    for (let { delta, values } of data) {
+      let barRight = (delta - this.dataOffsetX) * dataScaleX;
+      let barHeight = values.reduce((a, b) => a + b, 0);
+
+      let barWidth = barRight - prevRight;
+      if (barWidth < minBarsWidth) {
+        skippedCount++;
+        skippedHeight += barHeight;
+        continue;
+      }
+
+      let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
+      maxHeight = Math.max(averageHeight, maxHeight);
+
+      prevRight += barWidth + scaledMarginEnd;
+      skippedHeight = 0;
+      skippedCount = 0;
+    }
+
+    return maxHeight;
+  },
+
+  /**
+   * Creates the legend container when constructing this graph.
+   */
+  _createLegend: function() {
+    let legendNode = this._legendNode = this._document.createElementNS(HTML_NS, "div");
+    legendNode.className = "bar-graph-widget-legend";
+    this._container.appendChild(legendNode);
+  },
+
+  /**
+   * Creates a legend item when constructing this graph.
+   */
+  _createLegendItem: function(color, label) {
+    let itemNode = this._document.createElementNS(HTML_NS, "div");
+    itemNode.className = "bar-graph-widget-legend-item";
+
+    let colorNode = this._document.createElementNS(HTML_NS, "span");
+    colorNode.setAttribute("view", "color");
+    colorNode.setAttribute("data-index", this._legendNode.childNodes.length);
+    colorNode.style.backgroundColor = color;
+    colorNode.addEventListener("mouseover", this._onLegendMouseOver);
+    colorNode.addEventListener("mouseout", this._onLegendMouseOut);
+    colorNode.addEventListener("mousedown", this._onLegendMouseDown);
+    colorNode.addEventListener("mouseup", this._onLegendMouseUp);
+
+    let labelNode = this._document.createElementNS(HTML_NS, "span");
+    labelNode.setAttribute("view", "label");
+    labelNode.textContent = label;
+
+    itemNode.appendChild(colorNode);
+    itemNode.appendChild(labelNode);
+    this._legendNode.appendChild(itemNode);
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is hovered.
+   */
+  _onLegendMouseOver: function(e) {
+    setNamedTimeout("bar-graph-debounce", GRAPH_LEGEND_MOUSEOVER_DEBOUNCE, () => {
+      let type = e.target.dataset.index;
+      let rects = this._blocksBoundingRects.filter(e => e.type == type);
+
+      this._originalHighlights = this._mask;
+      this._hasCustomHighlights = true;
+      this.setMask(rects, true);
+
+      this.emit("legend-hover", [type, rects]);
+    });
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is unhovered.
+   */
+  _onLegendMouseOut: function() {
+    clearNamedTimeout("bar-graph-debounce");
+
+    if (this._hasCustomHighlights) {
+      this.setMask(this._originalHighlights);
+      this._hasCustomHighlights = false;
+      this._originalHighlights = null;
+    }
+
+    this.emit("legend-unhover");
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is pressed.
+   */
+  _onLegendMouseDown: function(e) {
+    e.preventDefault();
+    e.stopPropagation();
+
+    let type = e.target.dataset.index;
+    let rects = this._blocksBoundingRects.filter(e => e.type == type);
+    let leftmost = rects[0];
+    let rightmost = rects[rects.length - 1];
+    if (!leftmost || !rightmost) {
+      this.dropSelection();
+    } else {
+      this.setSelection({ start: leftmost.start, end: rightmost.end });
+    }
+
+    this.emit("legend-selection", [leftmost, rightmost]);
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is released.
+   */
+  _onLegendMouseUp: function(e) {
+    e.preventDefault();
+    e.stopPropagation();
+  }
+});
+
+/**
+ * Finds the first element in an array that validates a predicate.
+ * @param array
+ * @param function predicate
+ * @return number
+ */
+function findFirst(array, predicate) {
+  for (let i = 0, len = array.length; i < len; i++) {
+    let element = array[i];
+    if (predicate(element)) return element;
+  }
+}
+
+/**
+ * Finds the last element in an array that validates a predicate.
+ * @param array
+ * @param function predicate
+ * @return number
+ */
+function findLast(array, predicate) {
+  for (let i = array.length - 1; i >= 0; i--) {
+    let element = array[i];
+    if (predicate(element)) return element;
+  }
+}
+
+module.exports = BarGraphWidget;
--- a/browser/devtools/shared/widgets/Graphs.js
+++ b/browser/devtools/shared/widgets/Graphs.js
@@ -1,34 +1,31 @@
 /* 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 { Cc, Ci, Cu, Cr } = require("chrome");
 
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
 const { Heritage, setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 loader.lazyImporter(this, "DevToolsWorker",
   "resource://gre/modules/devtools/shared/worker.js");
 loader.lazyImporter(this, "LayoutHelpers",
   "resource://gre/modules/devtools/LayoutHelpers.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
 const WORKER_URL = "resource:///modules/devtools/GraphsWorker.js";
 
-const L10N = new ViewHelpers.L10N();
-
 // Generic constants.
 
 const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
 const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00075;
 const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.1;
 const GRAPH_WHEEL_MIN_SELECTION_WIDTH = 10; // px
 
 const GRAPH_SELECTION_BOUNDARY_HOVER_LINE_WIDTH = 4; // px
@@ -39,63 +36,16 @@ const GRAPH_MAX_SELECTION_RIGHT_PADDING 
 const GRAPH_REGION_LINE_WIDTH = 1; // px
 const GRAPH_REGION_LINE_COLOR = "rgba(237,38,85,0.8)";
 
 const GRAPH_STRIPE_PATTERN_WIDTH = 16; // px
 const GRAPH_STRIPE_PATTERN_HEIGHT = 16; // px
 const GRAPH_STRIPE_PATTERN_LINE_WIDTH = 2; // px
 const GRAPH_STRIPE_PATTERN_LINE_SPACING = 4; // px
 
-// Line graph constants.
-
-const LINE_GRAPH_DAMPEN_VALUES = 0.85;
-const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
-const LINE_GRAPH_MIN_MAX_TOOLTIP_DISTANCE = 14; // px
-
-const LINE_GRAPH_BACKGROUND_COLOR = "#0088cc";
-const LINE_GRAPH_STROKE_WIDTH = 1; // px
-const LINE_GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
-const LINE_GRAPH_HELPER_LINES_DASH = [5]; // px
-const LINE_GRAPH_HELPER_LINES_WIDTH = 1; // px
-const LINE_GRAPH_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
-const LINE_GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
-const LINE_GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
-const LINE_GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
-const LINE_GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.0)";
-
-const LINE_GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
-const LINE_GRAPH_SELECTION_LINE_COLOR = "#fff";
-const LINE_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
-const LINE_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
-const LINE_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
-const LINE_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
-
-// Bar graph constants.
-
-const BAR_GRAPH_DAMPEN_VALUES = 0.75;
-const BAR_GRAPH_BARS_MARGIN_TOP = 1; // px
-const BAR_GRAPH_BARS_MARGIN_END = 1; // px
-const BAR_GRAPH_MIN_BARS_WIDTH = 5; // px
-const BAR_GRAPH_MIN_BLOCKS_HEIGHT = 1; // px
-
-const BAR_GRAPH_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.0)";
-const BAR_GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.25)";
-
-const BAR_GRAPH_CLIPHEAD_LINE_COLOR = "#666";
-const BAR_GRAPH_SELECTION_LINE_COLOR = "#555";
-const BAR_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(0,136,204,0.25)";
-const BAR_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
-const BAR_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
-const BAR_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
-
-const BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND = "rgba(255,255,255,0.75)";
-const BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES = "rgba(255,255,255,0.5)";
-
-const BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
-
 /**
  * Small data primitives for all graphs.
  */
 this.GraphCursor = function() {
   this.x = null;
   this.y = null;
 };
 
@@ -1235,792 +1185,16 @@ AbstractCanvasGraph.prototype = {
    */
   _onResize: function() {
     if (this.hasData()) {
       setNamedTimeout(this._uid, GRAPH_RESIZE_EVENTS_DRAIN, this.refresh);
     }
   }
 };
 
-/**
- * A basic line graph, plotting values on a curve and adding helper lines
- * and tooltips for maximum, average and minimum values.
- *
- * @see AbstractCanvasGraph for emitted events and other options.
- *
- * Example usage:
- *   let graph = new LineGraphWidget(node, "units");
- *   graph.once("ready", () => {
- *     graph.setData(src);
- *   });
- *
- * Data source format:
- *   [
- *     { delta: x1, value: y1 },
- *     { delta: x2, value: y2 },
- *     ...
- *     { delta: xn, value: yn }
- *   ]
- * where each item in the array represents a point in the graph.
- *
- * @param nsIDOMNode parent
- *        The parent node holding the graph.
- * @param object options [optional]
- *        `metric`: The metric displayed in the graph, e.g. "fps" or "bananas".
- *        `min`: Boolean whether to show the min tooltip/gutter/line (default: true)
- *        `max`: Boolean whether to show the max tooltip/gutter/line (default: true)
- *        `avg`: Boolean whether to show the avg tooltip/gutter/line (default: true)
- */
-this.LineGraphWidget = function(parent, options, ...args) {
-  options = options || {};
-  let metric = options.metric;
-
-  this._showMin = options.min !== false;
-  this._showMax = options.max !== false;
-  this._showAvg = options.avg !== false;
-  AbstractCanvasGraph.apply(this, [parent, "line-graph", ...args]);
-
-  this.once("ready", () => {
-    // Create all gutters and tooltips incase the showing of min/max/avg
-    // are changed later
-    this._gutter = this._createGutter();
-
-    this._maxGutterLine = this._createGutterLine("maximum");
-    this._maxTooltip = this._createTooltip("maximum", "start", "max", metric);
-    this._minGutterLine = this._createGutterLine("minimum");
-    this._minTooltip = this._createTooltip("minimum", "start", "min", metric);
-    this._avgGutterLine = this._createGutterLine("average");
-    this._avgTooltip = this._createTooltip("average", "end", "avg", metric);
-  });
-};
-
-LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
-  backgroundColor: LINE_GRAPH_BACKGROUND_COLOR,
-  backgroundGradientStart: LINE_GRAPH_BACKGROUND_GRADIENT_START,
-  backgroundGradientEnd: LINE_GRAPH_BACKGROUND_GRADIENT_END,
-  strokeColor: LINE_GRAPH_STROKE_COLOR,
-  strokeWidth: LINE_GRAPH_STROKE_WIDTH,
-  maximumLineColor: LINE_GRAPH_MAXIMUM_LINE_COLOR,
-  averageLineColor: LINE_GRAPH_AVERAGE_LINE_COLOR,
-  minimumLineColor: LINE_GRAPH_MINIMUM_LINE_COLOR,
-  clipheadLineColor: LINE_GRAPH_CLIPHEAD_LINE_COLOR,
-  selectionLineColor: LINE_GRAPH_SELECTION_LINE_COLOR,
-  selectionBackgroundColor: LINE_GRAPH_SELECTION_BACKGROUND_COLOR,
-  selectionStripesColor: LINE_GRAPH_SELECTION_STRIPES_COLOR,
-  regionBackgroundColor: LINE_GRAPH_REGION_BACKGROUND_COLOR,
-  regionStripesColor: LINE_GRAPH_REGION_STRIPES_COLOR,
-
-  /**
-   * Optionally offsets the `delta` in the data source by this scalar.
-   */
-  dataOffsetX: 0,
-
-  /**
-   * Optionally uses this value instead of the last tick in the data source
-   * to compute the horizontal scaling.
-   */
-  dataDuration: 0,
-
-  /**
-   * The scalar used to multiply the graph values to leave some headroom.
-   */
-  dampenValuesFactor: LINE_GRAPH_DAMPEN_VALUES,
-
-  /**
-   * Specifies if min/max/avg tooltips have arrow handlers on their sides.
-   */
-  withTooltipArrows: true,
-
-  /**
-   * Specifies if min/max/avg tooltips are positioned based on the actual
-   * values, or just placed next to the graph corners.
-   */
-  withFixedTooltipPositions: false,
-
-  /**
-   * Takes a list of numbers and plots them on a line graph representing
-   * the rate of occurences in a specified interval. Useful for drawing
-   * framerate, for example, from a sequence of timestamps.
-   *
-   * @param array timestamps
-   *        A list of numbers representing time, ordered ascending. For example,
-   *        this can be the raw data received from the framerate actor, which
-   *        represents the elapsed time on each refresh driver tick.
-   * @param number interval
-   *        The maximum amount of time to wait between calculations.
-   * @param number duration
-   *        The duration of the recording in milliseconds.
-   */
-  setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
-    let {
-      plottedData,
-      plottedMinMaxSum
-    } = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
-      timestamps, interval, duration
-    });
-
-    this._tempMinMaxSum = plottedMinMaxSum;
-    this.setData(plottedData);
-  }),
-
-  /**
-   * Renders the graph's data source.
-   * @see AbstractCanvasGraph.prototype.buildGraphImage
-   */
-  buildGraphImage: function() {
-    let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
-    let width = this._width;
-    let height = this._height;
-
-    let totalTicks = this._data.length;
-    let firstTick = totalTicks ? this._data[0].delta : 0;
-    let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
-    let maxValue = Number.MIN_SAFE_INTEGER;
-    let minValue = Number.MAX_SAFE_INTEGER;
-    let avgValue = 0;
-
-    if (this._tempMinMaxSum) {
-      maxValue = this._tempMinMaxSum.maxValue;
-      minValue = this._tempMinMaxSum.minValue;
-      avgValue = this._tempMinMaxSum.avgValue;
-    } else {
-      let sumValues = 0;
-      for (let { delta, value } of this._data) {
-        maxValue = Math.max(value, maxValue);
-        minValue = Math.min(value, minValue);
-        sumValues += value;
-      }
-      avgValue = sumValues / totalTicks;
-    }
-
-    let duration = this.dataDuration || lastTick;
-    let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
-    let dataScaleY = this.dataScaleY = height / maxValue * this.dampenValuesFactor;
-
-    // Draw the background.
-
-    ctx.fillStyle = this.backgroundColor;
-    ctx.fillRect(0, 0, width, height);
-
-    // Draw the graph.
-
-    let gradient = ctx.createLinearGradient(0, height / 2, 0, height);
-    gradient.addColorStop(0, this.backgroundGradientStart);
-    gradient.addColorStop(1, this.backgroundGradientEnd);
-    ctx.fillStyle = gradient;
-    ctx.strokeStyle = this.strokeColor;
-    ctx.lineWidth = this.strokeWidth * this._pixelRatio;
-    ctx.beginPath();
-
-    for (let { delta, value } of this._data) {
-      let currX = (delta - this.dataOffsetX) * dataScaleX;
-      let currY = height - value * dataScaleY;
-
-      if (delta == firstTick) {
-        ctx.moveTo(-LINE_GRAPH_STROKE_WIDTH, height);
-        ctx.lineTo(-LINE_GRAPH_STROKE_WIDTH, currY);
-      }
-
-      ctx.lineTo(currX, currY);
-
-      if (delta == lastTick) {
-        ctx.lineTo(width + LINE_GRAPH_STROKE_WIDTH, currY);
-        ctx.lineTo(width + LINE_GRAPH_STROKE_WIDTH, height);
-      }
-    }
-
-    ctx.fill();
-    ctx.stroke();
-
-    this._drawOverlays(ctx, minValue, maxValue, avgValue, dataScaleY);
-
-    return canvas;
-  },
-
-  /**
-   * Draws the min, max and average horizontal lines, along with their
-   * repsective tooltips.
-   *
-   * @param CanvasRenderingContext2D ctx
-   * @param number minValue
-   * @param number maxValue
-   * @param number avgValue
-   * @param number dataScaleY
-   */
-  _drawOverlays: function(ctx, minValue, maxValue, avgValue, dataScaleY) {
-    let width = this._width;
-    let height = this._height;
-    let totalTicks = this._data.length;
-
-    // Draw the maximum value horizontal line.
-    if (this._showMax) {
-      ctx.strokeStyle = this.maximumLineColor;
-      ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
-      ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
-      ctx.beginPath();
-      let maximumY = height - maxValue * dataScaleY;
-      ctx.moveTo(0, maximumY);
-      ctx.lineTo(width, maximumY);
-      ctx.stroke();
-    }
-
-    // Draw the average value horizontal line.
-    if (this._showAvg) {
-      ctx.strokeStyle = this.averageLineColor;
-      ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
-      ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
-      ctx.beginPath();
-      let averageY = height - avgValue * dataScaleY;
-      ctx.moveTo(0, averageY);
-      ctx.lineTo(width, averageY);
-      ctx.stroke();
-    }
-
-    // Draw the minimum value horizontal line.
-    if (this._showMin) {
-      ctx.strokeStyle = this.minimumLineColor;
-      ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
-      ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
-      ctx.beginPath();
-      let minimumY = height - minValue * dataScaleY;
-      ctx.moveTo(0, minimumY);
-      ctx.lineTo(width, minimumY);
-      ctx.stroke();
-    }
-
-    // Update the tooltips text and gutter lines.
-
-    this._maxTooltip.querySelector("[text=value]").textContent =
-      L10N.numberWithDecimals(maxValue, 2);
-    this._avgTooltip.querySelector("[text=value]").textContent =
-      L10N.numberWithDecimals(avgValue, 2);
-    this._minTooltip.querySelector("[text=value]").textContent =
-      L10N.numberWithDecimals(minValue, 2);
-
-    let bottom = height / this._pixelRatio;
-    let maxPosY = map(maxValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
-    let avgPosY = map(avgValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
-    let minPosY = map(minValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
-
-    let safeTop = LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
-    let safeBottom = bottom - LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
-
-    let maxTooltipTop = (this.withFixedTooltipPositions
-      ? safeTop : clamp(maxPosY, safeTop, safeBottom));
-    let avgTooltipTop = (this.withFixedTooltipPositions
-      ? safeTop : clamp(avgPosY, safeTop, safeBottom));
-    let minTooltipTop = (this.withFixedTooltipPositions
-      ? safeBottom : clamp(minPosY, safeTop, safeBottom));
-
-    this._maxTooltip.style.top = maxTooltipTop + "px";
-    this._avgTooltip.style.top = avgTooltipTop + "px";
-    this._minTooltip.style.top = minTooltipTop + "px";
-
-    this._maxGutterLine.style.top = maxPosY + "px";
-    this._avgGutterLine.style.top = avgPosY + "px";
-    this._minGutterLine.style.top = minPosY + "px";
-
-    this._maxTooltip.setAttribute("with-arrows", this.withTooltipArrows);
-    this._avgTooltip.setAttribute("with-arrows", this.withTooltipArrows);
-    this._minTooltip.setAttribute("with-arrows", this.withTooltipArrows);
-
-    let distanceMinMax = Math.abs(maxTooltipTop - minTooltipTop);
-    this._maxTooltip.hidden = this._showMax === false || !totalTicks || distanceMinMax < LINE_GRAPH_MIN_MAX_TOOLTIP_DISTANCE;
-    this._avgTooltip.hidden = this._showAvg === false || !totalTicks;
-    this._minTooltip.hidden = this._showMin === false || !totalTicks;
-    this._gutter.hidden = (this._showMin === false && this._showAvg === false && this._showMax === false) || !totalTicks;
-
-    this._maxGutterLine.hidden = this._showMax === false;
-    this._avgGutterLine.hidden = this._showAvg === false;
-    this._minGutterLine.hidden = this._showMin === false;
-  },
-
-  /**
-   * Creates the gutter node when constructing this graph.
-   * @return nsIDOMNode
-   */
-  _createGutter: function() {
-    let gutter = this._document.createElementNS(HTML_NS, "div");
-    gutter.className = "line-graph-widget-gutter";
-    gutter.setAttribute("hidden", true);
-    this._container.appendChild(gutter);
-
-    return gutter;
-  },
-
-  /**
-   * Creates the gutter line nodes when constructing this graph.
-   * @return nsIDOMNode
-   */
-  _createGutterLine: function(type) {
-    let line = this._document.createElementNS(HTML_NS, "div");
-    line.className = "line-graph-widget-gutter-line";
-    line.setAttribute("type", type);
-    this._gutter.appendChild(line);
-
-    return line;
-  },
-
-  /**
-   * Creates the tooltip nodes when constructing this graph.
-   * @return nsIDOMNode
-   */
-  _createTooltip: function(type, arrow, info, metric) {
-    let tooltip = this._document.createElementNS(HTML_NS, "div");
-    tooltip.className = "line-graph-widget-tooltip";
-    tooltip.setAttribute("type", type);
-    tooltip.setAttribute("arrow", arrow);
-    tooltip.setAttribute("hidden", true);
-
-    let infoNode = this._document.createElementNS(HTML_NS, "span");
-    infoNode.textContent = info;
-    infoNode.setAttribute("text", "info");
-
-    let valueNode = this._document.createElementNS(HTML_NS, "span");
-    valueNode.textContent = 0;
-    valueNode.setAttribute("text", "value");
-
-    let metricNode = this._document.createElementNS(HTML_NS, "span");
-    metricNode.textContent = metric;
-    metricNode.setAttribute("text", "metric");
-
-    tooltip.appendChild(infoNode);
-    tooltip.appendChild(valueNode);
-    tooltip.appendChild(metricNode);
-    this._container.appendChild(tooltip);
-
-    return tooltip;
-  }
-});
-
-/**
- * A bar graph, plotting tuples of values as rectangles.
- *
- * @see AbstractCanvasGraph for emitted events and other options.
- *
- * Example usage:
- *   let graph = new BarGraphWidget(node);
- *   graph.format = ...;
- *   graph.once("ready", () => {
- *     graph.setData(src);
- *   });
- *
- * The `graph.format` traits are mandatory and will determine how the values
- * are styled as "blocks" in every "bar":
- *   [
- *     { color: "#f00", label: "Foo" },
- *     { color: "#0f0", label: "Bar" },
- *     ...
- *     { color: "#00f", label: "Baz" }
- *   ]
- *
- * Data source format:
- *   [
- *     { delta: x1, values: [y11, y12, ... y1n] },
- *     { delta: x2, values: [y21, y22, ... y2n] },
- *     ...
- *     { delta: xm, values: [ym1, ym2, ... ymn] }
- *   ]
- * where each item in the array represents a "bar", for which every value
- * represents a "block" inside that "bar", plotted at the "delta" position.
- *
- * @param nsIDOMNode parent
- *        The parent node holding the graph.
- */
-this.BarGraphWidget = function(parent, ...args) {
-  AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
-
-  // Populated with [node, event, listener] entries which need to be removed
-  // when this graph is being destroyed.
-  this.outstandingEventListeners = [];
-
-  this.once("ready", () => {
-    this._onLegendMouseOver = this._onLegendMouseOver.bind(this);
-    this._onLegendMouseOut = this._onLegendMouseOut.bind(this);
-    this._onLegendMouseDown = this._onLegendMouseDown.bind(this);
-    this._onLegendMouseUp = this._onLegendMouseUp.bind(this);
-    this._createLegend();
-  });
-
-  this.once("destroyed", () => {
-    for (let [node, event, listener] of this.outstandingEventListeners) {
-      node.removeEventListener(event, listener);
-    }
-    this.outstandingEventListeners = null;
-  });
-};
-
-BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
-  clipheadLineColor: BAR_GRAPH_CLIPHEAD_LINE_COLOR,
-  selectionLineColor: BAR_GRAPH_SELECTION_LINE_COLOR,
-  selectionBackgroundColor: BAR_GRAPH_SELECTION_BACKGROUND_COLOR,
-  selectionStripesColor: BAR_GRAPH_SELECTION_STRIPES_COLOR,
-  regionBackgroundColor: BAR_GRAPH_REGION_BACKGROUND_COLOR,
-  regionStripesColor: BAR_GRAPH_REGION_STRIPES_COLOR,
-
-  /**
-   * List of colors used to fill each block inside every bar, also
-   * corresponding to labels displayed in this graph's legend.
-   */
-  format: null,
-
-  /**
-   * Optionally offsets the `delta` in the data source by this scalar.
-   */
-  dataOffsetX: 0,
-
-  /**
-   * The scalar used to multiply the graph values to leave some headroom
-   * on the top.
-   */
-  dampenValuesFactor: BAR_GRAPH_DAMPEN_VALUES,
-
-  /**
-   * Bars that are too close too each other in the graph will be combined.
-   * This scalar specifies the required minimum width of each bar.
-   */
-  minBarsWidth: BAR_GRAPH_MIN_BARS_WIDTH,
-
-  /**
-   * Blocks in a bar that are too thin inside the bar will not be rendered.
-   * This scalar specifies the required minimum height of each block.
-   */
-  minBlocksHeight: BAR_GRAPH_MIN_BLOCKS_HEIGHT,
-
-  /**
-   * Renders the graph's background.
-   * @see AbstractCanvasGraph.prototype.buildBackgroundImage
-   */
-  buildBackgroundImage: function() {
-    let { canvas, ctx } = this._getNamedCanvas("bar-graph-background");
-    let width = this._width;
-    let height = this._height;
-
-    let gradient = ctx.createLinearGradient(0, 0, 0, height);
-    gradient.addColorStop(0, BAR_GRAPH_BACKGROUND_GRADIENT_START);
-    gradient.addColorStop(1, BAR_GRAPH_BACKGROUND_GRADIENT_END);
-    ctx.fillStyle = gradient;
-    ctx.fillRect(0, 0, width, height);
-
-    return canvas;
-  },
-
-  /**
-   * Renders the graph's data source.
-   * @see AbstractCanvasGraph.prototype.buildGraphImage
-   */
-  buildGraphImage: function() {
-    if (!this.format || !this.format.length) {
-      throw "The graph format traits are mandatory to style the data source.";
-    }
-    let { canvas, ctx } = this._getNamedCanvas("bar-graph-data");
-    let width = this._width;
-    let height = this._height;
-
-    let totalTypes = this.format.length;
-    let totalTicks = this._data.length;
-    let lastTick = this._data[totalTicks - 1].delta;
-
-    let minBarsWidth = this.minBarsWidth * this._pixelRatio;
-    let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
-
-    let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
-    let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
-      data: this._data,
-      dataScaleX: dataScaleX,
-      minBarsWidth: minBarsWidth
-    }) * this.dampenValuesFactor;
-
-    // Draw the graph.
-
-    // Iterate over the blocks, then the bars, to draw all rectangles of
-    // the same color in a single pass. See the @constructor for more
-    // information about the data source, and how a "bar" contains "blocks".
-
-    this._blocksBoundingRects = [];
-    let prevHeight = [];
-    let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
-    let unscaledMarginTop = BAR_GRAPH_BARS_MARGIN_TOP;
-
-    for (let type = 0; type < totalTypes; type++) {
-      ctx.fillStyle = this.format[type].color || "#000";
-      ctx.beginPath();
-
-      let prevRight = 0;
-      let skippedCount = 0;
-      let skippedHeight = 0;
-
-      for (let tick = 0; tick < totalTicks; tick++) {
-        let delta = this._data[tick].delta;
-        let value = this._data[tick].values[type] || 0;
-        let blockRight = (delta - this.dataOffsetX) * dataScaleX;
-        let blockHeight = value * dataScaleY;
-
-        let blockWidth = blockRight - prevRight;
-        if (blockWidth < minBarsWidth) {
-          skippedCount++;
-          skippedHeight += blockHeight;
-          continue;
-        }
-
-        let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
-        if (averageHeight >= minBlocksHeight) {
-          let bottom = height - ~~prevHeight[tick];
-          ctx.moveTo(prevRight, bottom);
-          ctx.lineTo(prevRight, bottom - averageHeight);
-          ctx.lineTo(blockRight, bottom - averageHeight);
-          ctx.lineTo(blockRight, bottom);
-
-          // Remember this block's type and location.
-          this._blocksBoundingRects.push({
-            type: type,
-            start: prevRight,
-            end: blockRight,
-            top: bottom - averageHeight,
-            bottom: bottom
-          });
-
-          if (prevHeight[tick] === undefined) {
-            prevHeight[tick] = averageHeight + unscaledMarginTop;
-          } else {
-            prevHeight[tick] += averageHeight + unscaledMarginTop;
-          }
-        }
-
-        prevRight += blockWidth + scaledMarginEnd;
-        skippedHeight = 0;
-        skippedCount = 0;
-      }
-
-      ctx.fill();
-    }
-
-    // The blocks bounding rects isn't guaranteed to be sorted ascending by
-    // block location on the X axis. This should be the case, for better
-    // cache cohesion and a faster `buildMaskImage`.
-    this._blocksBoundingRects.sort((a, b) => a.start > b.start ? 1 : -1);
-
-    // Update the legend.
-
-    while (this._legendNode.hasChildNodes()) {
-      this._legendNode.firstChild.remove();
-    }
-    for (let { color, label } of this.format) {
-      this._createLegendItem(color, label);
-    }
-
-    return canvas;
-  },
-
-  /**
-   * Renders the graph's mask.
-   * Fades in only the parts of the graph that are inside the specified areas.
-   *
-   * @param array highlights
-   *        A list of { start, end } values. Optionally, each object
-   *        in the list may also specify { top, bottom } pixel values if the
-   *        highlighting shouldn't span across the full height of the graph.
-   * @param boolean inPixels
-   *        Set this to true if the { start, end } values in the highlights
-   *        list are pixel values, and not values from the data source.
-   * @param function unpack [optional]
-   *        @see AbstractCanvasGraph.prototype.getMappedSelection
-   */
-  buildMaskImage: function(highlights, inPixels = false, unpack = e => e.delta) {
-    // A null `highlights` array is used to clear the mask. An empty array
-    // will mask the entire graph.
-    if (!highlights) {
-      return null;
-    }
-
-    // Get a render target for the highlights. It will be overlaid on top of
-    // the existing graph, masking the areas that aren't highlighted.
-
-    let { canvas, ctx } = this._getNamedCanvas("graph-highlights");
-    let width = this._width;
-    let height = this._height;
-
-    // Draw the background mask.
-
-    let pattern = AbstractCanvasGraph.getStripePattern({
-      ownerDocument: this._document,
-      backgroundColor: BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND,
-      stripesColor: BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES
-    });
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, width, height);
-
-    // Clear highlighted areas.
-
-    let totalTicks = this._data.length;
-    let firstTick = unpack(this._data[0]);
-    let lastTick = unpack(this._data[totalTicks - 1]);
-
-    for (let { start, end, top, bottom } of highlights) {
-      if (!inPixels) {
-        start = map(start, firstTick, lastTick, 0, width);
-        end = map(end, firstTick, lastTick, 0, width);
-      }
-      let firstSnap = findFirst(this._blocksBoundingRects, e => e.start >= start);
-      let lastSnap = findLast(this._blocksBoundingRects, e => e.start >= start && e.end <= end);
-
-      let x1 = firstSnap ? firstSnap.start : start;
-      let x2 = lastSnap ? lastSnap.end : firstSnap ? firstSnap.end : end;
-      let y1 = top || 0;
-      let y2 = bottom || height;
-      ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
-    }
-
-    return canvas;
-  },
-
-  /**
-   * A list storing the bounding rectangle for each drawn block in the graph.
-   * Created whenever `buildGraphImage` is invoked.
-   */
-  _blocksBoundingRects: null,
-
-  /**
-   * Calculates the height of the tallest bar that would eventially be rendered
-   * in this graph.
-   *
-   * Bars that are too close too each other in the graph will be combined.
-   * @see `minBarsWidth`
-   *
-   * @return number
-   *         The tallest bar height in this graph.
-   */
-  _calcMaxHeight: function({ data, dataScaleX, minBarsWidth }) {
-    let maxHeight = 0;
-    let prevRight = 0;
-    let skippedCount = 0;
-    let skippedHeight = 0;
-    let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
-
-    for (let { delta, values } of data) {
-      let barRight = (delta - this.dataOffsetX) * dataScaleX;
-      let barHeight = values.reduce((a, b) => a + b, 0);
-
-      let barWidth = barRight - prevRight;
-      if (barWidth < minBarsWidth) {
-        skippedCount++;
-        skippedHeight += barHeight;
-        continue;
-      }
-
-      let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
-      maxHeight = Math.max(averageHeight, maxHeight);
-
-      prevRight += barWidth + scaledMarginEnd;
-      skippedHeight = 0;
-      skippedCount = 0;
-    }
-
-    return maxHeight;
-  },
-
-  /**
-   * Creates the legend container when constructing this graph.
-   */
-  _createLegend: function() {
-    let legendNode = this._legendNode = this._document.createElementNS(HTML_NS, "div");
-    legendNode.className = "bar-graph-widget-legend";
-    this._container.appendChild(legendNode);
-  },
-
-  /**
-   * Creates a legend item when constructing this graph.
-   */
-  _createLegendItem: function(color, label) {
-    let itemNode = this._document.createElementNS(HTML_NS, "div");
-    itemNode.className = "bar-graph-widget-legend-item";
-
-    let colorNode = this._document.createElementNS(HTML_NS, "span");
-    colorNode.setAttribute("view", "color");
-    colorNode.setAttribute("data-index", this._legendNode.childNodes.length);
-    colorNode.style.backgroundColor = color;
-    colorNode.addEventListener("mouseover", this._onLegendMouseOver);
-    colorNode.addEventListener("mouseout", this._onLegendMouseOut);
-    colorNode.addEventListener("mousedown", this._onLegendMouseDown);
-    colorNode.addEventListener("mouseup", this._onLegendMouseUp);
-
-    this.outstandingEventListeners.push([colorNode, "mouseover", this._onLegendMouseOver]);
-    this.outstandingEventListeners.push([colorNode, "mouseout", this._onLegendMouseOut]);
-    this.outstandingEventListeners.push([colorNode, "mousedown", this._onLegendMouseDown]);
-    this.outstandingEventListeners.push([colorNode, "mouseup", this._onLegendMouseUp]);
-
-    let labelNode = this._document.createElementNS(HTML_NS, "span");
-    labelNode.setAttribute("view", "label");
-    labelNode.textContent = label;
-
-    itemNode.appendChild(colorNode);
-    itemNode.appendChild(labelNode);
-    this._legendNode.appendChild(itemNode);
-  },
-
-  /**
-   * Invoked whenever a color node in the legend is hovered.
-   */
-  _onLegendMouseOver: function(e) {
-    setNamedTimeout("bar-graph-debounce", BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE, () => {
-      let type = e.target.dataset.index;
-      let rects = this._blocksBoundingRects.filter(e => e.type == type);
-
-      this._originalHighlights = this._mask;
-      this._hasCustomHighlights = true;
-      this.setMask(rects, true);
-
-      this.emit("legend-hover", [type, rects]);
-    });
-  },
-
-  /**
-   * Invoked whenever a color node in the legend is unhovered.
-   */
-  _onLegendMouseOut: function() {
-    clearNamedTimeout("bar-graph-debounce");
-
-    if (this._hasCustomHighlights) {
-      this.setMask(this._originalHighlights);
-      this._hasCustomHighlights = false;
-      this._originalHighlights = null;
-    }
-
-    this.emit("legend-unhover");
-  },
-
-  /**
-   * Invoked whenever a color node in the legend is pressed.
-   */
-  _onLegendMouseDown: function(e) {
-    e.preventDefault();
-    e.stopPropagation();
-
-    let type = e.target.dataset.index;
-    let rects = this._blocksBoundingRects.filter(e => e.type == type);
-    let leftmost = rects[0];
-    let rightmost = rects[rects.length - 1];
-    if (!leftmost || !rightmost) {
-      this.dropSelection();
-    } else {
-      this.setSelection({ start: leftmost.start, end: rightmost.end });
-    }
-
-    this.emit("legend-selection", [leftmost, rightmost]);
-  },
-
-  /**
-   * Invoked whenever a color node in the legend is released.
-   */
-  _onLegendMouseUp: function(e) {
-    e.preventDefault();
-    e.stopPropagation();
-  }
-});
-
 // Helper functions.
 
 /**
  * Creates an iframe element with the provided source URL, appends it to
  * the specified node and invokes the callback once the content is loaded.
  *
  * @param string url
  *        The desired source URL for the iframe.
@@ -2194,51 +1368,16 @@ function map(value, istart, istop, ostar
  * @return number
  */
 function clamp(value, min, max) {
   if (value < min) return min;
   if (value > max) return max;
   return value;
 }
 
-/**
- * Calculates the squared distance between two 2D points.
- */
-function distSquared(x0, y0, x1, y1) {
-  let xs = x1 - x0;
-  let ys = y1 - y0;
-  return xs * xs + ys * ys;
-}
-
-/**
- * Finds the first element in an array that validates a predicate.
- * @param array
- * @param function predicate
- * @return number
- */
-function findFirst(array, predicate) {
-  for (let i = 0, len = array.length; i < len; i++) {
-    let element = array[i];
-    if (predicate(element)) return element;
-  }
-}
-
 exports.GraphCursor = GraphCursor;
 exports.GraphArea = GraphArea;
 exports.GraphAreaDragger = GraphAreaDragger;
 exports.GraphAreaResizer = GraphAreaResizer;
 exports.AbstractCanvasGraph = AbstractCanvasGraph;
-exports.LineGraphWidget = LineGraphWidget;
-exports.BarGraphWidget = BarGraphWidget;
 exports.CanvasGraphUtils = CanvasGraphUtils;
-
-/**
- * Finds the last element in an array that validates a predicate.
- * @param array
- * @param function predicate
- * @return number
- */
-function findLast(array, predicate) {
-  for (let i = array.length - 1; i >= 0; i--) {
-    let element = array[i];
-    if (predicate(element)) return element;
-  }
-}
+exports.CanvasGraphUtils.map = map;
+exports.CanvasGraphUtils.clamp = clamp;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/LineGraphWidget.js
@@ -0,0 +1,386 @@
+"use strict";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+const { ViewHelpers, Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
+const { AbstractCanvasGraph, CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const L10N = new ViewHelpers.L10N();
+
+// Line graph constants.
+
+const GRAPH_DAMPEN_VALUES_FACTOR = 0.85;
+const GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
+const GRAPH_MIN_MAX_TOOLTIP_DISTANCE = 14; // px
+
+const GRAPH_BACKGROUND_COLOR = "#0088cc";
+const GRAPH_STROKE_WIDTH = 1; // px
+const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
+const GRAPH_HELPER_LINES_DASH = [5]; // px
+const GRAPH_HELPER_LINES_WIDTH = 1; // px
+const GRAPH_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
+const GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
+const GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
+const GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
+const GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.0)";
+
+const GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
+const GRAPH_SELECTION_LINE_COLOR = "#fff";
+const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
+const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
+const GRAPH_REGION_BACKGROUND_COLOR = "transparent";
+const GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
+
+/**
+ * A basic line graph, plotting values on a curve and adding helper lines
+ * and tooltips for maximum, average and minimum values.
+ *
+ * @see AbstractCanvasGraph for emitted events and other options.
+ *
+ * Example usage:
+ *   let graph = new LineGraphWidget(node, "units");
+ *   graph.once("ready", () => {
+ *     graph.setData(src);
+ *   });
+ *
+ * Data source format:
+ *   [
+ *     { delta: x1, value: y1 },
+ *     { delta: x2, value: y2 },
+ *     ...
+ *     { delta: xn, value: yn }
+ *   ]
+ * where each item in the array represents a point in the graph.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the graph.
+ * @param object options [optional]
+ *        `metric`: The metric displayed in the graph, e.g. "fps" or "bananas".
+ *        `min`: Boolean whether to show the min tooltip/gutter/line (default: true)
+ *        `max`: Boolean whether to show the max tooltip/gutter/line (default: true)
+ *        `avg`: Boolean whether to show the avg tooltip/gutter/line (default: true)
+ */
+this.LineGraphWidget = function(parent, options = {}, ...args) {
+  let { metric, min, max, avg } = options;
+
+  this._showMin = min !== false;
+  this._showMax = max !== false;
+  this._showAvg = avg !== false;
+
+  AbstractCanvasGraph.apply(this, [parent, "line-graph", ...args]);
+
+  this.once("ready", () => {
+    // Create all gutters and tooltips incase the showing of min/max/avg
+    // are changed later
+    this._gutter = this._createGutter();
+    this._maxGutterLine = this._createGutterLine("maximum");
+    this._maxTooltip = this._createTooltip("maximum", "start", "max", metric);
+    this._minGutterLine = this._createGutterLine("minimum");
+    this._minTooltip = this._createTooltip("minimum", "start", "min", metric);
+    this._avgGutterLine = this._createGutterLine("average");
+    this._avgTooltip = this._createTooltip("average", "end", "avg", metric);
+  });
+};
+
+LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
+  backgroundColor: GRAPH_BACKGROUND_COLOR,
+  backgroundGradientStart: GRAPH_BACKGROUND_GRADIENT_START,
+  backgroundGradientEnd: GRAPH_BACKGROUND_GRADIENT_END,
+  strokeColor: GRAPH_STROKE_COLOR,
+  strokeWidth: GRAPH_STROKE_WIDTH,
+  maximumLineColor: GRAPH_MAXIMUM_LINE_COLOR,
+  averageLineColor: GRAPH_AVERAGE_LINE_COLOR,
+  minimumLineColor: GRAPH_MINIMUM_LINE_COLOR,
+  clipheadLineColor: GRAPH_CLIPHEAD_LINE_COLOR,
+  selectionLineColor: GRAPH_SELECTION_LINE_COLOR,
+  selectionBackgroundColor: GRAPH_SELECTION_BACKGROUND_COLOR,
+  selectionStripesColor: GRAPH_SELECTION_STRIPES_COLOR,
+  regionBackgroundColor: GRAPH_REGION_BACKGROUND_COLOR,
+  regionStripesColor: GRAPH_REGION_STRIPES_COLOR,
+
+  /**
+   * Optionally offsets the `delta` in the data source by this scalar.
+   */
+  dataOffsetX: 0,
+
+  /**
+   * Optionally uses this value instead of the last tick in the data source
+   * to compute the horizontal scaling.
+   */
+  dataDuration: 0,
+
+  /**
+   * The scalar used to multiply the graph values to leave some headroom.
+   */
+  dampenValuesFactor: GRAPH_DAMPEN_VALUES_FACTOR,
+
+  /**
+   * Specifies if min/max/avg tooltips have arrow handlers on their sides.
+   */
+  withTooltipArrows: true,
+
+  /**
+   * Specifies if min/max/avg tooltips are positioned based on the actual
+   * values, or just placed next to the graph corners.
+   */
+  withFixedTooltipPositions: false,
+
+  /**
+   * Takes a list of numbers and plots them on a line graph representing
+   * the rate of occurences in a specified interval. Useful for drawing
+   * framerate, for example, from a sequence of timestamps.
+   *
+   * @param array timestamps
+   *        A list of numbers representing time, ordered ascending. For example,
+   *        this can be the raw data received from the framerate actor, which
+   *        represents the elapsed time on each refresh driver tick.
+   * @param number interval
+   *        The maximum amount of time to wait between calculations.
+   * @param number duration
+   *        The duration of the recording in milliseconds.
+   */
+  setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
+    let {
+      plottedData,
+      plottedMinMaxSum
+    } = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
+      timestamps, interval, duration
+    });
+
+    this._tempMinMaxSum = plottedMinMaxSum;
+    this.setData(plottedData);
+  }),
+
+  /**
+   * Renders the graph's data source.
+   * @see AbstractCanvasGraph.prototype.buildGraphImage
+   */
+  buildGraphImage: function() {
+    let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
+    let width = this._width;
+    let height = this._height;
+
+    let totalTicks = this._data.length;
+    let firstTick = totalTicks ? this._data[0].delta : 0;
+    let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
+    let maxValue = Number.MIN_SAFE_INTEGER;
+    let minValue = Number.MAX_SAFE_INTEGER;
+    let avgValue = 0;
+
+    if (this._tempMinMaxSum) {
+      maxValue = this._tempMinMaxSum.maxValue;
+      minValue = this._tempMinMaxSum.minValue;
+      avgValue = this._tempMinMaxSum.avgValue;
+    } else {
+      let sumValues = 0;
+      for (let { delta, value } of this._data) {
+        maxValue = Math.max(value, maxValue);
+        minValue = Math.min(value, minValue);
+        sumValues += value;
+      }
+      avgValue = sumValues / totalTicks;
+    }
+
+    let duration = this.dataDuration || lastTick;
+    let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
+    let dataScaleY = this.dataScaleY = height / maxValue * this.dampenValuesFactor;
+
+    // Draw the background.
+
+    ctx.fillStyle = this.backgroundColor;
+    ctx.fillRect(0, 0, width, height);
+
+    // Draw the graph.
+
+    let gradient = ctx.createLinearGradient(0, height / 2, 0, height);
+    gradient.addColorStop(0, this.backgroundGradientStart);
+    gradient.addColorStop(1, this.backgroundGradientEnd);
+    ctx.fillStyle = gradient;
+    ctx.strokeStyle = this.strokeColor;
+    ctx.lineWidth = this.strokeWidth * this._pixelRatio;
+    ctx.beginPath();
+
+    for (let { delta, value } of this._data) {
+      let currX = (delta - this.dataOffsetX) * dataScaleX;
+      let currY = height - value * dataScaleY;
+
+      if (delta == firstTick) {
+        ctx.moveTo(-GRAPH_STROKE_WIDTH, height);
+        ctx.lineTo(-GRAPH_STROKE_WIDTH, currY);
+      }
+
+      ctx.lineTo(currX, currY);
+
+      if (delta == lastTick) {
+        ctx.lineTo(width + GRAPH_STROKE_WIDTH, currY);
+        ctx.lineTo(width + GRAPH_STROKE_WIDTH, height);
+      }
+    }
+
+    ctx.fill();
+    ctx.stroke();
+
+    this._drawOverlays(ctx, minValue, maxValue, avgValue, dataScaleY);
+
+    return canvas;
+  },
+
+  /**
+   * Draws the min, max and average horizontal lines, along with their
+   * repsective tooltips.
+   *
+   * @param CanvasRenderingContext2D ctx
+   * @param number minValue
+   * @param number maxValue
+   * @param number avgValue
+   * @param number dataScaleY
+   */
+  _drawOverlays: function(ctx, minValue, maxValue, avgValue, dataScaleY) {
+    let width = this._width;
+    let height = this._height;
+    let totalTicks = this._data.length;
+
+    // Draw the maximum value horizontal line.
+    if (this._showMax) {
+      ctx.strokeStyle = this.maximumLineColor;
+      ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
+      ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
+      ctx.beginPath();
+      let maximumY = height - maxValue * dataScaleY;
+      ctx.moveTo(0, maximumY);
+      ctx.lineTo(width, maximumY);
+      ctx.stroke();
+    }
+
+    // Draw the average value horizontal line.
+    if (this._showAvg) {
+      ctx.strokeStyle = this.averageLineColor;
+      ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
+      ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
+      ctx.beginPath();
+      let averageY = height - avgValue * dataScaleY;
+      ctx.moveTo(0, averageY);
+      ctx.lineTo(width, averageY);
+      ctx.stroke();
+    }
+
+    // Draw the minimum value horizontal line.
+    if (this._showMin) {
+      ctx.strokeStyle = this.minimumLineColor;
+      ctx.lineWidth = GRAPH_HELPER_LINES_WIDTH;
+      ctx.setLineDash(GRAPH_HELPER_LINES_DASH);
+      ctx.beginPath();
+      let minimumY = height - minValue * dataScaleY;
+      ctx.moveTo(0, minimumY);
+      ctx.lineTo(width, minimumY);
+      ctx.stroke();
+    }
+
+    // Update the tooltips text and gutter lines.
+
+    this._maxTooltip.querySelector("[text=value]").textContent =
+      L10N.numberWithDecimals(maxValue, 2);
+    this._avgTooltip.querySelector("[text=value]").textContent =
+      L10N.numberWithDecimals(avgValue, 2);
+    this._minTooltip.querySelector("[text=value]").textContent =
+      L10N.numberWithDecimals(minValue, 2);
+
+    let bottom = height / this._pixelRatio;
+    let maxPosY = CanvasGraphUtils.map(maxValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
+    let avgPosY = CanvasGraphUtils.map(avgValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
+    let minPosY = CanvasGraphUtils.map(minValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
+
+    let safeTop = GRAPH_TOOLTIP_SAFE_BOUNDS;
+    let safeBottom = bottom - GRAPH_TOOLTIP_SAFE_BOUNDS;
+
+    let maxTooltipTop = (this.withFixedTooltipPositions
+      ? safeTop : CanvasGraphUtils.clamp(maxPosY, safeTop, safeBottom));
+    let avgTooltipTop = (this.withFixedTooltipPositions
+      ? safeTop : CanvasGraphUtils.clamp(avgPosY, safeTop, safeBottom));
+    let minTooltipTop = (this.withFixedTooltipPositions
+      ? safeBottom : CanvasGraphUtils.clamp(minPosY, safeTop, safeBottom));
+
+    this._maxTooltip.style.top = maxTooltipTop + "px";
+    this._avgTooltip.style.top = avgTooltipTop + "px";
+    this._minTooltip.style.top = minTooltipTop + "px";
+
+    this._maxGutterLine.style.top = maxPosY + "px";
+    this._avgGutterLine.style.top = avgPosY + "px";
+    this._minGutterLine.style.top = minPosY + "px";
+
+    this._maxTooltip.setAttribute("with-arrows", this.withTooltipArrows);
+    this._avgTooltip.setAttribute("with-arrows", this.withTooltipArrows);
+    this._minTooltip.setAttribute("with-arrows", this.withTooltipArrows);
+
+    let distanceMinMax = Math.abs(maxTooltipTop - minTooltipTop);
+    this._maxTooltip.hidden = this._showMax === false || !totalTicks || distanceMinMax < GRAPH_MIN_MAX_TOOLTIP_DISTANCE;
+    this._avgTooltip.hidden = this._showAvg === false || !totalTicks;
+    this._minTooltip.hidden = this._showMin === false || !totalTicks;
+    this._gutter.hidden = (this._showMin === false && this._showAvg === false && this._showMax === false) || !totalTicks;
+
+    this._maxGutterLine.hidden = this._showMax === false;
+    this._avgGutterLine.hidden = this._showAvg === false;
+    this._minGutterLine.hidden = this._showMin === false;
+  },
+
+  /**
+   * Creates the gutter node when constructing this graph.
+   * @return nsIDOMNode
+   */
+  _createGutter: function() {
+    let gutter = this._document.createElementNS(HTML_NS, "div");
+    gutter.className = "line-graph-widget-gutter";
+    gutter.setAttribute("hidden", true);
+    this._container.appendChild(gutter);
+
+    return gutter;
+  },
+
+  /**
+   * Creates the gutter line nodes when constructing this graph.
+   * @return nsIDOMNode
+   */
+  _createGutterLine: function(type) {
+    let line = this._document.createElementNS(HTML_NS, "div");
+    line.className = "line-graph-widget-gutter-line";
+    line.setAttribute("type", type);
+    this._gutter.appendChild(line);
+
+    return line;
+  },
+
+  /**
+   * Creates the tooltip nodes when constructing this graph.
+   * @return nsIDOMNode
+   */
+  _createTooltip: function(type, arrow, info, metric) {
+    let tooltip = this._document.createElementNS(HTML_NS, "div");
+    tooltip.className = "line-graph-widget-tooltip";
+    tooltip.setAttribute("type", type);
+    tooltip.setAttribute("arrow", arrow);
+    tooltip.setAttribute("hidden", true);
+
+    let infoNode = this._document.createElementNS(HTML_NS, "span");
+    infoNode.textContent = info;
+    infoNode.setAttribute("text", "info");
+
+    let valueNode = this._document.createElementNS(HTML_NS, "span");
+    valueNode.textContent = 0;
+    valueNode.setAttribute("text", "value");
+
+    let metricNode = this._document.createElementNS(HTML_NS, "span");
+    metricNode.textContent = metric;
+    metricNode.setAttribute("text", "metric");
+
+    tooltip.appendChild(infoNode);
+    tooltip.appendChild(valueNode);
+    tooltip.appendChild(metricNode);
+    this._container.appendChild(tooltip);
+
+    return tooltip;
+  }
+});
+
+module.exports = LineGraphWidget;
--- a/browser/devtools/webaudioeditor/includes.js
+++ b/browser/devtools/webaudioeditor/includes.js
@@ -20,17 +20,17 @@ const { Task } = Cu.import("resource://g
 const { Class } = require("sdk/core/heritage");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const Telemetry = require("devtools/shared/telemetry");
 const telemetry = new Telemetry();
 
 devtools.lazyRequireGetter(this, "LineGraphWidget",
-  "devtools/shared/widgets/Graphs", true);
+  "devtools/shared/widgets/LineGraphWidget");
 
 // `AUDIO_NODE_DEFINITION` defined in the controller's initialization,
 // which describes all the properties of an AudioNode
 let AUDIO_NODE_DEFINITION;
 
 // Override DOM promises with Promise.jsm helpers
 const { defer, all } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 
--- a/browser/themes/shared/devtools/widgets.inc.css
+++ b/browser/themes/shared/devtools/widgets.inc.css
@@ -1020,20 +1020,16 @@
 }
 
 .line-graph-widget-tooltip[type=average] > [text=value] {
   color: var(--theme-highlight-orange);
 }
 
 /* Bar graph widget */
 
-.bar-graph-widget-canvas {
-  background: #f7f7f7;
-}
-
 .bar-graph-widget-legend {
   position: absolute;
   top: 4px;
   left: 8px;
   color: #292e33;
   font-size: 80%;
   pointer-events: none;
 }