CreepJS WebGL Report: Contexts and Capability Signals
Trace CreepJS WebGL fields from pinned source to report output, including context comparisons, optional renderer data, pixel reads, and limits.
BotBrowser Team
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.
A CreepJS WebGL report is not one graphics value or a single pass/fail test. In the pinned public implementation, separate code paths collect WebGL and WebGL2 parameters, optional renderer metadata, shader precision, canvas image data, and derived capability summaries. Reading those fields as one verdict hides which operation produced them and which failure path was actually reached.
BotBrowser documents profile-driven WebGL behavior and verification, but it does not claim that a CreepJS report identifies a physical device or predicts an unrelated site's decision. The useful task here is narrower: follow a named implementation from its source fields to its conditional report branches, then preserve the boundaries between those observations.
BotBrowser supports profile-based WebGL configuration and authorized checks of declared browser behavior; it cannot establish a physical device identity or another site's decision from a CreepJS field.
For the separate BrowserLeaks collector, the WebGL report analysis follows context discovery, optional renderer data and missing-field checks without treating them as CreepJS results.
TL;DR
- CreepJS commit
10aa6724cd33a1015db1574211890518cd04f0cccontains the reviewedsrc/webgl/index.ts. Static source inspection does not establish the live site's current behavior or a BotBrowser measurement. - The function creates WebGL and WebGL2 contexts separately. It exits when the WebGL context is absent, but the visible branch does not require the WebGL2 context to be available before continuing.
- The mirror check compares selected values found in the WebGL2 parameter map against truthy values in the WebGL map. It skips
ALIASED_LINE_WIDTH_RANGE,SHADING_LANGUAGE_VERSION, andVERSION. WEBGL_debug_renderer_info, shader precision, a canvas data URI, andreadPixelsoutput are separate observations. Missing data in one channel does not explain another channel.- A derived capability branch can set
LowerEntropy.WEBGLwhen a value is not found in an embedded list. That is behavior of this pinned implementation, not a universal WebGL rule or a statement about a person.
Contents
- Two contexts start separate collection paths
- The mirror check has conditional coverage
- Renderer and precision fields are not interchangeable
- Canvas export and pixel reads can diverge
- Capability summaries are a later derivation
- A report reading must preserve evidence boundaries
- Practical conclusion
Two contexts start separate collection paths
The source creates a canvas for the webgl context and another for webgl2, then calls getContext with the corresponding context names. The function checks whether the first context exists. If it does not, CreepJS records a failed webgl test result and returns from this path. The code shown at that point does not perform an equivalent early-return check for gl2; later helpers handle a missing context differently.
That order matters when someone reads a compact report. A failed WebGL path does not mean that both context types were tried successfully and returned matching results. Nor does the source branch explain why the first context was unavailable. Context creation can depend on browser support, the canvas state, resource conditions, and application code. The report field alone does not identify which explanation applies.
The distinction is useful in an owned diagnostic page. A collector can record webgl_context_created and webgl2_context_created independently, then preserve an error separately if setup throws. It should not write a single graphics_supported boolean before both attempts have a defined outcome. This proposed record shape is an application-side reading aid; it is not a CreepJS output schema and not a browser-standard API.
| Collection event | What the pinned code establishes | What remains unknown |
|---|---|---|
| The WebGL context is absent | This function records a failed webgl test result and stops this path | Why the request did not produce a context, or whether a later retry would differ |
| WebGL exists; WebGL2 is absent | The path continues past the first guard; helpers that receive a missing context can return empty or undefined data | Whether the report's later fields preserve that distinction for a particular consumer |
| Both context references exist | The subsequent parameter and rendering helpers can be called with both references | Whether every optional extension and readback operation will succeed |
| Setup throws before a field is assigned | The enclosing error handling can return an error-shaped result | Which operation failed unless the error is retained with its stage |
This is a control-flow map, not a compatibility matrix. A supported context does not imply every optional extension is present, and a successful context creation does not guarantee a readable drawing buffer. An application comparing browser builds should name its requested context, call outcome, and later operation separately. That makes a change in collection code visible instead of disguising it as a change in graphics capability.
The WebGL fingerprinting overview covers the broader disclosure surface and BotBrowser profile controls. The pinned src/webgl/index.ts module constructs separate context results, parameter comparisons, optional renderer metadata, shader precision, canvas and pixel outputs, and derived capability fields. The WebGL renderer missing-state analysis covers application-side states such as an unrequested field, unavailable context, lost context, and unavailable extension; it does not trace CreepJS's output assembly.
The mirror check has conditional coverage
After building parameter objects for the two contexts, the implementation defines three keys to exclude from its mirror comparison: ALIASED_LINE_WIDTH_RANGE, SHADING_LANGUAGE_VERSION, and VERSION. The mismatch list is produced from keys in the WebGL2 object. A candidate must also have a truthy value in the WebGL object, must not be one of the excluded names, and must have a different string representation in the two objects. The report branch runs only when the resulting list is non-empty.
The condition can be represented without claiming more than it does:
for each key in WebGL2 parameter object:
keep it only when WebGL[key] is truthy
skip ALIASED_LINE_WIDTH_RANGE, SHADING_LANGUAGE_VERSION, VERSION
compare String(WebGL[key]) with String(WebGL2[key])
report a mismatch only when at least one key remains
This is a useful source-review fixture because it shows both coverage and omissions. It is not an exhaustive equality assertion over every value exposed by either context. A property present only in the WebGL object is not iterated. A falsey WebGL value does not pass the truthiness guard. A difference hidden by string conversion may compare differently from a structural comparison of arrays or typed values. The three excluded names are intentionally outside this check, whatever their values.
| Example input shape | Included in the source's mismatch list? | Reason |
|---|---|---|
| Same truthy value for a non-excluded key in both maps | No | The string forms match |
| Different truthy values for a non-excluded key in both maps | Yes | The string forms differ |
WebGL value is 0, false, '', null, or undefined | No | The filter requires the WebGL-side value to be truthy |
| Key is absent from the WebGL2 map | No | The implementation iterates WebGL2 keys |
Key is VERSION or one of the other two excluded names | No | The explicit exclusion map filters it out |
These cases describe the predicate visible in the pinned source; they are not claims about what a particular browser returned. A reader can reproduce the reasoning by checking the VersionParam object, the Object.keys(params2) filter, and the mismatch.length condition at the pinned lines. The distinction prevents a common over-reading: “the report did not show a mismatch” does not mean that all WebGL and WebGL2 outputs were equal. It means only that this predicate produced an empty list for the data that reached it.
The report also does not tell a maintainer whether a mismatch came from an implementation defect, unsupported values, serialization, a missing context, or a collection change. Those are possible investigation directions, not conclusions embedded in the branch. A diagnostic record should retain the field name and collection stage when the cause matters; an aggregate count of “WebGL mismatches” discards exactly the evidence needed to tell the cases apart.
Renderer and precision fields are not interchangeable
The source first gathers parameter values from each context. It then merges in unmasked renderer information when WEBGL_debug_renderer_info is available. The helper requests that extension from the context; if the result is absent, the helper returns an empty object. If present, it reads UNMASKED_VENDOR_WEBGL and UNMASKED_RENDERER_WEBGL through getParameter.
That optional extension path is distinct from ordinary context parameters. The Khronos extension specification defines the optional unmasked vendor and renderer constants; the MDN getParameter reference describes the query API. Neither source makes a missing extension object a causal explanation. It does not prove a privacy setting, a missing GPU, a policy decision, or an application's intent. It means the helper did not add those two fields through this path.
Shader precision is another separate category. The helper requests several formats for vertex and fragment shaders, including low, medium, and high floating-point precision plus high integer precision. It then writes the precision, rangeMax, and rangeMin properties under keys associated with the shader stage and format. Those fields describe the result returned by the current context's precision query. They are not renderer names, extension flags, or proof that two devices are physically identical.
The final parameter object also adds selected context attributes and limits. The visible assembly includes the antialias attribute, MAX_VIEWPORT_DIMS, maximum texture anisotropy when an anisotropy extension is found, and MAX_DRAW_BUFFERS_WEBGL when its extension is present. Several of those lookups are wrapped in an attempt helper, while extension-dependent values can be absent. A report consumer should therefore keep “not present,” “not queried,” and “query raised an error” distinct when the surrounding output preserves that distinction.
| Output family | Source operation | A missing value cannot establish |
|---|---|---|
| Standard parameters | Enumerate the implementation's selected parameter names and call getParameter | That the browser lacks every graphics capability |
| Unmasked vendor/renderer | Request WEBGL_debug_renderer_info, then query its two constants | That a privacy control caused the absence |
| Shader precision | Call getShaderPrecisionFormat for named shader formats | A stable device identity or a site-level verdict |
| Optional limits | Request extension-dependent constants or context attributes | Whether another independent query would succeed |
The table is deliberately about the source path, not a recommended tracking schema. Renderer strings can be identifying and may be unnecessary for a support question. A product should collect only what its stated task needs, disclose optional diagnostics, and avoid turning a raw label into an account key. The BotBrowser WebGL documentation describes BotBrowser's own profile-driven settings; the CreepJS code is not evidence that BotBrowser returns any specific value on a user's machine.
Canvas export and pixel reads can diverge
The getWebGLData helper draws a pattern into a context and attempts to produce two output families. One is a canvas data URI. The other is a byte array populated by readPixels. The source reads the canvas drawing buffer dimensions, derives a smaller width and height by dividing them by 15 and 6, allocates four bytes per sampled pixel, then requests RGBA unsigned-byte values from the lower-left origin.
The order creates a concrete partial-failure case. The function attempts toDataURL before readPixels. If the inner readPixels call throws, the catch returns the dataURI already obtained and sets pixels to undefined. That is not an all-or-nothing result. A reader who sees an image value alongside missing pixel data should not assume that the entire WebGL path failed. Conversely, a missing data URI does not prove that the pixel operation was attempted: an earlier error can reach the outer catch first.
The helper also treats an OffscreenCanvas specially: it creates a regular canvas for the data-URI operation and draws into that canvas. The pixel read remains tied to the original WebGL context. This means the image export route and the pixel route are related by the drawing operation but are not literally the same API call or output representation.
| Failure point | Data URI path | Pixel path | Reading boundary |
|---|---|---|---|
| Context argument is missing | Helper returns undefined output fields | No read occurs | This helper does not explain why the context is missing |
| Drawing or data-URI creation throws | Outer error handling captures an error | The later read is not reached | Do not classify this as a readPixels failure |
readPixels throws after a data URI is made | The helper can return the data URI | pixels becomes undefined | The two result fields are not one atomic success flag |
| Both operations return | Data URI is present | Pixel array is returned | Neither value alone describes a physical device |
An engineer can use this map to write a support test for an owned rendering page: record whether drawing ran, whether serialization completed, and whether pixel readback completed. Keep the outputs separate so a fallback path can be evaluated without retaining more data than the support question requires. The sample grid is not a standardized fingerprint, and neither output establishes uniqueness or predicts whether a third party will accept a session.
Capability summaries are a later derivation
After collecting the source fields, the module creates a normalized numeric list from truthy parameter values that are not strings. Array.prototype.flat() flattens one array level; each resulting item is converted with Number, duplicates are removed, and the result is sorted numerically. The code then uses the renderer string to derive a GPU brand and computes two separate summaries: a compact value from the brand and parameter vector, and a capability value based on a reduction over the numeric list.
The module compares those derived values against embedded lists. If a brand-capability value exists but is absent from the brand list, the hasSusGpu condition is true. If the derived capability value exists but is absent from the numeric list, the hasSusCapabilities condition is true. Either branch can set LowerEntropy.WEBGL and call sendToTrash with a corresponding label. The source later logs the WebGL test as passed and returns the collected data with GPU classification fields.
The word “suspicious” in a variable name belongs to the implementation's internal branch. It is not a standardized definition of suspicious hardware. A value outside a source list can result from changes in the list, an unrepresented combination, a missing or transformed parameter, or the runtime environment. The static source page cannot tell which of those describes a browser currently producing a report.
| Derived item | Inputs visible in the source | What the branch does not prove |
|---|---|---|
webglParams | Truthy non-string top-level values; one-level array flattening, numeric conversion, deduplication, sorting | That every WebGL parameter is present or that this vector is stable across releases |
webglBrandCapabilities | A renderer-derived brand plus the string form of the parameter vector | That the renderer string names physical hardware or that the derived value uniquely identifies a device |
webglCapabilities | Numeric parameter values and their sorted positions in a reduction | That the result is an industry-standard capability identifier |
LowerEntropy.WEBGL branch | Membership tests against embedded brand and capability arrays | A universal bot verdict, account identity, or policy cause |
There is an important difference between raw fields and a derived summary. A raw field can be traced to a WebGL API call. A summary can depend on normalization, sorting, compact-value functions, lookup lists, and truthiness checks after collection. If the final report changes, a reader should first compare source revision and those transformations rather than attributing the difference to a physical GPU.
The CreepJS signal-groups analysis describes how a report groups independent browser surfaces. The cross-group decision analysis covers combinations and decision-level logic. The module's derived branch uses its collected WebGL fields and embedded lists; the branch does not describe cross-group aggregation.
A report reading must preserve evidence boundaries
A disciplined source review starts by fixing the revision. The same file path on a moving default branch can change between a saved report and today's code. Pinning commit 10aa6724cd33a1015db1574211890518cd04f0cc and the src/webgl/index.ts path makes line references and control-flow descriptions reproducible. It still does not prove that a live site deployed that revision, that a particular report was generated by it, or that every code path ran in a specific browser.
For a report under review, record only the facts needed to resolve the question:
- Identify the displayed field and the output family it belongs to: context, parameter, optional extension, shader query, data URI, pixel array, or derived summary.
- Match the field to a function and branch in the pinned source before assigning a cause.
- Preserve missing, undefined, empty, error, and present values as separate states when the source or report exposes them.
- Check whether a value was compared directly, stringified, normalized, hashed, or matched against a list.
- State the narrow result that the observed branch supports, and leave unobserved causes unresolved.
These steps help with privacy review, regression analysis, and explaining a third-party report without inventing a device identity. A source snapshot can show what code would do under particular inputs; it cannot supply those inputs or establish that an unobserved branch executed.
FAQ
Does a missing WebGL renderer mean that WebGL is disabled?
No. The renderer fields come through the optional WEBGL_debug_renderer_info extension, while context creation is checked separately. The source path can have a context without adding unmasked renderer values. The missing fields alone do not identify the explanation.
Does the mirror check compare every WebGL value?
No. It iterates the WebGL2 parameter object's keys, requires a truthy corresponding WebGL value, excludes three named keys, and compares string forms. An empty mismatch list is limited to that predicate and the data it received.
Is LowerEntropy.WEBGL a verdict that a browser is a bot?
The pinned module sets that internal flag when one of its derived values is not present in embedded lists. The source does not make it a universal WebGL standard, and this field alone does not establish a third-party decision about a visitor.
Does a successful data URI mean readPixels succeeded?
No. The helper attempts serialization before the pixel read. Its inner readback catch can return a data URI together with undefined pixel data. Those result fields have different completion states.
Sources
- Pinned CreepJS WebGL implementation, commit
10aa6724cd33a1015db1574211890518cd04f0cc - Khronos WebGL 1.0 specification
- Khronos
WEBGL_debug_renderer_infoextension - MDN:
WebGLRenderingContext.getParameter() - MDN:
HTMLCanvasElement.toDataURL() - BotBrowser WebGL documentation
- BotBrowser automation consistency documentation
Practical conclusion
Treat the report as a set of source-dependent observations, not a single device label. Pin the implementation revision, preserve the context and operation that produced each value, and keep optional renderer data, image serialization, pixel readback, and derived capability branches separate. When a report lacks one field, describe that absence at the level the code supports; do not infer why it happened or what another site decided.
This article is part of the guide: What Is a Browser Fingerprint?
Related Articles
BrowserLeaks WebGL Report: Collection and Missing States
FingerprintWEBGL_debug_renderer_info: Privacy and Renderer Boundaries
FingerprintWebGL Renderer Missing: Privacy or Context State?
Fingerprintwebgl_debug_renderer_info: What Your GPU Reveals to Pages
FingerprintWebGL Software Rendering, Capability Fallbacks, and Privacy
FingerprintCreepJS Math Signals: Read the Source Fields
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.