tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Heap.js (14803B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  Component,
      9  createFactory,
     10 } = require("resource://devtools/client/shared/vendor/react.mjs");
     11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     13 const {
     14  assert,
     15  safeErrorString,
     16 } = require("resource://devtools/shared/DevToolsUtils.js");
     17 const Census = createFactory(
     18  require("resource://devtools/client/memory/components/Census.js")
     19 );
     20 const CensusHeader = createFactory(
     21  require("resource://devtools/client/memory/components/CensusHeader.js")
     22 );
     23 const DominatorTree = createFactory(
     24  require("resource://devtools/client/memory/components/DominatorTree.js")
     25 );
     26 const DominatorTreeHeader = createFactory(
     27  require("resource://devtools/client/memory/components/DominatorTreeHeader.js")
     28 );
     29 const TreeMap = createFactory(
     30  require("resource://devtools/client/memory/components/TreeMap.js")
     31 );
     32 const HSplitBox = createFactory(
     33  require("resource://devtools/client/shared/components/HSplitBox.js")
     34 );
     35 const Individuals = createFactory(
     36  require("resource://devtools/client/memory/components/Individuals.js")
     37 );
     38 const IndividualsHeader = createFactory(
     39  require("resource://devtools/client/memory/components/IndividualsHeader.js")
     40 );
     41 const ShortestPaths = createFactory(
     42  require("resource://devtools/client/memory/components/ShortestPaths.js")
     43 );
     44 const {
     45  getStatusTextFull,
     46  L10N,
     47 } = require("resource://devtools/client/memory/utils.js");
     48 const {
     49  snapshotState: states,
     50  diffingState,
     51  viewState,
     52  censusState,
     53  treeMapState,
     54  dominatorTreeState,
     55  individualsState,
     56 } = require("resource://devtools/client/memory/constants.js");
     57 const models = require("resource://devtools/client/memory/models.js");
     58 const { snapshot: snapshotModel, diffingModel } = models;
     59 
     60 /**
     61 * Get the app state's current state atom.
     62 *
     63 * @see the relevant state string constants in `../constants.js`.
     64 *
     65 * @param {models.view} view
     66 * @param {snapshotModel} snapshot
     67 * @param {diffingModel} diffing
     68 * @param {individualsModel} individuals
     69 *
     70 * @return {snapshotState|diffingState|dominatorTreeState}
     71 */
     72 function getState(view, snapshot, diffing, individuals) {
     73  switch (view.state) {
     74    case viewState.CENSUS:
     75      return snapshot.census ? snapshot.census.state : snapshot.state;
     76 
     77    case viewState.DIFFING:
     78      return diffing.state;
     79 
     80    case viewState.TREE_MAP:
     81      return snapshot.treeMap ? snapshot.treeMap.state : snapshot.state;
     82 
     83    case viewState.DOMINATOR_TREE:
     84      return snapshot.dominatorTree
     85        ? snapshot.dominatorTree.state
     86        : snapshot.state;
     87 
     88    case viewState.INDIVIDUALS:
     89      return individuals.state;
     90  }
     91 
     92  assert(false, `Unexpected view state: ${view.state}`);
     93  return null;
     94 }
     95 
     96 /**
     97 * Return true if we should display a status message when we are in the given
     98 * state. Return false otherwise.
     99 *
    100 * @param {snapshotState|diffingState|dominatorTreeState} state
    101 * @param {models.view} view
    102 * @param {snapshotModel} snapshot
    103 *
    104 * @returns {boolean}
    105 */
    106 function shouldDisplayStatus(state, view, snapshot) {
    107  switch (state) {
    108    case states.IMPORTING:
    109    case states.SAVING:
    110    case states.SAVED:
    111    case states.READING:
    112    case censusState.SAVING:
    113    case treeMapState.SAVING:
    114    case diffingState.SELECTING:
    115    case diffingState.TAKING_DIFF:
    116    case dominatorTreeState.COMPUTING:
    117    case dominatorTreeState.COMPUTED:
    118    case dominatorTreeState.FETCHING:
    119    case individualsState.COMPUTING_DOMINATOR_TREE:
    120    case individualsState.FETCHING:
    121      return true;
    122  }
    123  return view.state === viewState.DOMINATOR_TREE && !snapshot.dominatorTree;
    124 }
    125 
    126 /**
    127 * Get the status text to display for the given state.
    128 *
    129 * @param {snapshotState|diffingState|dominatorTreeState} state
    130 * @param {diffingModel} diffing
    131 *
    132 * @returns {string}
    133 */
    134 function getStateStatusText(state, diffing) {
    135  if (state === diffingState.SELECTING) {
    136    return L10N.getStr(
    137      diffing.firstSnapshotId === null
    138        ? "diffing.prompt.selectBaseline"
    139        : "diffing.prompt.selectComparison"
    140    );
    141  }
    142 
    143  return getStatusTextFull(state);
    144 }
    145 
    146 /**
    147 * Given that we should display a status message, return true if we should also
    148 * display a throbber along with the status message. Return false otherwise.
    149 *
    150 * @param {diffingModel} diffing
    151 *
    152 * @returns {boolean}
    153 */
    154 function shouldDisplayThrobber(diffing) {
    155  return !diffing || diffing.state !== diffingState.SELECTING;
    156 }
    157 
    158 /**
    159 * Get the current state's error, or return null if there is none.
    160 *
    161 * @param {snapshotModel} snapshot
    162 * @param {diffingModel} diffing
    163 * @param {individualsModel} individuals
    164 *
    165 * @returns {Error|null}
    166 */
    167 function getError(snapshot, diffing, individuals) {
    168  if (diffing) {
    169    if (diffing.state === diffingState.ERROR) {
    170      return diffing.error;
    171    }
    172    if (diffing.census === censusState.ERROR) {
    173      return diffing.census.error;
    174    }
    175  }
    176 
    177  if (snapshot) {
    178    if (snapshot.state === states.ERROR) {
    179      return snapshot.error;
    180    }
    181 
    182    if (snapshot.census === censusState.ERROR) {
    183      return snapshot.census.error;
    184    }
    185 
    186    if (snapshot.treeMap === treeMapState.ERROR) {
    187      return snapshot.treeMap.error;
    188    }
    189 
    190    if (
    191      snapshot.dominatorTree &&
    192      snapshot.dominatorTree.state === dominatorTreeState.ERROR
    193    ) {
    194      return snapshot.dominatorTree.error;
    195    }
    196  }
    197 
    198  if (individuals && individuals.state === individualsState.ERROR) {
    199    return individuals.error;
    200  }
    201 
    202  return null;
    203 }
    204 
    205 /**
    206 * Main view for the memory tool.
    207 *
    208 * The Heap component contains several panels for different states; an initial
    209 * state of only a button to take a snapshot, loading states, the census view
    210 * tree, the dominator tree, etc.
    211 */
    212 class Heap extends Component {
    213  static get propTypes() {
    214    return {
    215      onSnapshotClick: PropTypes.func.isRequired,
    216      onLoadMoreSiblings: PropTypes.func.isRequired,
    217      onCensusExpand: PropTypes.func.isRequired,
    218      onCensusCollapse: PropTypes.func.isRequired,
    219      onDominatorTreeExpand: PropTypes.func.isRequired,
    220      onDominatorTreeCollapse: PropTypes.func.isRequired,
    221      onCensusFocus: PropTypes.func.isRequired,
    222      onDominatorTreeFocus: PropTypes.func.isRequired,
    223      onShortestPathsResize: PropTypes.func.isRequired,
    224      snapshot: snapshotModel,
    225      onViewSourceInDebugger: PropTypes.func.isRequired,
    226      onPopView: PropTypes.func.isRequired,
    227      individuals: models.individuals,
    228      onViewIndividuals: PropTypes.func.isRequired,
    229      onFocusIndividual: PropTypes.func.isRequired,
    230      diffing: diffingModel,
    231      view: models.view.isRequired,
    232      sizes: PropTypes.object.isRequired,
    233    };
    234  }
    235 
    236  constructor(props) {
    237    super(props);
    238    this._renderHeapView = this._renderHeapView.bind(this);
    239    this._renderInitial = this._renderInitial.bind(this);
    240    this._renderStatus = this._renderStatus.bind(this);
    241    this._renderError = this._renderError.bind(this);
    242    this._renderCensus = this._renderCensus.bind(this);
    243    this._renderTreeMap = this._renderTreeMap.bind(this);
    244    this._renderIndividuals = this._renderIndividuals.bind(this);
    245    this._renderDominatorTree = this._renderDominatorTree.bind(this);
    246  }
    247 
    248  /**
    249   * Render the heap view's container panel with the given contents inside of
    250   * it.
    251   *
    252   * @param {snapshotState|diffingState|dominatorTreeState} state
    253   * @param {...Any} contents
    254   */
    255  _renderHeapView(state, ...contents) {
    256    return dom.div(
    257      {
    258        id: "heap-view",
    259        "data-state": state,
    260      },
    261      dom.div(
    262        {
    263          className: "heap-view-panel",
    264          "data-state": state,
    265        },
    266        ...contents
    267      )
    268    );
    269  }
    270 
    271  _renderInitial(onSnapshotClick) {
    272    return this._renderHeapView(
    273      "initial",
    274      dom.button(
    275        {
    276          className: "devtools-button devtools-button-standalone take-snapshot",
    277          onClick: onSnapshotClick,
    278        },
    279        L10N.getStr("take-snapshot")
    280      )
    281    );
    282  }
    283 
    284  _renderStatus(state, statusText, diffing) {
    285    let throbber = "";
    286    if (shouldDisplayThrobber(diffing)) {
    287      throbber = "devtools-throbber";
    288    }
    289 
    290    return this._renderHeapView(
    291      state,
    292      dom.span(
    293        {
    294          className: `snapshot-status ${throbber}`,
    295        },
    296        statusText
    297      )
    298    );
    299  }
    300 
    301  _renderError(state, statusText, error) {
    302    return this._renderHeapView(
    303      state,
    304      dom.span({ className: "snapshot-status error" }, statusText),
    305      dom.pre({}, safeErrorString(error))
    306    );
    307  }
    308 
    309  _renderCensus(
    310    state,
    311    census,
    312    diffing,
    313    onViewSourceInDebugger,
    314    onViewIndividuals
    315  ) {
    316    assert(
    317      census.report,
    318      "Should not render census that does not have a report"
    319    );
    320 
    321    if (!census.report.children) {
    322      const censusFilterMsg = census.filter
    323        ? L10N.getStr("heapview.none-match")
    324        : L10N.getStr("heapview.empty");
    325      const msg = diffing
    326        ? L10N.getStr("heapview.no-difference")
    327        : censusFilterMsg;
    328      return this._renderHeapView(state, dom.div({ className: "empty" }, msg));
    329    }
    330 
    331    const contents = [];
    332 
    333    if (
    334      census.display.breakdown.by === "allocationStack" &&
    335      census.report.children &&
    336      census.report.children.length === 1 &&
    337      census.report.children[0].name === "noStack"
    338    ) {
    339      contents.push(
    340        dom.div(
    341          { className: "error no-allocation-stacks" },
    342          L10N.getStr("heapview.noAllocationStacks")
    343        )
    344      );
    345    }
    346 
    347    contents.push(CensusHeader({ diffing }));
    348    contents.push(
    349      Census({
    350        onViewSourceInDebugger,
    351        onViewIndividuals,
    352        diffing,
    353        census,
    354        onExpand: node => this.props.onCensusExpand(census, node),
    355        onCollapse: node => this.props.onCensusCollapse(census, node),
    356        onFocus: node => this.props.onCensusFocus(census, node),
    357      })
    358    );
    359 
    360    return this._renderHeapView(state, ...contents);
    361  }
    362 
    363  _renderTreeMap(state, treeMap) {
    364    return this._renderHeapView(state, TreeMap({ treeMap }));
    365  }
    366 
    367  _renderIndividuals(
    368    state,
    369    individuals,
    370    dominatorTree,
    371    onViewSourceInDebugger
    372  ) {
    373    assert(
    374      individuals.state === individualsState.FETCHED,
    375      "Should have fetched individuals"
    376    );
    377    assert(dominatorTree?.root, "Should have a dominator tree and its root");
    378 
    379    const tree = dom.div(
    380      {
    381        className: "vbox",
    382        style: {
    383          overflowY: "auto",
    384        },
    385      },
    386      IndividualsHeader(),
    387      Individuals({
    388        individuals,
    389        dominatorTree,
    390        onViewSourceInDebugger,
    391        onFocus: this.props.onFocusIndividual,
    392      })
    393    );
    394 
    395    const shortestPaths = ShortestPaths({
    396      graph: individuals.focused ? individuals.focused.shortestPaths : null,
    397    });
    398 
    399    return this._renderHeapView(
    400      state,
    401      dom.div(
    402        { className: "hbox devtools-toolbar" },
    403        dom.label(
    404          { id: "pop-view-button-label" },
    405          dom.button(
    406            {
    407              id: "pop-view-button",
    408              className: "devtools-button",
    409              onClick: this.props.onPopView,
    410            },
    411            L10N.getStr("toolbar.pop-view")
    412          ),
    413          L10N.getStr("toolbar.pop-view.label")
    414        ),
    415        dom.span(
    416          { className: "toolbar-text" },
    417          L10N.getStr("toolbar.viewing-individuals")
    418        )
    419      ),
    420      HSplitBox({
    421        start: tree,
    422        end: shortestPaths,
    423        startWidth: this.props.sizes.shortestPathsSize,
    424        onResize: this.props.onShortestPathsResize,
    425      })
    426    );
    427  }
    428 
    429  _renderDominatorTree(
    430    state,
    431    onViewSourceInDebugger,
    432    dominatorTree,
    433    onLoadMoreSiblings
    434  ) {
    435    const tree = dom.div(
    436      {
    437        className: "vbox",
    438        style: {
    439          overflowY: "auto",
    440        },
    441      },
    442      DominatorTreeHeader(),
    443      DominatorTree({
    444        onViewSourceInDebugger,
    445        dominatorTree,
    446        onLoadMoreSiblings,
    447        onExpand: this.props.onDominatorTreeExpand,
    448        onCollapse: this.props.onDominatorTreeCollapse,
    449        onFocus: this.props.onDominatorTreeFocus,
    450      })
    451    );
    452 
    453    const shortestPaths = ShortestPaths({
    454      graph: dominatorTree.focused ? dominatorTree.focused.shortestPaths : null,
    455    });
    456 
    457    return this._renderHeapView(
    458      state,
    459      HSplitBox({
    460        start: tree,
    461        end: shortestPaths,
    462        startWidth: this.props.sizes.shortestPathsSize,
    463        onResize: this.props.onShortestPathsResize,
    464      })
    465    );
    466  }
    467 
    468  render() {
    469    const {
    470      snapshot,
    471      diffing,
    472      onSnapshotClick,
    473      onLoadMoreSiblings,
    474      onViewSourceInDebugger,
    475      onViewIndividuals,
    476      individuals,
    477      view,
    478    } = this.props;
    479 
    480    if (!diffing && !snapshot && !individuals) {
    481      return this._renderInitial(onSnapshotClick);
    482    }
    483 
    484    const state = getState(view, snapshot, diffing, individuals);
    485    const statusText = getStateStatusText(state, diffing);
    486 
    487    if (shouldDisplayStatus(state, view, snapshot)) {
    488      return this._renderStatus(state, statusText, diffing);
    489    }
    490 
    491    const error = getError(snapshot, diffing, individuals);
    492    if (error) {
    493      return this._renderError(state, statusText, error);
    494    }
    495 
    496    if (view.state === viewState.CENSUS || view.state === viewState.DIFFING) {
    497      const census =
    498        view.state === viewState.CENSUS ? snapshot.census : diffing.census;
    499      if (!census) {
    500        return this._renderStatus(state, statusText, diffing);
    501      }
    502      return this._renderCensus(
    503        state,
    504        census,
    505        diffing,
    506        onViewSourceInDebugger,
    507        onViewIndividuals
    508      );
    509    }
    510 
    511    if (view.state === viewState.TREE_MAP) {
    512      return this._renderTreeMap(state, snapshot.treeMap);
    513    }
    514 
    515    if (view.state === viewState.INDIVIDUALS) {
    516      assert(
    517        individuals.state === individualsState.FETCHED,
    518        "Should have fetched the individuals -- " +
    519          "other states are rendered as statuses"
    520      );
    521      return this._renderIndividuals(
    522        state,
    523        individuals,
    524        individuals.dominatorTree,
    525        onViewSourceInDebugger
    526      );
    527    }
    528 
    529    assert(
    530      view.state === viewState.DOMINATOR_TREE,
    531      "If we aren't in progress, looking at a census, or diffing, then we " +
    532        "must be looking at a dominator tree"
    533    );
    534    assert(!diffing, "Should not have diffing");
    535    assert(snapshot.dominatorTree, "Should have a dominator tree");
    536 
    537    return this._renderDominatorTree(
    538      state,
    539      onViewSourceInDebugger,
    540      snapshot.dominatorTree,
    541      onLoadMoreSiblings
    542    );
    543  }
    544 }
    545 
    546 module.exports = Heap;