Skip to content

Explorer HTML v0.9.1: Security, performance, advanced searches and basic stats - #2088

Open
dbadol wants to merge 58 commits into
BeamMW:masterfrom
dbadol:HTML-Explorer
Open

dbadol wants to merge 58 commits into
BeamMW:masterfrom
dbadol:HTML-Explorer

Conversation

@dbadol

@dbadol dbadol commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor
  • Protect against code injection.
  • Allow advanced column searches (wildcards, regexp, etc.).
  • Add statistics in column tooltips.
  • Make 'Historical Blocks' network-specific.
  • Remember parameters per network.
  • Clear search fields with 'Escape' key.
  • Add 'graph' parameter in URL
  • Add a timeout and spinner to node queries
  • Several performance improvements.
  • Misc. corrections and improvements.

dbadol added 30 commits August 23, 2026 15:13
- Replace some let with const.
- Add 'use strict' in all script blocks.
- Remove duplicated CSS definition.
- Allow reduced scrolling motion.
- Use static node lists in loops where DOM might be modified.
- Use textContent instead of innerHTML where possible.
- Correct undeclared variable.
- Add optional chaining (?.) in case elements might be missing.
- Use firstElementChild instead of firstChild to ignore possible raw text, whitespace, etc.
- Add test in case SVG creation failed.
- Correct non-standard way of using getElementById.
- Add 'g_graphs' global variable and 'graph' URL parameter.
- Move graph display code into functions displayGraph() and removeGraph().
- Open graph collapsible and display graphs if requested in URL.
- Use updateGraphURL() to keep URL up to date with manual graph display.
In standard JS DOM, elements do not have a .setContent property. The correct property is .textContent.
- Tag columns with only numeric (or empty) values.
- Compute (asynchronously) several stats for such columns.
- Display stats as tooltips (for now).
- Recompute after filtering.
- Use type="search" instead of "text" (adds a clear cross on Chrome).
- Use 'input' instead of 'keyup' (triggered also with paste, clear cross, etc.).
- Clear search fields with Escape key also.
- Allow advanced searches in column filters (cf. NOT, wildcards, RegExp, etc.).
- Add function for testing cell content against filter.
- Add info about it in the tooltips.
- Optimize column filtering by moving the query processing outside of the loop on cells.
- Create first a filter function from query, and then apply it to all cells.
- Some let/const cleaning...
- Only update graphs after filtering columns if they are visible.
- Force graphs update when collapsible is opened.
- Use textContent instead of innerText (no layout updates needed).
- Let the browser manage the right moment to adjust the graph size.
- Optimize indexing of rows when filtering tables.
- Update row visibility only when needed.
- Pre-allocate an array with the table cell values.
- Compute statistics on that array.
- To prevent instant filters from slowing down the typing, wait 120ms before applying the column filter.
- Extend global variable 'specialBlocks' to be network-specific.
- Make the 'Historical Blocks' page network-specific.
- Move all network settings and corresponding UI update into a helper function setNetwork().
- 'dataset-numeric' was recently added to 'hdrs' headers for statistics computation.
- That tag is now also used to identify columns which can be displayed as graphs (instead of depending on a manual color definition in the global variables).
- When no default color is defined, a random one is generated.
- Add the network name in the parameters saved in localStorage.
- Move up getPersistentParam() to make it a general helper function for saving/retrieving parameters from localStorage or default.
- Move code out of setupGraph() to make another helper function loadGraphColors() for saving/retrieving graph colors.
- Refactor/streamline how global parameters are updated in showPage().
- Also: Move the declaration of 'g_graph' up in the code to avoid any error if used while still undeclared.
- Apply the same logic as for 'asset' without id (or with id=0).
Protection against code injection from node responses, URL parameters or localStorage:
- Add optimized helper function to escape HTML characters.
- Apply it everywhere data is inserted in HTML (with care about possible double calls).
- Adjust some usage of the MakeCell* helpers.
- Optimize formatTimestamp() by removing its regexp.
- Add sanitization for all 'hdrs' parameters.
- Sanitize all colors into hex format with convertColorToHex().
- Move Collators outside of the sortTable() function."
- Use createDocumentFragment() to update the layout only once, at the end.
Use the isNumeric tag to make sorting most columns faster.
- Extend column scanning to typing them as 'numeric', 'empty' or 'text'.
- Store initial row or tBody order (will be used in the future for better sorting).
- Remove a useless fallback.
- Use the column data-type to select sorting method.
- Sort directly in the requested direction (it saves one pass).
- Sort hex hashes similarly to text (it removes a collator and the RegExp testing).
- Don't sort empty columns.
- Default initial sort: A-Z for text columns and largest/newest first for numeric columns.
Use the column data-type to make filtering a bit faster:
- No need to use parseCellNumericValue() anymore.
- Optimize matcher() functions for numeric or text columns.
- Detect when no row is visible anymore.
- Revert back matcher() to using only one parameter.
@dbadol

