Bug 1215955 - Add clear snapshots button to devtools memory tool. r=fitzgen
authorJulian Descottes <jdescottes@mozilla.com>
Tue, 12 Jan 2016 19:14:15 +0100
changeset 280138 c457397a08bba7ca0dda015307e8c6e020c10985
parent 280137 94131e21eb935ad153e1d873996ffb778039635f
child 280139 3cea1d886e9e6861a8720de42825185381a208a3
push id70334
push usercbook@mozilla.com
push dateFri, 15 Jan 2016 10:48:53 +0000
treeherdermozilla-inbound@de9a0fc7828d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen
bugs1215955
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1215955 - Add clear snapshots button to devtools memory tool. r=fitzgen
devtools/client/locales/en-US/memory.properties
devtools/client/memory/actions/snapshot.js
devtools/client/memory/app.js
devtools/client/memory/components/toolbar.js
devtools/client/memory/constants.js
devtools/client/memory/reducers/snapshots.js
devtools/client/memory/test/browser/browser.ini
devtools/client/memory/test/browser/browser_memory-clear-snapshots.js
devtools/client/memory/test/browser/head.js
devtools/client/memory/test/unit/test_action-clear-snapshots_01.js
devtools/client/memory/test/unit/test_action-clear-snapshots_02.js
devtools/client/memory/test/unit/test_action-clear-snapshots_03.js
devtools/client/memory/test/unit/test_action-clear-snapshots_04.js
devtools/client/memory/test/unit/test_action-clear-snapshots_05.js
devtools/client/memory/test/unit/xpcshell.ini
devtools/client/themes/memory.css
--- a/devtools/client/locales/en-US/memory.properties
+++ b/devtools/client/locales/en-US/memory.properties
@@ -79,16 +79,20 @@ toolbar.view.dominators=Dominators
 # LOCALIZATION NOTE (take-snapshot): The label describing the button that
 # initiates taking a snapshot, either as the main label, or a tooltip.
 take-snapshot=Take snapshot
 
 # LOCALIZATION NOTE (import-snapshot): The label describing the button that
 # initiates importing a snapshot.
 import-snapshot=Import…
 
+# LOCALIZATION NOTE (clear-snapshots): The label describing the button that clears
+# existing snapshot.
+clear-snapshots=Clear
+
 # LOCALIZATION NOTE (diff-snapshots): The label for the button that initiates
 # selecting two snapshots to diff with each other.
 diff-snapshots=+/-
 
 # LOCALIZATION NOTE (diff-snapshots.tooltip): The tooltip for the button that
 # initiates selecting two snapshots to diff with each other.
 diff-snapshots.tooltip=Compare snapshots
 
