summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/reducers/snapshots.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/reducers/snapshots.js')
-rw-r--r--devtools/client/memory/reducers/snapshots.js517
1 files changed, 517 insertions, 0 deletions
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;
+};