dbadol commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Review of #2088 (Explorer HTML v0.9.1)

Line numbers are for explorer/htm/BeamExplorer.htm at the PR head, commit 97cbf10.

I tested points 1–4 and 6 in Chrome, running the PR build against the live mainnet and dappnet nodes. Points 5 and 7–9 come from reading the code only.

1. URLs for any network except mainnet lose their saved-setting parameters

L3668–3674, together with L3403 and L3792–3797.

g_network now starts as 'mainnet' (L3403). When loadPageFromURL() runs on a dappnet or testnet URL, showPage() treats it as a network switch. The loop at L3671–3673 then deletes cols, nMax, nMaxOps, nMaxTxs and dh from the parameters before they are read.

  • Repro: open ?network=dappnet&type=hdrs&cols=THk&nMax=8. You get the default columns and 100 rows, and the URL is rewritten to cols=THdfkioyzp&nMax=100.
  • The same URL on v0.9 works, and mainnet URLs still work.
  • Back/forward across a network change goes through the same path.
  • Fix: only reset these parameters when the user switches network from the dropdown, not when the page loads from a URL.

2. Node data can still inject code through the javascript: links

L2122–2126; callers at L2158–2160, L2166–2170, L2172–2175.

UrlSelf() escapes only & and ". Browsers percent-decode javascript: URLs before running them, so %22 closes the JSON string. escapeHtml() is applied to the link text in MakeBlock, MakeAsset and MakeCid, but the href still receives the raw value.

  • Repro: pass the contract id %22});window.__pwn=1;// to MakeCid(). Clicking the link sets window.__pwn = 1.
  • Fix: stop building javascript: URLs. Put the parameters in a data-* attribute and use one delegated click handler, or percent-encode % as well.

3. The graph wrappers throw on pages that have no graph

L4303–4305 and L4322–4324.

On any page except block headers, #graphCollapsible is null. The guard graphCollapsible && !graphCollapsible.open is then false, so the code goes on to updateGraphDataX(), which fails at L4629.

  • Repro: open Block Headers, then open Smart Contracts in the same tab, turn on the filters, type in a search box, and click a column header to sort.
  • Console:
    • Cannot read properties of null (reading 'tBodies')
    • Cannot convert undefined or null to object (from Object.assign at L4323)
  • Fix: change the guard to if (!graphCollapsible || !graphCollapsible.open) return; and add the same check to the sortTable wrapper.

4. filterTable and sortTable get wrapped again on every Block Headers render

L4300–4330, called from DisplayHdrs() at L2882.

setupGraph() wraps whatever filterTable and sortTable currently are, and those are already wrapped from the previous visit.

  • Measured: after Block Headers rendered 4 times, one keystroke in a filter ran updateGraphDataX() 4 times. The count keeps growing as you navigate, which works against the "improve performance" item.
  • Fix: wrap once when the script loads, or call the graph update directly from filterTable and sortTable.

5. Everyone's saved settings reset on upgrade

L3541–3556 and L3559–3575.

The localStorage keys changed from g_cols, g_colsAll, g_nMax, g_nMaxOps, g_nMaxTxs, g_dh and g_graph_colors to g_<network>_*. Nothing reads the old keys, so v0.9 users lose their columns, row counts and graph colours, and the old keys stay behind as orphans.

  • Fix: on first load, copy the old keys into g_mainnet_* once and delete them.

6. Asset names containing & are cut off

L3892–3896.

The regex N=([^;]*) runs on innerHTML, where & has become &amp;. The match stops at that entity's ;.

  • Repro: STD:SCH_VER=1;N=Tom & Jerry;SN=TJ renders as N=Tom &; Jerry, and .metadataAssetName contains only Tom &.
  • This bug is older than the PR, but the PR edits this function.
  • Fix: match on textContent, or match up to the next ;XX=.