--- a/devtools/client/memory/actions/snapshot.js
+++ b/devtools/client/memory/actions/snapshot.js
@@ -402,16 +402,42 @@ const refreshSelectedDominatorTree = exp
 const selectSnapshot = exports.selectSnapshot = function (id) {
   return {
     type: actions.SELECT_SNAPSHOT,
     id
   };
 };
 
 /**
+ * Delete all snapshots that are in the SAVED_CENSUS or ERROR state
+ *
+ * @param {HeapAnalysesClient} heapWorker
+ */
+const clearSnapshots = exports.clearSnapshots = function (heapWorker) {
+  return function*(dispatch, getState) {
+    let snapshots = getState().snapshots.filter(
+      s => s.state === states.SAVED_CENSUS || s.state === states.ERROR);
+
+    let ids = snapshots.map(s => s.id);
+
+    dispatch({ type: actions.DELETE_SNAPSHOTS_START, ids });
+
+    Promise.all(snapshots.map(s => {
+      heapWorker.deleteHeapSnapshot(s.path)
+      .catch(error => {
+        reportException("clearSnapshots", error);
+        dispatch({ type: actions.SNAPSHOT_ERROR, id: s.id, error });
+      });
+    }));
+
+    dispatch({ type: actions.DELETE_SNAPSHOTS_END, ids });
+  };
+};
+
+/**
  * Expand the given node in the snapshot's census report.
  *
  * @param {CensusTreeNode} node
  */
 const expandCensusNode = exports.expandCensusNode = function (id, node) {
   return {
     type: actions.EXPAND_CENSUS_NODE,
     id,
--- a/devtools/client/memory/app.js
+++ b/devtools/client/memory/app.js
@@ -17,16 +17,17 @@ const {
   focusDiffingCensusNode,
 } = require("./actions/diffing");
 const { toggleInvertedAndRefresh } = require("./actions/inverted");
 const { setFilterStringAndRefresh } = require("./actions/filter");
 const { pickFileAndExportSnapshot, pickFileAndImportSnapshotAndCensus } = require("./actions/io");
 const {
   selectSnapshotAndRefresh,
   takeSnapshotAndCensus,
+  clearSnapshots,
   fetchImmediatelyDominated,
   expandCensusNode,
   collapseCensusNode,
   focusCensusNode,
   expandDominatorTreeNode,
   collapseDominatorTreeNode,
   focusDominatorTreeNode,
 } = require("./actions/snapshot");
@@ -93,16 +94,17 @@ const MemoryApp = createClass({
         {
           id: "memory-tool"
         },
 
         Toolbar({
           snapshots,
           breakdowns: getBreakdownDisplayData(),
           onImportClick: () => dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)),
+          onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)),
           onTakeSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)),
           onBreakdownChange: breakdown =>
             dispatch(setBreakdownAndRefresh(heapWorker, breakdownNameToSpec(breakdown))),
           onToggleRecordAllocationStacks: () =>
             dispatch(toggleRecordingAllocationStacks(front)),
           allocations,
           inverted,
           onToggleInverted: () =>
--- a/devtools/client/memory/components/toolbar.js
+++ b/devtools/client/memory/components/toolbar.js
@@ -12,16 +12,17 @@ const Toolbar = module.exports = createC
   displayName: "Toolbar",
   propTypes: {
     breakdowns: PropTypes.arrayOf(PropTypes.shape({
       name: PropTypes.string.isRequired,
       displayName: PropTypes.string.isRequired,
     })).isRequired,
     onTakeSnapshotClick: PropTypes.func.isRequired,
     onImportClick: PropTypes.func.isRequired,
+    onClearSnapshotsClick: PropTypes.func.isRequired,
     onBreakdownChange: PropTypes.func.isRequired,
     onToggleRecordAllocationStacks: PropTypes.func.isRequired,
     allocations: models.allocations,
     onToggleInverted: PropTypes.func.isRequired,
     inverted: PropTypes.bool.isRequired,
     filterString: PropTypes.string,
     setFilterString: PropTypes.func.isRequired,
     diffing: models.diffingModel,
@@ -35,16 +36,17 @@ const Toolbar = module.exports = createC
     onDominatorTreeBreakdownChange: PropTypes.func.isRequired,
     snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
   },
 
   render() {
     let {
       onTakeSnapshotClick,
       onImportClick,
+      onClearSnapshotsClick,
       onBreakdownChange,
       breakdowns,
       dominatorTreeBreakdowns,
       onDominatorTreeBreakdownChange,
       onToggleRecordAllocationStacks,
       allocations,
       onToggleInverted,
       inverted,
@@ -185,17 +187,25 @@ const Toolbar = module.exports = createC
             {
               id: "import-snapshot",
               className: "devtools-toolbarbutton import-snapshot devtools-button",
               onClick: onImportClick,
               title: L10N.getStr("import-snapshot"),
               "data-text-only": true,
             },
             L10N.getStr("import-snapshot")
-          )
+          ),
+
+          dom.button({
+            id: "clear-snapshots",
+            className: "devtools-toolbarbutton clear-snapshots devtools-button",
+            onClick: onClearSnapshotsClick,
+            title: L10N.getStr("clear-snapshots"),
+            "data-text-only": true,
+          }, L10N.getStr("clear-snapshots"))
         ),
 
         dom.label(
           {},
           dom.input({
             id: "record-allocation-stacks-checkbox",
             type: "checkbox",
             checked: allocations.recording,
--- a/devtools/client/memory/constants.js
+++ b/devtools/client/memory/constants.js
@@ -45,16 +45,20 @@ actions.EXPORT_SNAPSHOT_ERROR = "export-
 // and represents the entire state until the census is available.
 actions.IMPORT_SNAPSHOT_START = "import-snapshot-start";
 actions.IMPORT_SNAPSHOT_END = "import-snapshot-end";
 actions.IMPORT_SNAPSHOT_ERROR = "import-snapshot-error";
 
 // Fired by UI to select a snapshot to view.
 actions.SELECT_SNAPSHOT = "select-snapshot";
 
+// Fired to delete a provided list of snapshots
+actions.DELETE_SNAPSHOTS_START = "delete-snapshots-start";
+actions.DELETE_SNAPSHOTS_END = "delete-snapshots-end";
+
 // Fired to toggle tree inversion on or off.
 actions.TOGGLE_INVERTED = "toggle-inverted";
 
 // Fired when a snapshot is selected for diffing.
 actions.SELECT_SNAPSHOT_FOR_DIFFING = "select-snapshot-for-diffing";
 
 // Fired when taking a census diff.
 actions.TAKE_CENSUS_DIFF_START = "take-census-diff-start";
--- a/devtools/client/memory/reducers/snapshots.js
+++ b/devtools/client/memory/reducers/snapshots.js
@@ -133,16 +133,24 @@ handlers[actions.FOCUS_CENSUS_NODE] = fu
     return immutableUpdate(snapshot, { census });
   });
 };
 
 handlers[actions.SELECT_SNAPSHOT] = function (snapshots, { id }) {
   return snapshots.map(s => immutableUpdate(s, { selected: s.id === id }));
 };
 
