WebGraph

What the browser hides

The six ways an element can be hidden, the site that motivated each rule, and what stays in anyway.

A plain fetch has a tree and no idea what a reader sees. The browser knows, and the collector (fetch/js/collect.js) writes it onto every element as data-wg-hidden=<kind> before the DOM is serialised. The value says how the element is hidden, because the kinds mean different things: "not on the page", "on the page but not yet shown", and "on the page and about to appear".

The kinds

kindhow the collector decideswhat the parser does
displaycomputed display: noneabsent — dropped, and kept out of the union
visibilitycomputed visibility: hiddenabsent, as display
clippeda box of 1 × 1 px or less with overflow: hidden, or clip: rect(0 0 0 0), or clip-path: inset(50%)screen-reader-only; dropped unless include_hidden_text
overflowan ancestor with overflow: hidden/clip whose own box has no height or no widthin the flow; kept, unmeasured, after its heading
opacitycomputed opacity: 0measured and kept where it sits, unless it is a bare <button> or a twin of a visible sibling
offscreena box lying entirely at negative page coordinates, not inside a scroll containerabsent, as display

display: none — php.net. The manual's hidden table of contents (nav#trick, about a hundred links) and cppreference's hover menus were rightly absent from the rendered document and then put straight back by the union from the static copy. hidden_matter now records every string the browser hid so the merge reads a static-only block as hidden, not lost: php.net 330 → 233 blocks, cppreference 254 → 135, nasa.gov's mega-menu 299 → 153 (PR #65).

visibility: hidden. Marked and treated as display is. No motivating site is recorded for it; it is the second way CSS removes an element from view.

clipped — linear.app. The screen-reader-only convention clips an element to a one-pixel box. Class names catch most of it on a plain fetch (sr-only, visually-hidden, screen-reader-text, …), but linear.app's <h1> carries a second copy of the headline in a CSS-module class the name rule cannot know (Fzcv4W_visuallyHidden); the measured box says what the class means (PR #59).

overflow — apple.com/iphone. An accordion tray is overflow: hidden at height 0, and its paragraphs keep their natural boxes underneath the next item's heading. Ordered by those boxes, the trays zip with the headings. The text is real but has no position on the page, so it is marked overflow, kept, and slotted after its heading like any unmeasured block (PR #60). Only an ancestor that is itself collapsed counts: a carousel clips horizontally at full height and its later cards are reachable by scrolling, and their boxes say, correctly, that they come later.

opacity — apple.com/iphone again. Content faded in by an entrance animation starts at opacity 0. It is on the page and about to be shown, so it is marked but still measured: the "iPhone 18 Pro" hero, when unmeasured, was slotted after the other hero's image and before its heading (PR #60). Opacity is not an absent kind — not in hidden_matter, not in the group pass of the twin rule. Two things still go: a standalone <button> the browser is not showing (sphinx-copybutton's "Copy", drawn at opacity 0 until hovered, came out as a one-word paragraph between "For example:" and the example on every docs.python.org page), and an opacity-0 element whose visible sibling says the same thing.

offscreen — vtu.ac.in. The owner's first live whole-site test carries about sixty injected gambling links on every page, each in <div style="position:absolute; left:-20914565266523px">. The browser reports a box for them — twenty trillion pixels to the left — and the collector read "has a box" as "visible", so the whole-page Markdown opened with sixty lines of spam before "About VTU". A box entirely at negative page coordinates is now hidden the way display: none is (PR #88): vtu.ac.in/about-vtu 202 → 148 blocks, zero spam tokens, opening with "Online Fee Payment" as the screen does.

Not inside a scroll container. w3schools' fixed sidebar and php.net's manual index scroll themselves to the current entry, and the entries above it have negative boxes while being one wheel-tick away. The first version of the rule dropped a heading on each. Only the document's own negative space is unreachable; an ancestor with overflow: auto or scroll exempts everything under it.

Hidden but openable

Hidden content that a control opens is content: an accordion tray, an inactive tab, a "Show more" body. _drop_unreachable_hidden keeps a display/visibility/offscreen container when something names it (aria-controls, data-bs-target, data-tab, for, an href="#id"), or the panel says what it is (role="tabpanel", menu, listbox, tree), or it sits in a <details>. Those stay, unmeasured, where the source puts them.

Hidden content nothing reaches is different: a ticker's source, a widget for another breakpoint, a template. karnataka.gov.in hides a 3,144-word "Recent Govt Announcements" div beside a 726-word page and nothing opens it (PR #62). A reference to the page's main region opens nothing inside it — MDN's skip link href="#content" names <main id="content">, an ancestor of everything, and made every hidden copy under it "reachable" (PR #74).

Dialogs are dropped. A closed <dialog>, or a role="dialog" that is hidden by the renderer's measure, aria-hidden="true" or the hidden attribute, is not collapsed content in the sense an accordion is; it is a different screen. karnataka.gov.in's nine Bootstrap modals — Privacy Policy, Terms, Help, Site Map — were 2,100 words under display: none, three times the words the page shows (PR #62; the page went 9,270 → 726 words). An open dialog stays and is judged like anything else.

Hidden twins

Responsive markup renders one label twice — <span class="md:hidden">NEW</span> <span class="hidden md:block">NEW</span> — and the browser shows one. Both are inline, so they share a block, and the block read "NEW NEW". _drop_hidden_twins removes a hidden element whose visible sibling says the same thing, compared without whitespace; a first pass matches a parent's hidden children as a group against its shown ones, because linear.app's headline is held three ways and no single hidden span has a single visible twin (PR #59). A hidden element saying something of its own stays.

include_hidden_text

The default output is what a sighted reader sees. Measured first: the whole-page text is 0.99–1.00 word-complete against Chromium's body text on 21 live pages, and the missing 1% is screen-reader-only labels, skip links and [edit] controls (PR #57). A caller who wants every string passes include_hidden_text (--include-hidden-text on the CLI). It keeps exactly three things: clipped elements, inline-styled offscreen elements, and the sr-only / editsection branch of the class rule. Closed dialogs, hidden twins, unreachable trays and the union's hidden_matter are unaffected — that is not text a browser holds, it is text nobody was shown.