7. adj=0 changed direction

L3445–3448.

sanePlusMinusOne returns Number(s) >= 0 ? 1 : -1. The sanitizer it replaced used > 0, so adj=0 now searches forward (+1) instead of backward (-1). This matters only if something passes adj=0; I didn't find a caller. Either keep the old mapping or reject 0.

8. The colour "preservation" in setupGraph() does nothing

L4267 vs L4296 and L3560.

color: g_graph.color || {} is overwritten a few lines later: loadGraphColors() (called at L4296) sets g_graph.color = {} at L3560. As a result, random colours for columns without a default colour are regenerated on every render. Either remove the carry-over, or have loadGraphColors() merge into the existing object.

9. Duplicated code

L3437–3444; L4340 and L4472.

  • sanePositiveInteger and saneStrictPositiveInteger differ only in < 0 vs <= 0.
  • The check closest('th').dataset.numeric === 'true' || code === 'T' appears in both setupGraph() and displayGraph().
  • A saneInteger(min) factory and an isGraphable(header, code) helper would each leave one copy to maintain.

@dbadol

dbadol commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Corrections

Remark: Some 9 new commits were added after commit 97cbf10 and were not included in the first review above.

  1. URL network loses their parameters: Corrected with a new parameter.
  2. Code injection in javascript: links: Corrected using encodeURIComponent in UrlSelf().
  3. Run updateGraphDataX() even when #graphCollapsible is null: Corrected with a test on graphCollapsible.
  4. Continuous wrapping of filterTable and sortTable: Corrected with a flag attached to the functions.
  5. Everyone's saved settings reset on upgrade: Intentional. It’s not a big deal.
  6. Asset names containing & are cut off: Corrected with a better regexp
  7. adj=0 changed direction: Intentional. 0 should be considered same as default, which is 1.
  8. Color "preservation" in setupGraph(): Corrected.
  9. Duplicated code: It’s minimal. We can leave it as it is.

@dbadol

dbadol commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Copy of the results of the second review:

Review of #2088, round 2

Line numbers are for explorer/htm/BeamExplorer.htm at the PR head, commit d094b73. This round covers all 15 commits since 97cbf10.

Points 1–4 below were reproduced in Chrome against the live mainnet and dappnet nodes. Points 5 and 6 come from reading the code.

Status of round 1

# Finding Status
1 Non-mainnet URLs lose their parameters Fixed. ?network=dappnet&type=hdrs&cols=THk&nMax=8 now loads with cols=THk, nMax=8.
2 Code injection in javascript: links Not fixed, see point 1 below.
3 Graph wrappers throw on pages without a graph Fixed. Filtering and sorting on Smart Contracts after visiting Block Headers: no console errors.
4 Wrappers stack up on every visit Fixed. After 3 renders, one filter keystroke runs updateGraphDataX() once.
5 Saved settings reset on upgrade Declined; your call.
6 Asset names with & cut off Fixed. N=Tom & Jerry now renders in full.
7 adj=0 changed direction Declined; fine given 0 means default.
8 Colour "preservation" was dead code The fix introduced a new bug, see point 2 below.
9 Duplicated code Declined; your call.

1. UrlSelf() is still injectable, now through '

L2123–2130.

The browser percent-decodes a javascript: URL once before running it. So the %27 added at L2127 is turned back into ' before the JS parser sees it, and it closes the '...' string literal. The decodeURIComponent() inside the link then decodes a second time.

  • Repro: pass the contract id '+(window.__pwn=1)+' to MakeCid() and click the link. window.__pwn becomes 1, and showPage still gets called with {id: "1"}, so nothing looks wrong to the user.
  • Side effect of the double decode: an id containing a literal %41 reaches showPage() as A (ab%41cd becomes abAcd).
  • Fix: stop building javascript: URLs. Render <a href="?network=…&type=…&id=…" data-page="contract" data-params="…"> with the params HTML-escaped, and route clicks through one delegated listener that calls showPage(). That also gives working middle-click and "open in new tab".

2. Graph colours now leak from one network to another

L3563–3567, together with L4273.

