summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/reducers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/memory/reducers
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/memory/reducers')
-rw-r--r--devtools/client/memory/reducers/allocations.js54
-rw-r--r--devtools/client/memory/reducers/census-display.js24
-rw-r--r--devtools/client/memory/reducers/diffing.js175
-rw-r--r--devtools/client/memory/reducers/errors.js21
-rw-r--r--devtools/client/memory/reducers/filter.js14
-rw-r--r--devtools/client/memory/reducers/front.js11
-rw-r--r--devtools/client/memory/reducers/individuals.js84
-rw-r--r--devtools/client/memory/reducers/label-display.js22
-rw-r--r--devtools/client/memory/reducers/moz.build19
-rw-r--r--devtools/client/memory/reducers/sizes.js20
-rw-r--r--devtools/client/memory/reducers/snapshots.js517
-rw-r--r--devtools/client/memory/reducers/tree-map-display.js22
-rw-r--r--devtools/client/memory/reducers/view.js52
13 files changed, 1035 insertions, 0 deletions
diff --git a/devtools/client/memory/reducers/allocations.js b/devtools/client/memory/reducers/allocations.js
new file mode 100644
index 0000000000..f019705560
--- /dev/null
+++ b/devtools/client/memory/reducers/allocations.js
@@ -0,0 +1,54 @@
+/* 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("resource://devtools/shared/DevToolsUtils.js");
+const { actions } = require("resource://devtools/client/memory/constants.js");
+
+const 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) {
+ const 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 0000000000..59a9d9ca5c
--- /dev/null
+++ b/devtools/client/memory/reducers/census-display.js
@@ -0,0 +1,24 @@
+/* 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("resource://devtools/client/memory/constants.js");
+const DEFAULT_CENSUS_DISPLAY = censusDisplays.coarseType;
+
+const handlers = Object.create(null);
+
+handlers[actions.SET_CENSUS_DISPLAY] = function (_, { display }) {
+ return display;
+};
+
+module.exports = function (state = DEFAULT_CENSUS_DISPLAY, action) {
+ const 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 0000000000..da28b57deb
--- /dev/null
+++ b/devtools/client/memory/reducers/diffing.js
@@ -0,0 +1,175 @@
+/* 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("resource://devtools/client/shared/vendor/immutable.js");
+const {
+ immutableUpdate,
+ assert,
+} = require("resource://devtools/shared/DevToolsUtils.js");
+const {
+ actions,
+ diffingState,
+ viewState,
+} = require("resource://devtools/client/memory/constants.js");
+const {
+ snapshotIsDiffable,
+} = require("resource://devtools/client/memory/utils.js");
+
+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 0000000000..ad1c8bd621
--- /dev/null
+++ b/devtools/client/memory/reducers/errors.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 {
+ ERROR_TYPE: TASK_ERROR_TYPE,
+} = require("resource://devtools/client/shared/redux/middleware/task.js");
+
+/**
+ * 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 0000000000..3a1ad7d8cb
--- /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("resource://devtools/client/memory/constants.js");
+
+module.exports = function filterReducer(filterString = null, action) {
+ if (action.type === actions.SET_FILTER_STRING) {
+ return action.filter || null;
+ }
+
+ return filterString;
+};
diff --git a/devtools/client/memory/reducers/front.js b/devtools/client/memory/reducers/front.js
new file mode 100644
index 0000000000..92ff6c151c
--- /dev/null
+++ b/devtools/client/memory/reducers/front.js
@@ -0,0 +1,11 @@
+/* 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("resource://devtools/client/memory/constants.js");
+
+module.exports = (front = null, action) => {
+ return action.type === actions.UPDATE_MEMORY_FRONT ? action.front : front;
+};
diff --git a/devtools/client/memory/reducers/individuals.js b/devtools/client/memory/reducers/individuals.js
new file mode 100644
index 0000000000..9b5a2c89a5
--- /dev/null
+++ b/devtools/client/memory/reducers/individuals.js
@@ -0,0 +1,84 @@
+/* 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("resource://devtools/shared/DevToolsUtils.js");
+const {
+ actions,
+ individualsState,
+ viewState,
+} = require("resource://devtools/client/memory/constants.js");
+
+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 0000000000..678930a91a
--- /dev/null
+++ b/devtools/client/memory/reducers/label-display.js
@@ -0,0 +1,22 @@
+/* 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("resource://devtools/client/memory/constants.js");
+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 0000000000..53677d1d8e
--- /dev/null
+++ b/devtools/client/memory/reducers/moz.build
@@ -0,0 +1,19 @@
+# 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",
+ "front.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 0000000000..f79e9f6b28
--- /dev/null
+++ b/devtools/client/memory/reducers/sizes.js
@@ -0,0 +1,20 @@
+/* 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("resource://devtools/client/memory/constants.js");
+const {
+ immutableUpdate,
+} = require("resource://devtools/shared/DevToolsUtils.js");
+
+const handlers = Object.create(null);
+
+handlers[actions.RESIZE_SHORTEST_PATHS] = function (sizes, { size }) {
+ return immutableUpdate(sizes, { shortestPathsSize: size });
+};
+
+module.exports = function (sizes = { shortestPathsSize: 0.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 0000000000..a56deddb9a
--- /dev/null
+++ b/devtools/client/memory/reducers/snapshots.js
@@ -0,0 +1,517 @@
+/* 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("resource://devtools/client/shared/vendor/immutable.js");
+const {
+ immutableUpdate,
+ assert,
+} = require("resource://devtools/shared/DevToolsUtils.js");
+const {
+ actions,
+ snapshotState: states,
+ censusState,
+ treeMapState,
+ dominatorTreeState,
+ viewState,
+} = require("resource://devtools/client/memory/constants.js");
+const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
+
+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.includes(s.id));
+};
+
+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 0000000000..7f145e8181
--- /dev/null
+++ b/devtools/client/memory/reducers/tree-map-display.js
@@ -0,0 +1,22 @@
+/* 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("resource://devtools/client/memory/constants.js");
+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 0000000000..99dab7ef03
--- /dev/null
+++ b/devtools/client/memory/reducers/view.js
@@ -0,0 +1,52 @@
+/* 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("resource://devtools/shared/DevToolsUtils.js");
+const {
+ actions,
+ viewState,
+} = require("resource://devtools/client/memory/constants.js");
+
+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;
+};