+handlers[actions.DELETE_SNAPSHOTS_START] = function (snapshots, { ids }) {
+  return snapshots.filter(s => ids.indexOf(s.id) === -1);
+};
+
+handlers[actions.DELETE_SNAPSHOTS_END] = function (snapshots) {
+  return snapshots;
+};
+
 handlers[actions.CHANGE_VIEW] = function (snapshots, { view }) {
   return view === viewState.DIFFING
     ? snapshots.map(s => immutableUpdate(s, { selected: false }))
     : snapshots;
 };
 
 handlers[actions.COMPUTE_DOMINATOR_TREE_START] = function (snapshots, { id }) {
   const dominatorTree = Object.freeze({
--- a/devtools/client/memory/test/browser/browser.ini
+++ b/devtools/client/memory/test/browser/browser.ini
@@ -13,11 +13,13 @@ support-files =
     skip-if = debug # bug 1219554
 [browser_memory_dominator_trees_01.js]
 [browser_memory_filter_01.js]
     skip-if = debug # bug 1219554
 [browser_memory_no_allocation_stacks.js]
 [browser_memory_no_auto_expand.js]
     skip-if = debug # bug 1219554
 [browser_memory_percents_01.js]
+[browser_memory-clear-snapshots.js]
+    skip-if = debug # bug 1219554
 [browser_memory-simple-01.js]
     skip-if = debug # bug 1219554
 [browser_memory_transferHeapSnapshot_e10s_01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/browser/browser_memory-clear-snapshots.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests taking and then clearing snapshots.
+ */
+
+const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
+
+this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
+  const { gStore, document } = panel.panelWin;
+  const { getState, dispatch } = gStore;
+
+  let snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
+  is(getState().snapshots.length, 0, "Starts with no snapshots in store");
+  is(snapshotEls.length, 0, "No snapshots visible");
+
+  info("Take two snapshots");
+  yield takeSnapshot(panel.panelWin);
+  yield takeSnapshot(panel.panelWin);
+  yield waitUntilSnapshotState(gStore, [states.SAVED_CENSUS, states.SAVED_CENSUS]);
+
+  snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
+  is(snapshotEls.length, 2, "Two snapshots visible");
+
+  info("Click on Clear Snapshots");
+  yield clearSnapshots(panel.panelWin);
+  is(getState().snapshots.length, 0, "No snapshots in store");
+  snapshotEls = document.querySelectorAll("#memory-tool-container .list li");
+  is(snapshotEls.length, 0, "No snapshot visible");
+});
--- a/devtools/client/memory/test/browser/head.js
+++ b/devtools/client/memory/test/browser/head.js
@@ -99,16 +99,24 @@ function waitUntilSnapshotState (store, 
 function takeSnapshot (window) {
   let { gStore, document } = window;
   let snapshotCount = gStore.getState().snapshots.length;
   info(`Taking snapshot...`);
   document.querySelector(".devtools-toolbar .take-snapshot").click();
   return waitUntilState(gStore, () => gStore.getState().snapshots.length === snapshotCount + 1);
 }
 
+function clearSnapshots (window) {
+  let { gStore, document } = window;
+  document.querySelector(".devtools-toolbar .clear-snapshots").click();
+  return waitUntilState(gStore, () => gStore.getState().snapshots.every(
+    (snapshot) => snapshot.state !== states.SAVED_CENSUS)
+  );
+}
+
 /**
  * Sets breakdown and waits for currently selected breakdown to use it
  * and be completed the census.
  */
 function setBreakdown (window, type) {
   info(`Setting breakdown to ${type}...`);
   let { gStore, gHeapAnalysesClient } = window;
   // XXX: Should handle this via clicking the DOM, but React doesn't
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_01.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes snapshots with state SAVED_CENSUS
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
+  ok(true, "snapshot created");
+
+  ok(true, "dispatch clearSnapshots action");
+  let deleteEvents = Promise.all([
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+  ]);
+  dispatch(clearSnapshots(heapWorker));
+  yield deleteEvents;
+  ok(true, "received delete snapshots events");
+
+  equal(getState().snapshots.length, 0, "no snapshot remaining");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_02.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots preserves snapshots with state != SAVED_CENSUS or ERROR
+
+let { takeSnapshotAndCensus, clearSnapshots, takeSnapshot } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  ok(true, "create a snapshot in SAVED_CENSUS state");
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  ok(true, "create a snapshot in SAVED state");
+  dispatch(takeSnapshot(front));
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED]);
+  ok(true, "snapshots created with expected states");
+
+  ok(true, "dispatch clearSnapshots action");
+  let deleteEvents = Promise.all([
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+  ]);
+  dispatch(clearSnapshots(heapWorker));
+  yield deleteEvents;
+  ok(true, "received delete snapshots events");
+
+  equal(getState().snapshots.length, 1, "one snapshot remaining");
+  let remainingSnapshot = getState().snapshots[0];
+  notEqual(remainingSnapshot.state, states.SAVED_CENSUS,
+    "remaining snapshot doesn't have the SAVED_CENSUS state");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_03.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes snapshots with state ERROR
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  ok(true, "create a snapshot with SAVED_CENSUS state");
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
+  ok(true, "snapshot created with SAVED_CENSUS state");
+
+  ok(true, "set snapshot state to error");
+  let id = getState().snapshots[0].id;
+  dispatch({ type: actions.SNAPSHOT_ERROR, id, error: new Error("_") });
+  yield waitUntilSnapshotState(store, [states.ERROR]);
+  ok(true, "snapshot set to error state");
+
+  ok(true, "dispatch clearSnapshots action");
+  let deleteEvents = Promise.all([
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+  ]);
+  dispatch(clearSnapshots(heapWorker));
+  yield deleteEvents;
+  ok(true, "received delete snapshots events");
+  equal(getState().snapshots.length, 0, "error snapshot deleted");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_04.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes several snapshots
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  ok(true, "create 3 snapshots in SAVED_CENSUS state");
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  ok(true, "snapshots created in SAVED_CENSUS state");
+  yield waitUntilSnapshotState(store,
+    [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
+
+  ok(true, "set first snapshot state to error");
+  let id = getState().snapshots[0].id;
+  dispatch({ type: actions.SNAPSHOT_ERROR, id, error: new Error("_") });
+  yield waitUntilSnapshotState(store,
+    [states.ERROR, states.SAVED_CENSUS, states.SAVED_CENSUS]);
+  ok(true, "first snapshot set to error state");
+
+  ok(true, "dispatch clearSnapshots action");
+  let deleteEvents = Promise.all([
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_END)
+  ]);
+  dispatch(clearSnapshots(heapWorker));
+  yield deleteEvents;
+  ok(true, "received delete snapshots events");
+
+  equal(getState().snapshots.length, 0, "no snapshot remaining");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-clear-snapshots_05.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test clearSnapshots deletes several snapshots
+
+let { takeSnapshotAndCensus, clearSnapshots } = require("devtools/client/memory/actions/snapshot");
+let { snapshotState: states, actions } = require("devtools/client/memory/constants");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  ok(true, "create 3 snapshots in SAVED_CENSUS state");
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  ok(true, "snapshots created in SAVED_CENSUS state");
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS]);
+
+  let errorHeapWorker = {
+    deleteHeapSnapshot: function() {
+      return Promise.reject("_");
+    }
+  };
+
+  ok(true, "dispatch clearSnapshots action");
+  let deleteEvents = Promise.all([
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_START),
+    waitUntilAction(store, actions.DELETE_SNAPSHOTS_END),
+    waitUntilAction(store, actions.SNAPSHOT_ERROR),
+    waitUntilAction(store, actions.SNAPSHOT_ERROR),
+  ]);
+  dispatch(clearSnapshots(errorHeapWorker));
+  yield deleteEvents;
+  ok(true, "received delete snapshots and snapshot error events");
+  equal(getState().snapshots.length, 0, "no snapshot remaining");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
--- a/devtools/client/memory/test/unit/xpcshell.ini
+++ b/devtools/client/memory/test/unit/xpcshell.ini
@@ -5,16 +5,21 @@ tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_action_diffing_01.js]
 [test_action_diffing_02.js]
 [test_action_diffing_03.js]
 [test_action_diffing_04.js]
 [test_action_diffing_05.js]
