Back to Knowledge Hub
Fingerprint

getClientRects Fingerprinting: DOMRect Consistency

Read CreepJS DOMRect checks without confusing empty geometry, fragmented text, transforms or missing results with a device identity.

BotBrowser Team

Documentation

Want the structured docs for Fingerprint?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

getClientRects fingerprinting examines browser layout geometry, not Client Hints request headers. A rectangle can change because the selected box, text fragment or coordinate context changed. BotBrowser documents deterministic ClientRects noise, disabled by default, and a seed for reproducible font metrics; those controls do not establish that every geometry path agrees or that CreepJS will accept a result. A useful DOMRect report separates the measured object, the API and the exact consistency rule before assigning a cause.

Watch the video

This English-language video follows the same question as the article: four CSSOM View geometry calls return different shapes, so a fragment count, an enclosure and a selected range cannot be read as one value. It shows the three report states (absent, empty list, zero-dimension rectangle), the three bounding-reduction exits, and two recorded Console sessions on a controlled same-origin fixture. Two native expressions were typed by hand, with the fixture panel printing the third reading. It does not show site collection or a third-party verdict, and a geometry difference does not identify a device, a person or a modification mechanism. The audio is English.

TL;DR

  • Element.getClientRects() returns a list of box fragments; getBoundingClientRect() reduces that list to one rectangle, with separate empty and zero-dimension cases. List length and bounding dimensions answer different questions.
  • Range geometry can describe selected text fragments. It is not interchangeable with an element's entire border box.
  • CreepJS revision 10aa6724cd33a1015db1574211890518cd04f0cc records four geometry arrays and applies several fixture-specific checks, not one universal device test.
  • An absent DOMRect report, an empty fragment list and a rectangle containing zeros are separate states.
  • A CreepJS lied flag records that revision's consistency assessment. It does not establish who changed a value, which device produced it or what an external site will decide.

Contents

Element and Range each produce a fragment list or bounding rectangle; an absent report, empty list and zero rectangle remain distinct.

Four geometry surfaces

The CSSOM View specification defines Element.getClientRects() around an element's CSS boxes. A multiline inline element can produce several fragments rather than one continuous rectangle. The list retains the distinction between those fragments, including zero-sized ones. An element without an associated CSS box produces an empty list. Consequently, the expression “the ClientRects value” is incomplete: it might refer to a list, one selected member, a derived union or a digest of several fixtures.

Element.getBoundingClientRect() instead reduces geometry to one rectangle. The specification distinguishes an empty list, a list whose rectangles all have zero width or height, and the remaining union case. The first returns a zero rectangle; the second returns the first rectangle. Otherwise, the union includes rectangles whose height or width is not zero: the algorithm uses an OR condition. That final branch must not erase the preceding exceptions. A rectangle at a nonzero position with zero height is not the same observation as no fragments at all.

MDN describes the element bounding rectangle as including padding and border, with coordinates relative to the viewport. A comparison with a CSS declaration such as width: 100px must account for box sizing rather than expecting the declared width to equal every reported width. Content width and the enclosing border box are different quantities. A report that records only a rounded width loses both the API choice and the box model behind it.

Range.getClientRects() concerns a selected portion of a document. The corresponding bounding method reduces that range's list, also distinguishing empty, all-zero-dimension and union cases. A selected text substring and its containing element need not have equal bounds: one refers to the selected text, while the other can include the whole box. The text-measurement analysis covers the separate Canvas measureText() surface; its TextMetrics fields are not DOMRect fragment lists.

APIReturned shapeComparison that preserves its meaning
Element.getClientRects()A DOMRectList of box fragmentsCompare the same element and fragment positions
Element.getBoundingClientRect()One DOMRect from the bounding algorithmCompare the same element and viewport context
Range.getClientRects()A list for the selected rangePreserve selection boundaries and text layout
Range.getBoundingClientRect()One rectangle from the range-list reductionPreserve the range and the empty/zero branches

The rectangle interfaces expose x, y, width, height, top, right, bottom and left. A collector can serialize those fields, but serialization does not make the underlying objects equivalent. MDN also distinguishes the read-only list interface from a JavaScript array. Converting a list into an array changes its container, not the layout operation that created the entries. A digest of that array still cannot explain a changed fragment by itself.

What the pinned CreepJS checks compare

The public DOMRect module at the pinned revision copies the eight fields into plain objects and returns four separately named arrays: elementClientRects, elementBoundingClientRect, rangeClientRects and rangeBoundingClientRect. For the two list APIs, its fixture collection reads the first list member, not every rectangle of an arbitrary multiline page. That source choice matters: a displayed group digest is not evidence that every fragment of every element was examined.

The same module contains a preferred-rectangle helper. It selects among the element list, element bounding rectangle, range list and range bounding rectangle according to previously recorded API issues. This helper is separate from the four-array collection. Describing it as “CreepJS always uses the bounding rectangle” would erase that branch structure. Conversely, the helper's alternatives do not mean that all four APIs should return identical rectangles for every possible selection.

