Backed out 3 changesets (bug 1175686) for browser_graphs-11b.js failures.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 18 Jun 2015 14:38:57 -0400
changeset 267651 288cd0b9c9a3cad41633ea159d7107febf0a46e1
parent 267650 08fe9014f0020340d3ab1aa25fb802167f4020fb
child 267652 d56a1257088ed44e2804f7fe7c615341954bd7de
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1175686
milestone41.0a1
backs out21126200b02fcd52f2e247110e46e0a45afd5aa7
fc161a706ebf37144dc63e61cf6c07c088403bbe
8c6b76badad2ea7ea396546bb012a782482e11bc
Backed out 3 changesets (bug 1175686) for browser_graphs-11b.js failures. Backed out changeset 21126200b02f (bug 1175686) Backed out changeset fc161a706ebf (bug 1175686) Backed out changeset 8c6b76badad2 (bug 1175686) CLOSED TREE
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/LineGraphWidget");
-const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget");
+const { LineGraphWidget } = require("devtools/shared/widgets/Graphs");
+const { BarGraphWidget } = require("devtools/shared/widgets/Graphs");
 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,22 +43,20 @@ 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/LineGraphWidget");
+let {LineGraphWidget} = 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();
   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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
 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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/BarGraphWidget");
+let {BarGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
 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/BarGraphWidget");
+let {BarGraphWidget} = devtools.require("devtools/shared/widgets/Graphs");
 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,16 +1,14 @@
 /* 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 = devtools.require("devtools/shared/widgets/LineGraphWidget");
-let BarGraphWidget = devtools.require("devtools/shared/widgets/BarGraphWidget");
-let {CanvasGraphUtils} = devtools.require("devtools/shared/widgets/Graphs");
+let {LineGraphWidget,BarGraphWidget,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/LineGraphWidget");
+let {LineGraphWidget} = 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-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/LineGraphWidget");
+let {LineGraphWidget} = 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-15.js
+++ b/browser/devtools/shared/test/browser_graphs-15.js
@@ -1,31 +1,30 @@
 /* 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/LineGraphWidget");
+let {LineGraphWidget} = 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();
 });
 
deleted file mode 100644
--- a/browser/devtools/shared/widgets/BarGraphWidget.js
+++ /dev/null
@@ -1,463 +0,0 @@
-"use strict";
-
-const { Cc, Ci, Cu, Cr } = require("chrome");
-
-const { Heritage } = 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;
-  }
-}
-
-module.exports = BarGraphWidget;
--- a/browser/devtools/shared/widgets/Graphs.js
+++ b/browser/devtools/shared/widgets/Graphs.js
@@ -1,31 +1,34 @@
 /* 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
@@ -36,16 +39,63 @@ 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;
 };
 
@@ -1185,16 +1235,792 @@ 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.
@@ -1368,16 +2194,51 @@ 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;
-exports.CanvasGraphUtils.map = map;
-exports.CanvasGraphUtils.clamp = clamp;
+
+/**
+ * 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;
+  }
+}
deleted file mode 100644
--- a/browser/devtools/shared/widgets/LineGraphWidget.js
+++ /dev/null
@@ -1,386 +0,0 @@
-"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/LineGraphWidget");
+  "devtools/shared/widgets/Graphs", true);
 
 // `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,16 +1020,20 @@
 }
 
 .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;
 }