+[test_action-clear-snapshots_01.js]
+[test_action-clear-snapshots_02.js]
+[test_action-clear-snapshots_03.js]
+[test_action-clear-snapshots_04.js]
+[test_action-clear-snapshots_05.js]
 [test_action-export-snapshot.js]
 [test_action-filter-01.js]
 [test_action-filter-02.js]
 [test_action-filter-03.js]
 [test_action-import-snapshot-and-census.js]
 [test_action-select-snapshot.js]
 [test_action-set-breakdown.js]
 [test_action-set-breakdown-and-refresh-01.js]
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -53,19 +53,20 @@ html, body, #app, #memory-tool {
   align-items: center;
 }
 
 .devtools-toolbar > .toolbar-group:nth-of-type(1) {
   /**
    * We want this to be exactly at a `--sidebar-width` distance from the
    * toolbar's start boundary. A `.devtools-toolbar` has a 3px start padding.
    */
-  flex: 0 0 calc(var(--sidebar-width) - 3px);
+  flex: 0 0 calc(var(--sidebar-width) - 4px);
   border-inline-end: 1px solid var(--theme-splitter-color);
   margin-inline-end: 5px;
+  padding-right: 1px;
 }
 
 .devtools-toolbar > .toolbar-group {
   /**
    * Flex: contains several children, which need to be laid out horizontally,
    * and aligned vertically in the middle of the container.
    */
   display: flex;
@@ -106,18 +107,23 @@ html, body, #app, #memory-tool {
     background-image: url(images/command-screenshot@2x.png);
   }
 }
 
 /**
  * Due to toolbar styles of `.devtools-toolbarbutton:not([label])` which overrides
  * .devtools-toolbarbutton's min-width of 78px, reset the min-width.
  */
-#import-snapshot {
-  min-width: 78px;
+#import-snapshot,
+#clear-snapshots {
+  -moz-box-align: center;
+  flex-grow: 1;
+  padding: 1px;
+  margin: 2px 1px;
+  min-width: unset;
 }
 
 .spacer {
   flex: 1;
 }
 
 #filter {
   align-self: stretch;