The movement check captures a fixture's initial top, applies a shift class, captures the shifted value, removes the class and reads again. Its comparison concerns restoration relative to that shifted measurement. It is not a general rule that a rectangle must remain unchanged after a layout mutation. A failed restoration check and a changed initial top describe different comparisons; reporting only “position changed” cannot distinguish them.

The arithmetic check examines relationships among opposite edges, dimensions and origin fields in collected element rectangles. A second check compares the left and right edges of two particular fixtures intended to match. These are different predicates: internal field arithmetic can be consistent while two fixtures differ. The CreepJS signal-group analysis explains why a group-level assessment must retain its scope rather than become a claim about a person or every browser API.

Source-defined checkWhat is comparedWhat its failure alone does not establish
Shift and restorationInitial, shifted and restored fixture positionThe cause of the position difference
Rectangle arithmeticEdge, origin and dimension relationshipsWhich software layer supplied a value
Equal fixturesSelected edges of two specific elementsEquality of unrelated page elements
Rotated fixtureA source-defined expectation with engine/DPR conditionsA universal geometry signature for all browsers
Ghost fixtureWhether its eight recorded fields are all zeroThat every invisible element must have zero geometry

The rotated-fixture branch depends on the revision's engine classification and, in one branch, devicePixelRatio. Its expected values are implementation-specific source assumptions, not a standard conformance table. Copying a known digest into a report would supply neither the underlying rectangle nor evidence that the original layout was measured. The defensible statement is that this revision compares its fixture against a bounded expectation, not that a mismatch proves an operating system.

The lied value can also begin with previously recorded concerns about the four geometry APIs or String.fromCodePoint, before these geometry checks execute. A final true value therefore does not identify which local predicate first changed it. The module catches failures, logs an unsuccessful test and returns no result; its rendering function separately displays a blocked state when the report is absent. These paths make “no report” distinct from a successfully collected array containing zeros. No CreepJS runtime result is claimed here.

Layout changes that alter a comparison

Viewport-relative coordinates change when content moves relative to that viewport. MDN explicitly notes scrolling when explaining getBoundingClientRect(). A before/after comparison that scrolls the document has changed an input even when the element's CSS width is untouched. Adding scroll offsets can support a document-coordinate calculation, but it must be identified as a derived value rather than silently compared with the original viewport coordinate.

Line wrapping changes fragment boundaries. A text span measured in a narrow column can have a different DOMRectList.length from the same span in a wider column. The enclosing rectangle then summarizes those fragments and may include space that is not occupied by any one line of text. Matching only the enclosure can hide a fragmentation change; matching only the first fragment can miss every later line. Neither comparison establishes that a font was replaced.

CSS transforms affect the rectangles described by the geometry APIs. A rotated fixture should therefore be compared with the same rotation and reference origin, not an untransformed CSS size. A bounding rectangle remains an enclosure rather than a traced polygon of the visible shape. This is why a rotation-specific detector branch needs its own fixture context. The measured width cannot be read as the length of a rotated edge without additional geometry.

Range selection introduces another input: the selected nodes or text offsets. Changing a selection from an entire element to a substring can legitimately alter the range list while leaving the element's own box unchanged. The CSSOM View range algorithm also treats selected text and element boxes differently. A report that names Range.getClientRects() but omits its selection cannot support a precise comparison with another capture of “the same page.”

Font configuration belongs in the layout context, but a changed rectangle is not a font-name lookup. Font availability, fallback, text content, container width and styles can all be relevant to the selected fixture. The product's font measurement documentation documents ClientRects controls; it does not supply a reference DOMRect for every text, selection and layout. A seed addresses reproducibility of the documented noise, not every source of page-layout variation.

The CreepJS ghost check must also remain attached to its fixture. “Not visible to a person” includes several possible rendering states and does not define one CSS box model. An offscreen element may still participate in layout; an element without an associated box follows another API path. Turning the ghost check into a universal invisible-element rule would replace the source's concrete condition with a much broader unsupported claim.

A state matrix for DOMRect reports

The first interpretation decision is whether geometry was obtained at all. A collector exception, an empty fragment list and a zero-sized returned rectangle require separate record values. Using a zero rectangle as a placeholder for every failure destroys that distinction and can make a downstream consistency check appear to have examined data that was never collected. null chosen by a reporting application is also not a native DOMRectList result.

Observed stateBounded interpretationNext comparison input
Collector returned no reportThe report is absent; a cause is not establishedCapture phase and available error category
List exists with length === 0No fragments were returned for that API callElement box association or range selection context
A rectangle has zero width or heightGeometry exists with a degenerate dimensionAPI, position and the other dimension
List length changesThe captured fragment structure differsSame text, width, selection and layout timing
Arithmetic predicate failsRecorded fields fail that exact comparisonOriginal fields and predicate name
Only a group digest changesSerialized inputs differed somewhereNamed array, fixture and fields before attribution

