tor-browser

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

ConsoleTable.js (7099B)


      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
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 "use strict";
      5 
      6 const {
      7  Component,
      8  createFactory,
      9 } = require("resource://devtools/client/shared/vendor/react.mjs");
     10 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     11 const {
     12  getArrayTypeNames,
     13 } = require("resource://devtools/shared/webconsole/messages.js");
     14 const {
     15  l10n,
     16  getDescriptorValue,
     17 } = require("resource://devtools/client/webconsole/utils/messages.js");
     18 loader.lazyGetter(this, "MODE", function () {
     19  return ChromeUtils.importESModule(
     20    "resource://devtools/client/shared/components/reps/index.mjs",
     21    { global: "current" }
     22  ).MODE;
     23 });
     24 
     25 const GripMessageBody = createFactory(
     26  require("resource://devtools/client/webconsole/components/Output/GripMessageBody.js")
     27 );
     28 
     29 loader.lazyRequireGetter(
     30  this,
     31  "PropTypes",
     32  "resource://devtools/client/shared/vendor/react-prop-types.js"
     33 );
     34 
     35 const TABLE_ROW_MAX_ITEMS = 1000;
     36 // Match Chrome max column number.
     37 const TABLE_COLUMN_MAX_ITEMS = 21;
     38 
     39 class ConsoleTable extends Component {
     40  static get propTypes() {
     41    return {
     42      dispatch: PropTypes.func.isRequired,
     43      parameters: PropTypes.array.isRequired,
     44      serviceContainer: PropTypes.object.isRequired,
     45      id: PropTypes.string.isRequired,
     46      setExpanded: PropTypes.func,
     47    };
     48  }
     49 
     50  constructor(props) {
     51    super(props);
     52    this.getHeaders = this.getHeaders.bind(this);
     53    this.getRows = this.getRows.bind(this);
     54  }
     55 
     56  getHeaders(columns) {
     57    const headerItems = [];
     58    columns.forEach((value, key) =>
     59      headerItems.push(
     60        dom.th(
     61          {
     62            key,
     63            title: value,
     64          },
     65          value
     66        )
     67      )
     68    );
     69    return dom.thead({}, dom.tr({}, headerItems));
     70  }
     71 
     72  getRows(columns, items) {
     73    const { dispatch, serviceContainer, setExpanded } = this.props;
     74 
     75    const rows = [];
     76    items.forEach(item => {
     77      const cells = [];
     78 
     79      columns.forEach((value, key) => {
     80        const cellValue = item[key];
     81        const cellContent =
     82          typeof cellValue === "undefined"
     83            ? ""
     84            : GripMessageBody({
     85                grip: cellValue,
     86                mode: MODE.SHORT,
     87                useQuotes: false,
     88                serviceContainer,
     89                dispatch,
     90                setExpanded,
     91              });
     92 
     93        cells.push(
     94          dom.td(
     95            {
     96              key,
     97            },
     98            cellContent
     99          )
    100        );
    101      });
    102      rows.push(dom.tr({}, cells));
    103    });
    104    return dom.tbody({}, rows);
    105  }
    106 
    107  render() {
    108    const { parameters } = this.props;
    109    const { valueGrip, headersGrip } = getValueAndHeadersGrip(parameters);
    110 
    111    const headers = headersGrip?.preview ? headersGrip.preview.items : null;
    112 
    113    const data = valueGrip?.ownProperties;
    114 
    115    // if we don't have any data, don't show anything.
    116    if (!data) {
    117      return null;
    118    }
    119 
    120    const dataType = getParametersDataType(parameters);
    121    const { columns, items } = getTableItems(data, dataType, headers);
    122 
    123    // We need to wrap the <table> in a div so we can have the max-height set properly
    124    // without changing the table display.
    125    return dom.div(
    126      { className: "consoletable-wrapper" },
    127      dom.table(
    128        {
    129          className: "consoletable",
    130        },
    131        this.getHeaders(columns),
    132        this.getRows(columns, items)
    133      )
    134    );
    135  }
    136 }
    137 
    138 function getValueAndHeadersGrip(parameters) {
    139  const [valueFront, headersFront] = parameters;
    140 
    141  const headersGrip = headersFront?.getGrip
    142    ? headersFront.getGrip()
    143    : headersFront;
    144 
    145  const valueGrip = valueFront?.getGrip ? valueFront.getGrip() : valueFront;
    146 
    147  return { valueGrip, headersGrip };
    148 }
    149 
    150 function getParametersDataType(parameters = null) {
    151  if (!Array.isArray(parameters) || parameters.length === 0) {
    152    return null;
    153  }
    154  const [firstParam] = parameters;
    155  if (!firstParam || !firstParam.getGrip) {
    156    return null;
    157  }
    158  const grip = firstParam.getGrip();
    159  return grip.class;
    160 }
    161 
    162 const INDEX_NAME = "_index";
    163 const VALUE_NAME = "_value";
    164 
    165 function getNamedIndexes(type) {
    166  return {
    167    [INDEX_NAME]: getArrayTypeNames().concat("Object").includes(type)
    168      ? l10n.getStr("table.index")
    169      : l10n.getStr("table.iterationIndex"),
    170    [VALUE_NAME]: l10n.getStr("table.value"),
    171    key: l10n.getStr("table.key"),
    172  };
    173 }
    174 
    175 function hasValidCustomHeaders(headers) {
    176  return (
    177    Array.isArray(headers) &&
    178    headers.every(
    179      header => typeof header === "string" || Number.isInteger(Number(header))
    180    )
    181  );
    182 }
    183 
    184 function getTableItems(data = {}, type, headers = null) {
    185  const namedIndexes = getNamedIndexes(type);
    186 
    187  let columns = new Map();
    188  const items = [];
    189 
    190  const addItem = function (item) {
    191    items.push(item);
    192    Object.keys(item).forEach(key => addColumn(key));
    193  };
    194 
    195  const validCustomHeaders = hasValidCustomHeaders(headers);
    196 
    197  const addColumn = function (columnIndex) {
    198    const columnExists = columns.has(columnIndex);
    199    const hasMaxColumns = columns.size == TABLE_COLUMN_MAX_ITEMS;
    200 
    201    if (
    202      !columnExists &&
    203      !hasMaxColumns &&
    204      (!validCustomHeaders ||
    205        headers.includes(columnIndex) ||
    206        columnIndex === INDEX_NAME)
    207    ) {
    208      columns.set(columnIndex, namedIndexes[columnIndex] || columnIndex);
    209    }
    210  };
    211 
    212  for (let [index, property] of Object.entries(data)) {
    213    if (type !== "Object" && index == parseInt(index, 10)) {
    214      index = parseInt(index, 10);
    215    }
    216 
    217    const item = {
    218      [INDEX_NAME]: index,
    219    };
    220 
    221    const propertyValue = getDescriptorValue(property);
    222    const propertyValueGrip = propertyValue?.getGrip
    223      ? propertyValue.getGrip()
    224      : propertyValue;
    225 
    226    if (propertyValueGrip?.ownProperties) {
    227      const entries = propertyValueGrip.ownProperties;
    228      for (const [key, entry] of Object.entries(entries)) {
    229        item[key] = getDescriptorValue(entry);
    230      }
    231    } else if (
    232      propertyValueGrip?.preview &&
    233      (type === "Map" || type === "WeakMap")
    234    ) {
    235      item.key = propertyValueGrip.preview.key;
    236      item[VALUE_NAME] = propertyValueGrip.preview.value;
    237    } else {
    238      item[VALUE_NAME] = propertyValue;
    239    }
    240 
    241    addItem(item);
    242 
    243    if (items.length === TABLE_ROW_MAX_ITEMS) {
    244      break;
    245    }
    246  }
    247 
    248  // Some headers might not be present in the items, so we make sure to
    249  // return all the headers set by the user.
    250  if (validCustomHeaders) {
    251    headers.forEach(header => addColumn(header));
    252  }
    253 
    254  // We want to always have the index column first
    255  if (columns.has(INDEX_NAME)) {
    256    const index = columns.get(INDEX_NAME);
    257    columns.delete(INDEX_NAME);
    258    columns = new Map([[INDEX_NAME, index], ...columns.entries()]);
    259  }
    260 
    261  // We want to always have the values column last
    262  if (columns.has(VALUE_NAME)) {
    263    const index = columns.get(VALUE_NAME);
    264    columns.delete(VALUE_NAME);
    265    columns.set(VALUE_NAME, index);
    266  }
    267 
    268  return {
    269    columns,
    270    items,
    271  };
    272 }
    273 
    274 module.exports = ConsoleTable;