From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- devtools/client/memory/reducers/allocations.js | 54 +++ devtools/client/memory/reducers/census-display.js | 21 + devtools/client/memory/reducers/diffing.js | 170 +++++++ devtools/client/memory/reducers/errors.js | 21 + devtools/client/memory/reducers/filter.js | 14 + devtools/client/memory/reducers/front.js | 11 + devtools/client/memory/reducers/individuals.js | 81 ++++ devtools/client/memory/reducers/label-display.js | 19 + devtools/client/memory/reducers/moz.build | 19 + devtools/client/memory/reducers/sizes.js | 18 + devtools/client/memory/reducers/snapshots.js | 511 +++++++++++++++++++++ .../client/memory/reducers/tree-map-display.js | 22 + devtools/client/memory/reducers/view.js | 49 ++ 13 files changed, 1010 insertions(+) create mode 100644 devtools/client/memory/reducers/allocations.js create mode 100644 devtools/client/memory/reducers/census-display.js create mode 100644 devtools/client/memory/reducers/diffing.js create mode 100644 devtools/client/memory/reducers/errors.js create mode 100644 devtools/client/memory/reducers/filter.js create mode 100644 devtools/client/memory/reducers/front.js create mode 100644 devtools/client/memory/reducers/individuals.js create mode 100644 devtools/client/memory/reducers/label-display.js create mode 100644 devtools/client/memory/reducers/moz.build create mode 100644 devtools/client/memory/reducers/sizes.js create mode 100644 devtools/client/memory/reducers/snapshots.js create mode 100644 devtools/client/memory/reducers/tree-map-display.js create mode 100644 devtools/client/memory/reducers/view.js (limited to 'devtools/client/memory/reducers') diff --git a/devtools/client/memory/reducers/allocations.js b/devtools/client/memory/reducers/allocations.js new file mode 100644 index 0000000000..8a6fba0e1c --- /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("devtools/shared/DevToolsUtils"); +const { actions } = require("devtools/client/memory/constants"); + +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..e1809c9174 --- /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("devtools/client/memory/constants"); +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..f4d0379b38 --- /dev/null +++ b/devtools/client/memory/reducers/diffing.js @@ -0,0 +1,170 @@ +/* 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("devtools/client/memory/constants"); +const { snapshotIsDiffable } = require("devtools/client/memory/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 0000000000..8a7158e17d --- /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("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 0000000000..b1fbcc95d3 --- /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("devtools/client/memory/constants"); + +module.exports = function(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..1dd8f37d81 --- /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("devtools/client/memory/constants"); + +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..30d056c29f --- /dev/null +++ b/devtools/client/memory/reducers/individuals.js @@ -0,0 +1,81 @@ +/* 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("devtools/client/memory/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 0000000000..f46ce3ce88 --- /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("devtools/client/memory/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 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..6b51b61c3a --- /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("devtools/client/memory/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: 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..5ee724de2f --- /dev/null +++ b/devtools/client/memory/reducers/snapshots.js @@ -0,0 +1,511 @@ +/* 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("devtools/client/memory/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.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..233e26a099 --- /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("devtools/client/memory/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 0000000000..0fc395b0d3 --- /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("devtools/client/memory/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; +}; -- cgit v1.2.3