These rows are decision cases derived from the sources, not measured browser outcomes. For example, a hypothetical empty list followed by a zero bounding rectangle can be consistent with the specified empty-list reduction. It does not show that a detector caught modification. Conversely, a report that claims a successful list read but stores no list length cannot distinguish this case from a missing value inserted by its own serializer.

A second hypothetical case is a multiline span with more fragments after its container narrows. The useful finding is a changed layout input with altered fragmentation. No specific width or height is predicted here, and no device is identified. A third case holds the element fixed but changes the selected substring: the range result changes while an element-level comparison can remain stable. Each case preserves which API question was asked.

A fourth case is a CreepJS assessment without its individual reason. The final lied flag is insufficient to choose between inherited geometry or String.fromCodePoint concerns, restoration failure, arithmetic failure or a fixture-specific expectation. Reading the module's named diagnostic reason is more informative than attaching a guessed cause to the boolean. This does not require collecting every rectangle on a user's page or exporting a reusable fingerprint to a third party.

A bounded comparison record

A minimal record can answer the interpretation question without preserving an entire page's text or every geometry value. Record api, a non-sensitive fixture identifier, selection type, list length when applicable, collection state and the named comparison rule. Include the source revision when interpreting CreepJS-specific checks. Avoid calling a rule “standard” merely because the underlying measurement method is standardized: the API algorithm and a detector's expectations are separate layers.

Record fieldWhy it changes the interpretation
apiSeparates Element/Range and list/enclosure
selectionTypeSeparates an element selection from a text substring
fragmentCountPreserves a list's empty and fragmented states
coordinateSpaceIdentifies viewport coordinates or a derived conversion
layoutContextNames relevant width, scrolling and applied rotations
collectionStateSeparates collected, empty and failed capture
rule and sourceRevisionBind an assessment to its actual predicate and code

The field names above are a proposed support-record schema, not CreepJS output keys beyond the explicitly named source arrays. A record should not claim fragmentCount: 0 when collection failed before a list existed. It should also avoid substituting an empty string for an absent assessment; that would make a missing rule look like an executed comparison with no finding. Data minimization and correct state representation support the same diagnostic goal.

BotBrowser's documented --bot-noise-client-rects=true switch enables deterministic noise and is disabled by default. --bot-noise-seed is documented for reproducible font metrics. Neither statement establishes equivalent coverage for Element and Range methods, preservation of every detector-specific arithmetic expectation, anonymity, or acceptance by a service. Those outcomes would require their own evidence. The text-metrics switch is separately named --bot-noise-text-rects=true; it should not be silently substituted for the ClientRects control.

For a report comparison, the actionable sequence is to identify the API and state, check whether the selection and layout inputs match, then inspect the named predicate. If those inputs are unavailable, the remaining finding is limited to an unexplained geometry difference. Attaching an OS, device model or modification mechanism would exceed the available record. The smallest useful follow-up is a bounded reproduction of the specific fixture, not broader collection of unrelated identity signals.

FAQ: ClientRects fingerprint signals

Is ClientRects another name for Client Hints?

No. getClientRects() is a geometry method. Client Hints communicate request-related browser information through a separate mechanism. Similar wording does not make a header result evidence about element layout, and a DOMRect report does not establish which request headers were sent.

Does a changed digest reveal which rectangle changed?

No. A group digest summarizes serialized inputs. Without the array name, fixture and underlying fields, it does not localize a difference. A changed digest can justify examining that group; it cannot independently name the changed input or its cause.

Must Element and Range geometry always match?

No. An element box and a selected text range can cover different content. Even when one fixture makes them comparable, that fixture-specific relationship does not become a guarantee for arbitrary selections, multiline spans or transformed boxes.

Does a zero rectangle prove collection was blocked?

No. The empty-list bounding algorithm can return a zero rectangle, and degenerate boxes are another state. CreepJS's absent-report display is separate. The collector must preserve whether a call returned a list, a rectangle or no report before explaining a zero.

Is the CreepJS rotated-fixture expectation a web standard?

No. The pinned source applies its own engine- and DPR-conditioned branch. CSSOM View defines geometry methods, not that detector's expected digests. A future CreepJS revision can change the branch without changing the underlying API name.

Does setting a seed guarantee the same result everywhere?

No. The documented seed concerns reproducible noise. Different text, layout width, selection, transforms or coordinate context can still change the question presented to a geometry API. No cross-device equivalence or detector outcome is established by the setting alone.

Sources

Practical conclusion

A useful getClientRects fingerprint report identifies an API, a selected object and a specific comparison. Preserve empty, degenerate and failed states; keep source-defined CreepJS rules separate from standardized geometry. If only a digest or boolean remains, the cause is unresolved. Check the missing API or fixture context before changing a browser setting or assigning a device identity.

#Fingerprinting#Fonts#Privacy

This article is part of the guide: What Is a Browser Fingerprint?

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.