summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/reducers
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/reducers')
-rw-r--r--devtools/client/memory/reducers/allocations.js42
-rw-r--r--devtools/client/memory/reducers/census-display.js21
-rw-r--r--devtools/client/memory/reducers/diffing.js146
-rw-r--r--devtools/client/memory/reducers/errors.js17
-rw-r--r--devtools/client/memory/reducers/filter.js14
-rw-r--r--devtools/client/memory/reducers/individuals.js73
-rw-r--r--devtools/client/memory/reducers/label-display.js19
-rw-r--r--devtools/client/memory/reducers/moz.build18
-rw-r--r--devtools/client/memory/reducers/sizes.js18
-rw-r--r--devtools/client/memory/reducers/snapshots.js459
-rw-r--r--devtools/client/memory/reducers/tree-map-display.js19
-rw-r--r--devtools/client/memory/reducers/view.js49
12 files changed, 895 insertions, 0 deletions
diff --git a/devtools/client/memory/reducers/allocations.js b/devtools/client/memory/reducers/allocations.js
new file mode 100644
index 000000000..ccb92f825
--- /dev/null
+++ b/devtools/client/memory/reducers/allocations.js
@@ -0,0 +1,42 @@
+/* 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/. */
+
+const { assert } = require("devtools/shared/DevToolsUtils");
+const { actions } = require("../constants");
+
+let handlers = Object.create(null);
+
+handlers[actions.TOGGLE_RECORD_ALLOCATION_STACKS_START] = function (state, action) {
+ assert(!state.togglingInProgress,
+ "Changing recording state must not be reentrant.");
+
+ return {
+ recording: !state.recording,
+ togglingInProgress: true,
+ };
+};
+
+handlers[actions.TOGGLE_RECORD_ALLOCATION_STACKS_END] = function (state, action) {
+ assert(state.togglingInProgress,
+ "Should not complete changing recording state if we weren't changing "
+ + "recording state already.");
+
+ return {
+ recording: state.recording,
+ togglingInProgress: false,
+ };
+};
+
+const DEFAULT_ALLOCATIONS_STATE = {
+ recording: false,
+ togglingInProgress: false
+};
+
+module.exports = function (state = DEFAULT_ALLOCATIONS_STATE, action) {
+ let handle = handlers[action.type];
+ if (handle) {
+ return handle(state, action);
+ }
+ return state;
+};
diff --git a/devtools/client/memory/reducers/census-display.js b/devtools/client/memory/reducers/census-display.js
new file mode 100644
index 000000000..83535a201
--- /dev/null
+++ b/devtools/client/memory/reducers/census-display.js
@@ -0,0 +1,21 @@
+/* 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 { actions, censusDisplays } = require("../constants");
+const DEFAULT_CENSUS_DISPLAY = censusDisplays.coarseType;
+
+let handlers = Object.create(null);
+
+handlers[actions.SET_CENSUS_DISPLAY] = function (_, { display }) {
+ return display;
+};
+
+module.exports = function (state = DEFAULT_CENSUS_DISPLAY, action) {
+ let handle = handlers[action.type];
+ if (handle) {
+ return handle(state, action);
+ }
+ return state;
+};
diff --git a/devtools/client/memory/reducers/diffing.js b/devtools/client/memory/reducers/diffing.js
new file mode 100644
index 000000000..6d4973c8e
--- /dev/null
+++ b/devtools/client/memory/reducers/diffing.js
@@ -0,0 +1,146 @@
+/* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const { immutableUpdate, assert } = require("devtools/shared/DevToolsUtils");
+const { actions, diffingState, viewState } = require("../constants");
+const { snapshotIsDiffable } = require("../utils");
+
+const handlers = Object.create(null);
+
+handlers[actions.POP_VIEW] = function (diffing, { previousView }) {
+ if (previousView.state === viewState.DIFFING) {
+ assert(previousView.diffing, "Should have previousView.diffing");
+ return previousView.diffing;
+ }
+
+ return null;
+};
+
+handlers[actions.CHANGE_VIEW] = function (diffing, { newViewState }) {
+ if (newViewState === viewState.DIFFING) {
+ assert(!diffing, "Should not switch to diffing view when already diffing");
+ return Object.freeze({
+ firstSnapshotId: null,
+ secondSnapshotId: null,
+ census: null,
+ state: diffingState.SELECTING,
+ });
+ }
+
+ return null;
+};
+
+handlers[actions.SELECT_SNAPSHOT_FOR_DIFFING] = function (diffing, { snapshot }) {
+ assert(diffing,
+ "Should never select a snapshot for diffing when we aren't diffing " +
+ "anything");
+ assert(diffing.state === diffingState.SELECTING,
+ "Can't select when not in SELECTING state");
+ assert(snapshotIsDiffable(snapshot),
+ "snapshot must be in a diffable state");
+
+ if (!diffing.firstSnapshotId) {
+ return immutableUpdate(diffing, {
+ firstSnapshotId: snapshot.id
+ });
+ }
+
+ assert(!diffing.secondSnapshotId,
+ "If we aren't selecting the first, then we must be selecting the " +
+ "second");
+
+ if (snapshot.id === diffing.firstSnapshotId) {
+ // Ignore requests to select the same snapshot.
+ return diffing;
+ }
+
+ return immutableUpdate(diffing, {
+ secondSnapshotId: snapshot.id
+ });
+};
+
+handlers[actions.TAKE_CENSUS_DIFF_START] = function (diffing, action) {
+ assert(diffing, "Should be diffing when starting a census diff");
+ assert(action.first.id === diffing.firstSnapshotId,
+ "First snapshot's id should match");
+ assert(action.second.id === diffing.secondSnapshotId,
+ "Second snapshot's id should match");
+
+ return immutableUpdate(diffing, {
+ state: diffingState.TAKING_DIFF,
+ census: {
+ report: null,
+ inverted: action.inverted,
+ filter: action.filter,
+ display: action.display,
+ }
+ });
+};
+
+handlers[actions.TAKE_CENSUS_DIFF_END] = function (diffing, action) {
+ assert(diffing, "Should be diffing when ending a census diff");
+ assert(action.first.id === diffing.firstSnapshotId,
+ "First snapshot's id should match");
+ assert(action.second.id === diffing.secondSnapshotId,
+ "Second snapshot's id should match");
+
+ return immutableUpdate(diffing, {
+ state: diffingState.TOOK_DIFF,
+ census: {
+ report: action.report,
+ parentMap: action.parentMap,
+ expanded: Immutable.Set(),
+ inverted: action.inverted,
+ filter: action.filter,
+ display: action.display,
+ }
+ });
+};
+
+handlers[actions.DIFFING_ERROR] = function (diffing, action) {
+ return {
+ state: diffingState.ERROR,
+ error: action.error
+ };
+};
+
+handlers[actions.EXPAND_DIFFING_CENSUS_NODE] = function (diffing, { node }) {
+ assert(diffing, "Should be diffing if expanding diffing's census nodes");
+ assert(diffing.state === diffingState.TOOK_DIFF,
+ "Should have taken the census diff if expanding nodes");
+ assert(diffing.census, "Should have a census");
+ assert(diffing.census.report, "Should have a census report");
+ assert(diffing.census.expanded, "Should have a census's expanded set");
+
+ const expanded = diffing.census.expanded.add(node.id);
+ const census = immutableUpdate(diffing.census, { expanded });
+ return immutableUpdate(diffing, { census });
+};
+
+handlers[actions.COLLAPSE_DIFFING_CENSUS_NODE] = function (diffing, { node }) {
+ assert(diffing, "Should be diffing if expanding diffing's census nodes");
+ assert(diffing.state === diffingState.TOOK_DIFF,
+ "Should have taken the census diff if expanding nodes");
+ assert(diffing.census, "Should have a census");
+ assert(diffing.census.report, "Should have a census report");
+ assert(diffing.census.expanded, "Should have a census's expanded set");
+
+ const expanded = diffing.census.expanded.delete(node.id);
+ const census = immutableUpdate(diffing.census, { expanded });
+ return immutableUpdate(diffing, { census });
+};
+
+handlers[actions.FOCUS_DIFFING_CENSUS_NODE] = function (diffing, { node }) {
+ assert(diffing, "Should be diffing.");
+ assert(diffing.census, "Should have a census");
+ const census = immutableUpdate(diffing.census, { focused: node });
+ return immutableUpdate(diffing, { census });
+};
+
+module.exports = function (diffing = null, action) {
+ const handler = handlers[action.type];
+ return handler ? handler(diffing, action) : diffing;
+};
diff --git a/devtools/client/memory/reducers/errors.js b/devtools/client/memory/reducers/errors.js
new file mode 100644
index 000000000..5fc282df2
--- /dev/null
+++ b/devtools/client/memory/reducers/errors.js
@@ -0,0 +1,17 @@
+/* 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/. */
+
+const { ERROR_TYPE: TASK_ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
+
+/**
+ * Handle errors dispatched from task middleware and
+ * store them so we can check in tests or dump them out.
+ */
+module.exports = function (state = [], action) {
+ switch (action.type) {
+ case TASK_ERROR_TYPE:
+ return [...state, action.error];
+ }
+ return state;
+};
diff --git a/devtools/client/memory/reducers/filter.js b/devtools/client/memory/reducers/filter.js
new file mode 100644
index 000000000..99188142c
--- /dev/null
+++ b/devtools/client/memory/reducers/filter.js
@@ -0,0 +1,14 @@
+/* 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 { actions } = require("../constants");
+
+module.exports = function (filterString = null, action) {
+ if (action.type === actions.SET_FILTER_STRING) {
+ return action.filter || null;
+ } else {
+ return filterString;
+ }
+};
diff --git a/devtools/client/memory/reducers/individuals.js b/devtools/client/memory/reducers/individuals.js
new file mode 100644
index 000000000..10f016fe5
--- /dev/null
+++ b/devtools/client/memory/reducers/individuals.js
@@ -0,0 +1,73 @@
+/* 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 { assert, immutableUpdate } = require("devtools/shared/DevToolsUtils");
+const { actions, individualsState, viewState } = require("../constants");
+
+const handlers = Object.create(null);
+
+handlers[actions.POP_VIEW] = function (_state, _action) {
+ return null;
+};
+
+handlers[actions.CHANGE_VIEW] = function (individuals, { newViewState }) {
+ if (newViewState === viewState.INDIVIDUALS) {
+ assert(!individuals,
+ "Should not switch to individuals view when already in individuals view");
+ return Object.freeze({
+ state: individualsState.COMPUTING_DOMINATOR_TREE,
+ });
+ }
+
+ return null;
+};
+
+handlers[actions.FOCUS_INDIVIDUAL] = function (individuals, { node }) {
+ assert(individuals, "Should have individuals");
+ return immutableUpdate(individuals, { focused: node });
+};
+
+handlers[actions.FETCH_INDIVIDUALS_START] = function (individuals, action) {
+ assert(individuals, "Should have individuals");
+ return Object.freeze({
+ state: individualsState.FETCHING,
+ focused: individuals.focused,
+ });
+};
+
+handlers[actions.FETCH_INDIVIDUALS_END] = function (individuals, action) {
+ assert(individuals, "Should have individuals");
+ assert(!individuals.nodes, "Should not have nodes");
+ assert(individuals.state === individualsState.FETCHING,
+ "Should only end fetching individuals after starting.");
+
+ const focused = individuals.focused
+ ? action.nodes.find(n => n.nodeId === individuals.focused.nodeId)
+ : null;
+
+ return Object.freeze({
+ state: individualsState.FETCHED,
+ nodes: action.nodes,
+ id: action.id,
+ censusBreakdown: action.censusBreakdown,
+ indices: action.indices,
+ labelDisplay: action.labelDisplay,
+ focused,
+ dominatorTree: action.dominatorTree,
+ });
+};
+
+handlers[actions.INDIVIDUALS_ERROR] = function (_, { error }) {
+ return Object.freeze({
+ error,
+ nodes: null,
+ state: individualsState.ERROR,
+ });
+};
+
+module.exports = function (individuals = null, action) {
+ const handler = handlers[action.type];
+ return handler ? handler(individuals, action) : individuals;
+};
diff --git a/devtools/client/memory/reducers/label-display.js b/devtools/client/memory/reducers/label-display.js
new file mode 100644
index 000000000..e7d142682
--- /dev/null
+++ b/devtools/client/memory/reducers/label-display.js
@@ -0,0 +1,19 @@
+/* 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 { actions, labelDisplays } = require("../constants");
+const DEFAULT_LABEL_DISPLAY = labelDisplays.coarseType;
+
+const handlers = Object.create(null);
+
+handlers[actions.SET_LABEL_DISPLAY] = function (_, { display }) {
+ return display;
+};
+
+module.exports = function (state = DEFAULT_LABEL_DISPLAY, action) {
+ const handler = handlers[action.type];
+ return handler ? handler(state, action) : state;
+};
diff --git a/devtools/client/memory/reducers/moz.build b/devtools/client/memory/reducers/moz.build
new file mode 100644
index 000000000..664c4496c
--- /dev/null
+++ b/devtools/client/memory/reducers/moz.build
@@ -0,0 +1,18 @@
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+ 'allocations.js',
+ 'census-display.js',
+ 'diffing.js',
+ 'errors.js',
+ 'filter.js',
+ 'individuals.js',
+ 'label-display.js',
+ 'sizes.js',
+ 'snapshots.js',
+ 'tree-map-display.js',
+ 'view.js',
+)
diff --git a/devtools/client/memory/reducers/sizes.js b/devtools/client/memory/reducers/sizes.js
new file mode 100644
index 000000000..f04530cfc
--- /dev/null
+++ b/devtools/client/memory/reducers/sizes.js
@@ -0,0 +1,18 @@
+/* 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 { actions } = require("../constants");
+const { immutableUpdate } = require("devtools/shared/DevToolsUtils");
+
+const handlers = Object.create(null);
+
+handlers[actions.RESIZE_SHORTEST_PATHS] = function (sizes, { size }) {
+ return immutableUpdate(sizes, { shortestPathsSize: size });
+};
+
+module.exports = function (sizes = { shortestPathsSize: .5 }, action) {
+ const handler = handlers[action.type];
+ return handler ? handler(sizes, action) : sizes;
+};
diff --git a/devtools/client/memory/reducers/snapshots.js b/devtools/client/memory/reducers/snapshots.js
new file mode 100644
index 000000000..6293bdded
--- /dev/null
+++ b/devtools/client/memory/reducers/snapshots.js
@@ -0,0 +1,459 @@
+/* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const { immutableUpdate, assert } = require("devtools/shared/DevToolsUtils");
+const {
+ actions,
+ snapshotState: states,
+ censusState,
+ treeMapState,
+ dominatorTreeState,
+ viewState,
+} = require("../constants");
+const DominatorTreeNode = require("devtools/shared/heapsnapshot/DominatorTreeNode");
+
+const handlers = Object.create(null);
+
+handlers[actions.SNAPSHOT_ERROR] = function (snapshots, { id, error }) {
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { state: states.ERROR, error })
+ : snapshot;
+ });
+};
+
+handlers[actions.TAKE_SNAPSHOT_START] = function (snapshots, { snapshot }) {
+ return [...snapshots, snapshot];
+};
+
+handlers[actions.TAKE_SNAPSHOT_END] = function (snapshots, { id, path }) {
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { state: states.SAVED, path })
+ : snapshot;
+ });
+};
+
+handlers[actions.IMPORT_SNAPSHOT_START] = handlers[actions.TAKE_SNAPSHOT_START];
+
+handlers[actions.READ_SNAPSHOT_START] = function (snapshots, { id }) {
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { state: states.READING })
+ : snapshot;
+ });
+};
+
+handlers[actions.READ_SNAPSHOT_END] = function (snapshots, { id, creationTime }) {
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { state: states.READ, creationTime })
+ : snapshot;
+ });
+};
+
+handlers[actions.TAKE_CENSUS_START] = function (snapshots, { id, display, filter }) {
+ const census = {
+ report: null,
+ display,
+ filter,
+ state: censusState.SAVING
+ };
+
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { census })
+ : snapshot;
+ });
+};
+
+handlers[actions.TAKE_CENSUS_END] = function (snapshots, { id,
+ report,
+ parentMap,
+ display,
+ filter }) {
+ const census = {
+ report,
+ parentMap,
+ expanded: Immutable.Set(),
+ display,
+ filter,
+ state: censusState.SAVED
+ };
+
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { census })
+ : snapshot;
+ });
+};
+
+handlers[actions.TAKE_CENSUS_ERROR] = function (snapshots, { id, error }) {
+ assert(error, "actions with TAKE_CENSUS_ERROR should have an error");
+
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ const census = Object.freeze({
+ state: censusState.ERROR,
+ error,
+ });
+
+ return immutableUpdate(snapshot, { census });
+ });
+};
+
+handlers[actions.TAKE_TREE_MAP_START] = function (snapshots, { id, display }) {
+ const treeMap = {
+ report: null,
+ display,
+ state: treeMapState.SAVING
+ };
+
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { treeMap })
+ : snapshot;
+ });
+};
+
+handlers[actions.TAKE_TREE_MAP_END] = function (snapshots, action) {
+ const { id, report, display } = action;
+ const treeMap = {
+ report,
+ display,
+ state: treeMapState.SAVED
+ };
+
+ return snapshots.map(snapshot => {
+ return snapshot.id === id
+ ? immutableUpdate(snapshot, { treeMap })
+ : snapshot;
+ });
+};
+
+handlers[actions.TAKE_TREE_MAP_ERROR] = function (snapshots, { id, error }) {
+ assert(error, "actions with TAKE_TREE_MAP_ERROR should have an error");
+
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ const treeMap = Object.freeze({
+ state: treeMapState.ERROR,
+ error,
+ });
+
+ return immutableUpdate(snapshot, { treeMap });
+ });
+};
+
+handlers[actions.EXPAND_CENSUS_NODE] = function (snapshots, { id, node }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.census, "Should have a census");
+ assert(snapshot.census.report, "Should have a census report");
+ assert(snapshot.census.expanded, "Should have a census's expanded set");
+
+ const expanded = snapshot.census.expanded.add(node.id);
+ const census = immutableUpdate(snapshot.census, { expanded });
+ return immutableUpdate(snapshot, { census });
+ });
+};
+
+handlers[actions.COLLAPSE_CENSUS_NODE] = function (snapshots, { id, node }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.census, "Should have a census");
+ assert(snapshot.census.report, "Should have a census report");
+ assert(snapshot.census.expanded, "Should have a census's expanded set");
+
+ const expanded = snapshot.census.expanded.delete(node.id);
+ const census = immutableUpdate(snapshot.census, { expanded });
+ return immutableUpdate(snapshot, { census });
+ });
+};
+
+handlers[actions.FOCUS_CENSUS_NODE] = function (snapshots, { id, node }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.census, "Should have a census");
+ const census = immutableUpdate(snapshot.census, { focused: node });
+ 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, { newViewState }) {
+ return newViewState === viewState.DIFFING
+ ? snapshots.map(s => immutableUpdate(s, { selected: false }))
+ : snapshots;
+};
+
+handlers[actions.POP_VIEW] = function (snapshots, { previousView }) {
+ return snapshots.map(s => immutableUpdate(s, {
+ selected: s.id === previousView.selected
+ }));
+};
+
+handlers[actions.COMPUTE_DOMINATOR_TREE_START] = function (snapshots, { id }) {
+ const dominatorTree = Object.freeze({
+ state: dominatorTreeState.COMPUTING,
+ dominatorTreeId: undefined,
+ root: undefined,
+ });
+
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(!snapshot.dominatorTree,
+ "Should not have a dominator tree model");
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.COMPUTE_DOMINATOR_TREE_END] = function (snapshots, { id, dominatorTreeId }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree model");
+ assert(snapshot.dominatorTree.state == dominatorTreeState.COMPUTING,
+ "Should be in the COMPUTING state");
+
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, {
+ state: dominatorTreeState.COMPUTED,
+ dominatorTreeId,
+ });
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.FETCH_DOMINATOR_TREE_START] = function (snapshots, { id, display }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree model");
+ assert(snapshot.dominatorTree.state !== dominatorTreeState.COMPUTING &&
+ snapshot.dominatorTree.state !== dominatorTreeState.ERROR,
+ `Should have already computed the dominator tree, found state = ${snapshot.dominatorTree.state}`);
+
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, {
+ state: dominatorTreeState.FETCHING,
+ root: undefined,
+ display,
+ });
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.FETCH_DOMINATOR_TREE_END] = function (snapshots, { id, root }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree model");
+ assert(snapshot.dominatorTree.state == dominatorTreeState.FETCHING,
+ "Should be in the FETCHING state");
+
+ let focused;
+ if (snapshot.dominatorTree.focused) {
+ focused = (function findFocused(node) {
+ if (node.nodeId === snapshot.dominatorTree.focused.nodeId) {
+ return node;
+ }
+
+ if (node.children) {
+ const length = node.children.length;
+ for (let i = 0; i < length; i++) {
+ const result = findFocused(node.children[i]);
+ if (result) {
+ return result;
+ }
+ }
+ }
+
+ return undefined;
+ }(root));
+ }
+
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, {
+ state: dominatorTreeState.LOADED,
+ root,
+ expanded: Immutable.Set(),
+ focused,
+ });
+
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.EXPAND_DOMINATOR_TREE_NODE] = function (snapshots, { id, node }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree");
+ assert(snapshot.dominatorTree.expanded,
+ "Should have the dominator tree's expanded set");
+
+ const expanded = snapshot.dominatorTree.expanded.add(node.nodeId);
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, { expanded });
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.COLLAPSE_DOMINATOR_TREE_NODE] = function (snapshots, { id, node }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree");
+ assert(snapshot.dominatorTree.expanded,
+ "Should have the dominator tree's expanded set");
+
+ const expanded = snapshot.dominatorTree.expanded.delete(node.nodeId);
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, { expanded });
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.FOCUS_DOMINATOR_TREE_NODE] = function (snapshots, { id, node }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree");
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, { focused: node });
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.FETCH_IMMEDIATELY_DOMINATED_START] = function (snapshots, { id }) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree model");
+ assert(snapshot.dominatorTree.state == dominatorTreeState.INCREMENTAL_FETCHING ||
+ snapshot.dominatorTree.state == dominatorTreeState.LOADED,
+ "The dominator tree should be loaded if we are going to " +
+ "incrementally fetch children.");
+
+ const activeFetchRequestCount = snapshot.dominatorTree.activeFetchRequestCount
+ ? snapshot.dominatorTree.activeFetchRequestCount + 1
+ : 1;
+
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, {
+ state: dominatorTreeState.INCREMENTAL_FETCHING,
+ activeFetchRequestCount,
+ });
+
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+handlers[actions.FETCH_IMMEDIATELY_DOMINATED_END] =
+ function (snapshots, { id, path, nodes, moreChildrenAvailable}) {
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ assert(snapshot.dominatorTree, "Should have a dominator tree model");
+ assert(snapshot.dominatorTree.root, "Should have a dominator tree model root");
+ assert(snapshot.dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING,
+ "The dominator tree state should be INCREMENTAL_FETCHING");
+
+ const root = DominatorTreeNode.insert(snapshot.dominatorTree.root,
+ path,
+ nodes,
+ moreChildrenAvailable);
+
+ const focused = snapshot.dominatorTree.focused
+ ? DominatorTreeNode.getNodeByIdAlongPath(snapshot.dominatorTree.focused.nodeId,
+ root,
+ path)
+ : undefined;
+
+ const activeFetchRequestCount = snapshot.dominatorTree.activeFetchRequestCount === 1
+ ? undefined
+ : snapshot.dominatorTree.activeFetchRequestCount - 1;
+
+ // If there are still outstanding requests, we need to stay in the
+ // INCREMENTAL_FETCHING state until they complete.
+ const state = activeFetchRequestCount
+ ? dominatorTreeState.INCREMENTAL_FETCHING
+ : dominatorTreeState.LOADED;
+
+ const dominatorTree = immutableUpdate(snapshot.dominatorTree, {
+ state,
+ root,
+ focused,
+ activeFetchRequestCount,
+ });
+
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+ };
+
+handlers[actions.DOMINATOR_TREE_ERROR] = function (snapshots, { id, error }) {
+ assert(error, "actions with DOMINATOR_TREE_ERROR should have an error");
+
+ return snapshots.map(snapshot => {
+ if (snapshot.id !== id) {
+ return snapshot;
+ }
+
+ const dominatorTree = Object.freeze({
+ state: dominatorTreeState.ERROR,
+ error,
+ });
+
+ return immutableUpdate(snapshot, { dominatorTree });
+ });
+};
+
+module.exports = function (snapshots = [], action) {
+ const handler = handlers[action.type];
+ if (handler) {
+ return handler(snapshots, action);
+ }
+ return snapshots;
+};
diff --git a/devtools/client/memory/reducers/tree-map-display.js b/devtools/client/memory/reducers/tree-map-display.js
new file mode 100644
index 000000000..a0d2faadc
--- /dev/null
+++ b/devtools/client/memory/reducers/tree-map-display.js
@@ -0,0 +1,19 @@
+/* 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 { actions, treeMapDisplays } = require("../constants");
+const DEFAULT_TREE_MAP_DISPLAY = treeMapDisplays.coarseType;
+
+const handlers = Object.create(null);
+
+handlers[actions.SET_TREE_MAP_DISPLAY] = function (_, { display }) {
+ return display;
+};
+
+module.exports = function (state = DEFAULT_TREE_MAP_DISPLAY, action) {
+ const handler = handlers[action.type];
+ return handler ? handler(state, action) : state;
+};
diff --git a/devtools/client/memory/reducers/view.js b/devtools/client/memory/reducers/view.js
new file mode 100644
index 000000000..5d31e0c9b
--- /dev/null
+++ b/devtools/client/memory/reducers/view.js
@@ -0,0 +1,49 @@
+/* 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 { assert } = require("devtools/shared/DevToolsUtils");
+const { actions, viewState } = require("../constants");
+
+const handlers = Object.create(null);
+
+handlers[actions.POP_VIEW] = function (view, _) {
+ assert(view.previous, "Had better have a previous view state when POP_VIEW");
+ return Object.freeze({
+ state: view.previous.state,
+ previous: null,
+ });
+};
+
+handlers[actions.CHANGE_VIEW] = function (view, action) {
+ const { newViewState, oldDiffing, oldSelected } = action;
+ assert(newViewState);
+
+ if (newViewState === viewState.INDIVIDUALS) {
+ assert(oldDiffing || oldSelected);
+ return Object.freeze({
+ state: newViewState,
+ previous: Object.freeze({
+ state: view.state,
+ selected: oldSelected,
+ diffing: oldDiffing,
+ }),
+ });
+ }
+
+ return Object.freeze({
+ state: newViewState,
+ previous: null,
+ });
+};
+
+const DEFAULT_VIEW = {
+ state: viewState.TREE_MAP,
+ previous: null,
+};
+
+module.exports = function (view = DEFAULT_VIEW, action) {
+ const handler = handlers[action.type];
+ return handler ? handler(view, action) : view;
+};