loadGraphColors() keeps whatever is already in g_graph.color and only fills codes that are missing. setupGraph() carries g_graph.color over at L4273, so colours from the previous network stay in memory after a switch. That includes custom colours and the PoW defaults when switching to a PoS network.

  • Repro: save {k: '#ff0000'} as g_mainnet_graph_colors, open Block Headers on mainnet, then switch to dappnet, which has no saved colours. g_graph.color.k is still #ff0000; the dappnet default is #ff0080.
  • Fix: carry over only the random colours. For example, keep a separate g_graph.randomColor map that survives re-renders, and let loadGraphColors() rebuild g_graph.color from the defaults, the stored colours for this network, and then randomColor.

3. Sorting while the graph is closed leaves the X axis invalid

L4321–4327 (early return at L4326).

The new early return also skips the part of the wrapper that sets g_graph.xcode = 'row' and disables the X-axis options that no longer match the sort order. toggleGraph() (L4542) redraws the data when the graph is reopened, but it doesn't redo that check.

  • Repro: open the graph and set the X axis to Timestamp. Close the graph, sort by Fees, then reopen it. g_graph.xcode is still T, the Timestamp option is still enabled, and the X values are out of order (1790753348, 1790753168, 1790757053, …), so the plot zig-zags.
  • Fix: in the wrapper, do the xcode reset and the option enable/disable before the early return, and skip only the redraw.

4. The new early exit in filterTable() never fires on single-body tables

L5842 and L5893–5894.

results_rows is filled with 1, and the loop only writes indices from 1 up, because index 0 is the header row. results_rows[0] therefore stays 1, and !results_rows.includes(1) is never true. The multi-tbody branch works.

  • Fix: !results_rows.includes(1, 1).

5. Sorting an "empty" column leaves a misleading header

L5661–5676, together with L5687.

sortTable() clears the asc/desc class from every header and marks the clicked column before it reaches if (colType === 'empty') return col;. If the table was sorted by another column, the rows stay in that order, but the header now shows the empty column as the sorted one.

  • Fix: return for 'empty' before the header classes are touched.

6. Leftover g_collatorAlphabetical comment

L5630–5633: after "More natural default sorting", g_collatorAlphabetical is only a commented-out line between the comments describing the two collators. Remove the line and its description.


Checked and not an issue: the numeric sort does Number() on the joined values of a multi-row tbody, which would give NaN if a group had several numeric values. On the contract call tables I checked (Nephrite, 100 calls), the only numeric column is Height, and its sub-call cells are empty.

dbadol added 6 commits October 3, 2026 19:05
- Reset all colors in loadGraphColors(), so that they can be redefined from default, localStorage or random.
- Replace random colors by pseudo-random ones (i.e. deterministic based on column code string) so there is no need to .carry them over.
Moved the early return *after* the update of the X-axis options.
- Move the early return in sortTable() for empty columns.
- Don't show sort icon/option for empty columns.
Better protection against code injection (and possibility to open links in new tab/window!):
- Replace `javascript:` links back to normal self-links `href="?params=..."`.
- Update `UrlSelf()` to build such safe self-links.
- Intercept left mouse clicks to route self-links through showPage().
- Update logo and menu links when network changes.
- Use `data-retry=true` to tag links which should replace browsing history (e.g. after node query errors).
Automatic display of the "What's new" popup the first time a new version is loaded.
@dbadol

dbadol commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

New set of corrections:

1. UrlSelf() is still injectable

Allowing middle-click and "open in new tab" capability is a nice feature indeed! I implemented the suggested approach: links with href="?..." and a click listener to route through showPage().

2. Graph colours leaking

I used a simpler solution: A deterministic color generator, based on the column code string. Colors can then be reset everywhere: start with default and saved ones, and re-compute 'pseudo-random' ones. Nothing to carry over.

3. Sorting while the graph is closed leaves the X axis invalid

Fixed. Moved the early return.

4. New early exit in filterTable() never fires on single-body tables

Fixed. Ignore header row.

5. Sorting an "empty" column leaves a misleading header

Fixed. Moved the early return, and removed the sorting icon/option for empty columns all together.

6. Leftover g_collatorAlphabetical comment

Intentional. Will be used later.

Additional commit

I added a feature to display the "What's new" popup once for each version.

dbadol added 3 commits October 6, 2026 23:32
It's just for consistency:
- `window` if it relates to browser, viewport, storage, network, or URL.
- `document` when it relates to HTML tags, elements, CSS classes.
@dbadol

dbadol commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

New round of corrections:

  1. Retry links don't loose all the URL parameters anymore.
  2. Empty columns can now be displayed as graph (constant zero value) but not for the X-Axis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant