Skip to content
Chrome 155 → 156

Chrome 156: what changed for fingerprinting and automation

Chrome 156 changes what a page can observe in 22 places. A server sees the same TLS handshake, HTTP/2 settings and header order as on 155 in a fresh profile; only the version strings and the GREASE brand change. The new differences are in what scripts see: time-zone data, CSS random(), three static methods on RTCPeerConnection, text imports, and an XML error text that is Chrome 153's again.

Provisional — not yet re-measured on the stable build

Chrome 156 reaches the stable channel on 20 October 2026, and has been rolling out to a small share of Windows and macOS users since 7 October, first as 156.0.8078.12 and from 14 October as 156.0.8078.35. The values on this page were measured on the 156 beta (156.0.8078.12) against stable 155.0.8059.84, because 156.0.8078.12 was the only Linux build of 156 that Google had published on 19 October. Every value was measured on both builds in repeated runs, and between a beta and its stable release a value can still move. These are expected to: the time-zone data, because the stable build carries tzdata 2026e, which adds Winnipeg to the Inuvik change measured here and, according to the source, names London's winter time “Greenwich Mean Time”; the XSLT banner, an empty element on the beta that the source removes from transformToDocument() output from 156.0.8078.30; WGSL fragment_depth, which the beta lists and the source unships from 156.0.8078.43, so it has no row here (see what the docs claim); and Origin.from() of an SVG <a>, changed in the source but not in the beta, so not on this page yet. The field-trial note under TLS comes from the beta binary too. We re-measure on the stable build and correct this page; the “Run the checks in this browser” button above always reports what your own Chrome actually does.

Pim
Pim · Clearcote Research
Stable release
October 20, 2026
Measured on (Linux x64)
155.0.8059.84 → 156.0.8078.12
Source read at
155.0.8059.87 → 156.0.8078.45
V8
15.5.35.25 → 15.6.75.19

Key takeaways

  • Time-zone data moves on: America/Inuvik stays at UTC-06:00 after 1 November 2026, and EST5EDT, CST6CDT, MST7MDT and PST8PDT resolve to themselves instead of America/New_York and the other US zones. The stable build's tzdata 2026e adds Winnipeg.
  • New syntax a script sees at once: CSS random(), scroll-snap-type: pair, :navigation-source, and import ... with { type: 'text' }. RTCPeerConnection gains three static methods on desktop, the only change to Chrome's stable interface list.
  • DOMParser uses the Rust XML parser again, so malformed XML produces the Chrome 153 error text. Google's beta field-trial configuration put 12 of 21 profiles back on libxml2; with the configuration for stable 156, the beta build kept the Rust parser in 8 of 8.
  • V8 15.6 adds no built-ins but changes behaviour: three RegExp fixes, fractional getTimezoneOffset() for dates before standard time, Temporal keeps the year in Chinese and Korean calendars, and escaped get and set class fields parse. Separately, six secure-context interfaces move within window's property order.
  • A server sees the same TLS and HTTP/2 fingerprints as on 152 to 155 in a fresh profile, and 155's image/jxl in Accept stays. The new GREASE brand is Not:A-Brand version 8, listed first.

Every check, both versions, and your browser

29 one-line checks across 22 changes. The strip shows where they are; the table shows what each returns on Chrome 155 and Chrome 156. Run them in this browser to see which version its engine matches.

Chrome 155 → Chrome 156 · 29 checks

Run the checks to see which Chrome version this browser's engine matches.

Where the changes are. Select a surface to filter the rows.

CheckChrome 155Chrome 156This browser
Client Hints
New GREASE brand and brand order in User-Agent Client Hintsnavigator.userAgentData.brands.map((b) => `${b.brand};v=${b.version}`).join(', ')"Google Chrome;v=155, Chromium;v=155, Not(A:Brand;v=24""Not:A-Brand;v=8, Chromium;v=156, Google Chrome;v=156"–
JavaScript and DOM
WebRTC Diagnostic Logging: three new static methods on RTCPeerConnectiontypeof RTCPeerConnection.startDiagnosticLogging"undefined""function"n/a
WebRTC Diagnostic Logging: three new static methods on RTCPeerConnectionObject.getOwnPropertyNames(RTCPeerConnection).length47n/a
import ... with { type: 'text' } shipsimport('data:text/plain,cc156', { with: { type: 'text' } }).then((m) => m.default, (e) => e.name)"TypeError""cc156"n/a
Six secure-context interfaces move together in window's property orderObject.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('Clipboard') + 1]"ClipboardChangeEvent""ClipboardItem"–
Six secure-context interfaces move together in window's property orderObject.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('ServiceWorkerContainer') + 1]"NotRestoredReasonDetails""CrashReportContext"–
Date.prototype.getTimezoneOffset() keeps seconds for local mean timeNumber.isInteger(new Date(Date.UTC(1653, 1, 10, 6, 13, 20)).getTimezoneOffset())truefalsen/a
postMessage() options: delegate moves to every port and worker(() => { let read = false; new MessageChannel().port1.postMessage(0, { get delegate() { read = true; } }); return read; })()falsetrue–
postMessage() options: delegate moves to every port and worker(() => { const keys = []; window.postMessage(0, new Proxy({}, { get: (t, k) => { keys.push(String(k)); } })); return keys.join(); })()"transfer,includeUserActivation,delegate,targetOrigin""transfer,delegate,includeUserActivation,targetOrigin"–
CanvasRenderingContext2D.direction reports inheritdocument.createElement('canvas').getContext('2d').direction"ltr""inherit"–
pipeTo() marks the source stream as disturbed at once(() => { const r = new Response('x'); r.body.pipeTo(new WritableStream({}, { highWaterMark: 0 })); return r.bodyUsed; })()falsetrue–
RegExp: case folding in /iu backreferences, lookbehind alternatives and legacy /i factoring/^Ā(..)\1$/iu.test('ĀßSsß')truefalse–
RegExp: case folding in /iu backreferences, lookbehind alternatives and legacy /i factoring/(?<=(ba|a|c))x/.exec('bax')[1]"a""ba"–
RegExp: case folding in /iu backreferences, lookbehind alternatives and legacy /i factoring/^(?:ſa|sb|ſc|sd)$/i.test('sb')falsetrue–
CDATA at the start of an SVG or MathML fragment is parsed as text(() => { const p = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p.innerHTML = '<![CDATA[y]]>'; return p.textContent; })()"""y"–
Class fields named with an escaped get or set parse(() => { try { return Object.keys(new (eval('(class { g\\u0065t\n a; })'))()).join(); } catch (e) { return e.name; } })()"SyntaxError""get,a"n/a
Parsing and rendering
DOMParser on the Rust XML parser again: the Chrome 153 error text is backnew DOMParser().parseFromString('<a><b></a>', 'application/xml').querySelector('parsererror div').textContent.trim()"error on line 1 at column 11: Opening and ending tag mismatch: b line 1 and a""error on line 1 at column 10: Unexpected closing tag: a != b"n/a
XSLT output no longer carries the deprecation banner text/This site uses XSLT/.test(new XMLSerializer().serializeToString(((p) => (p.importStylesheet(new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><html><body><p><xsl:value-of select="r/v"/></p></body></html></xsl:template></xsl:stylesheet>', 'application/xml')), p.transformToDocument(new DOMParser().parseFromString('<r><v>1</v></r>', 'application/xml'))))(new XSLTProcessor())))truefalsen/a
text-transform: capitalize after a supplementary character(() => { const p = document.body.appendChild(document.createElement('p')); p.style.textTransform = 'capitalize'; p.innerHTML = '𝐀<span>bc def</span>'; const t = p.innerText; p.remove(); return t; })()"𝐀Bc Def""𝐀bc Def"–
CSS
CSS random() functionCSS.supports('width', 'random(1px, 2px)')falsetrue–
scroll-snap-type: pairCSS.supports('scroll-snap-type', 'pair mandatory')falsetrue–
overflow: clip next to a scrolling axis stays clip(() => { const d = document.body.appendChild(document.createElement('div')); d.style.overflow = 'clip auto'; const v = getComputedStyle(d).overflowX; d.remove(); return v; })()"hidden""clip"n/a
The :navigation-source pseudo-classCSS.supports('selector(:navigation-source)')falsetrue–
Native radio buttons get a 50% border radius(() => { const r = document.body.appendChild(document.createElement('input')); r.type = 'radio'; const v = getComputedStyle(r).borderTopLeftRadius; r.remove(); return v; })()"0px""50%"–
Media and GPU
AudioDecoder.isConfigSupported() no longer requires a descriptionAudioDecoder.isConfigSupported({ codec: 'flac', sampleRate: 48e3, numberOfChannels: 2 }).then((r) => r.supported, (e) => e.name)"TypeError"true–
Intl and time zones
Time-zone data: Inuvik keeps summer time, and the EST5EDT-style zones stand alonenew Intl.DateTimeFormat('en-US', { timeZone: 'America/Inuvik', timeZoneName: 'longOffset' }).formatToParts(Date.UTC(2026, 10, 15)).find((p) => p.type === 'timeZoneName').value"GMT-07:00""GMT-06:00"–
Time-zone data: Inuvik keeps summer time, and the EST5EDT-style zones stand alonenew Intl.DateTimeFormat('en-US', { timeZone: 'EST5EDT' }).resolvedOptions().timeZone"America/New_York""EST5EDT"–
Time-zone data: Inuvik keeps summer time, and the EST5EDT-style zones stand alonenew Date(Date.UTC(1992, 4, 2, 5, 30)).toLocaleString('en-US', { timeZone: 'America/Bogota' })"5/2/1992, 12:30:00 AM""5/2/1992, 1:30:00 AM"–
Temporal formatting keeps the year in Chinese and Korean calendarsTemporal.PlainDate.from('2026-08-27').toLocaleString('zh-CN-u-ca-chinese', { dateStyle: 'long' }) === new Date(Date.UTC(2026, 7, 27)).toLocaleDateString('zh-CN-u-ca-chinese', { dateStyle: 'long', timeZone: 'UTC' })falsetrue–

A tinted cell differs from the version before it. Values are as the DevTools console prints them. Checks that need a debugger attached are not run live.

TLS, HTTP/2 and request headers in Chrome 156

TLS ClientHello
Identical to Chrome 155 in a fresh profile: JA4 t13d1517h2_8daaf6152771_cb7bf5808d99, JA3N bd4930bd9b000ee684830e44bab76fdf, the same 19 extensions (GREASE, ECH, ALPS and trust anchors included), key-share groups (X25519MLKEM768 first) and signature algorithms, measured three times against two independent TLS echo services. Google's field-trial configuration can add one extension (code point 4832), which changes JA4 to t13d1518h2_8daaf6152771_4980c97edce0: the configuration served to the 156 beta did so in 5 of 11 profiles. With the configuration Google serves to stable 156 applied to the same beta binary, none of 8 profiles sent it, and none of 11 stable 155 profiles with their stable configuration did.
HTTP/2
Identical to Chrome 155: Akamai fingerprint 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p, HEADERS priority weight 256 (exclusive, depending on stream 0), pseudo-header order m,a,s,p, with or without the field-trial configuration.
Request headers
The same names in the same order on all 21 request types, and the same values apart from the version: User-Agent, Sec-CH-UA (with the new GREASE brand, now first), Sec-CH-UA-Full-Version and Sec-CH-UA-Full-Version-List. 155's version-free tell stays: image/jxl in the Accept header of navigations and image requests. So 156 adds no way for a server to tell it from 155 without a version string, except on a page that imports a module twice after the first fetch failed: 155 requests it once, 156 requests it again (measured on a 404 and on a wrong MIME type). Read in the source but not measured: Link headers on subresource responses no longer trigger preloads or compression-dictionary fetches.

Compare your own browser's handshake with these in the JA4 fingerprint checker.

Chrome 156 User-Agent strings and Client Hints

PlatformUser-Agent
WindowsMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36
macOSMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36
LinuxMozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36
AndroidMozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Mobile Safari/537.36
Headless (Linux)Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/156.0.0.0 Safari/537.36

The Linux and headless strings were measured on the 156 beta; the string does not change within a major. The others follow Chromium's reduced User-Agent format (user_agent_utils.cc): only the major version is real, the rest is frozen, and the platform part is fixed per operating system.

Sec-CH-UA (the low-entropy brand list sent with every request):

"Not:A-Brand";v="8", "Chromium";v="156", "Google Chrome";v="156"

The Chrome user agent page has these for the current version and every recent one; what is my user agent shows your own.

User-Agent and Client Hints

high impactMeasured on both builds

New GREASE brand and brand order in User-Agent Client Hints

The fake GREASE brand and the order of the brand list are computed from the major version. On 156 the brand is Not:A-Brand version 8, and it is listed first, ahead of Chromium and Google Chrome. Sent on every request in Sec-CH-UA, and returned by navigator.userAgentData.brands in windows and workers.

navigator.userAgentData.brands.map((b) => `${b.brand};v=${b.version}`).join(', ')
Chrome 155
"Google Chrome;v=155, Chromium;v=155, Not(A:Brand;v=24"
Chrome 156
"Not:A-Brand;v=8, Chromium;v=156, Google Chrome;v=156"

Why it matters: A version number changed by hand, with the previous brand list left in place, produces a combination no real Chrome sends. Version 8 is the one Chrome 153 used, but with a different brand and order. The algorithm is unchanged; only its input is.

JavaScript and Web APIs

high impactMeasured on both builds

WebRTC Diagnostic Logging: three new static methods on RTCPeerConnection

RTCPeerConnection.startDiagnosticLogging(), stopDiagnosticLogging() and cancelDiagnosticLogging() ship on desktop (not on Android), each a static method of length 0, so Object.getOwnPropertyNames(RTCPeerConnection) grows from 4 to 7 names. This is the only change to Chrome's stable window interface listing between 155 and 156; no interface is added or removed. The older navigator.rtc API stays behind a flag.

typeof RTCPeerConnection.startDiagnosticLogging
Chrome 155
"undefined"
Chrome 156
"function"
  • Object.getOwnPropertyNames(RTCPeerConnection).length4→ 7

Why it matters: A property read on a global constructor, synchronous and permission-free. On Android 156 reads like 155, so the live column skips it.

high impactMeasured on both builds

import ... with { type: 'text' } ships

Blink turns on the TC39 Import Text proposal: a module imported with type: 'text' resolves to its source text as the default export, with no MIME type check. On 155 the import rejects with TypeError: "text" is not a valid module type.

import('data:text/plain,cc156', { with: { type: 'text' } }).then((m) => m.default, (e) => e.name)
Chrome 155
"TypeError"
Chrome 156
"cc156"

Why it matters: One dynamic import answers it, and none of the five seeded 155 profiles we measured had it on. The live column skips it because this site's Content Security Policy does not allow data: scripts, so here the import fails the same way on both versions.

medium impactMeasured on both builds

Six secure-context interfaces move together in window's property order

Clipboard, ClipboardItem, Lock, LockManager, ServiceWorker and ServiceWorkerContainer, all [SecureContext] interfaces, leave their alphabetical slots and sit together between CaptureController and CrashReportContext. The number of interfaces does not change (1,226), and the order is the same headless, headful and on https://example.com.

Object.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('Clipboard') + 1]
Chrome 155
"ClipboardChangeEvent"
Chrome 156
"ClipboardItem"
  • Object.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('ServiceWorkerContainer') + 1]"NotRestoredReasonDetails"→ "CrashReportContext"

Why it matters: Any hash of window's property order moves, as it did in 154 and 155. These two neighbour checks did not move with Google's field-trial configuration (16 seeded profiles), but a hash of the whole order does: the configuration served to the 156 beta removes XSLTProcessor. On a page that is not a secure context the six interfaces are absent.

medium impactMeasured on both builds

Date.prototype.getTimezoneOffset() keeps seconds for local mean time

V8 15.6 stops truncating the offset to whole minutes. Before a zone adopted standard time it used local mean time, which has seconds, so in Europe/Amsterdam the offset of 10 February 1653 is -17.5 minutes on 156 and -17 on 155. toString() still prints whole minutes.

Number.isInteger(new Date(Date.UTC(1653, 1, 10, 6, 13, 20)).getTimezoneOffset())
Chrome 155
true
Chrome 156
false

Why it matters: One call on an old date reveals the engine, and the exact fraction also reveals the local zone's history. The answer depends on the visitor's time zone: we measured it with TZ=Europe/Amsterdam, and in UTC both versions read true. So the live column skips it.

medium impactMeasured on both builds

postMessage() options: delegate moves to every port and worker

The capability-delegation member delegate moves from WindowPostMessageOptions to PostMessageOptions. MessagePort, Worker and ServiceWorker postMessage() now read it, and window.postMessage() reads it before includeUserActivation instead of after.

(() => { let read = false; new MessageChannel().port1.postMessage(0, { get delegate() { read = true; } }); return read; })()
Chrome 155
false
Chrome 156
true
  • (() => { const keys = []; window.postMessage(0, new Proxy({}, { get: (t, k) => { keys.push(String(k)); } })); return keys.join(); })()"transfer,includeUserActivation,delegate,targetOrigin"→ "transfer,delegate,includeUserActivation,targetOrigin"

Why it matters: A getter on an options object shows which members the bindings read and in which order, a classic way to fingerprint the IDL. Synchronous and permission-free.

medium impactMeasured on both builds

CanvasRenderingContext2D.direction reports inherit

The direction getter now reflects the stored keyword. A fresh 2D context answers inherit instead of the resolved ltr, also on a canvas with dir="rtl", where 155 answers rtl. Text rendering is unchanged.

document.createElement('canvas').getContext('2d').direction
Chrome 155
"ltr"
Chrome 156
"inherit"

Why it matters: One property read on a fresh context, and canvas probes already create one.

medium impactMeasured on both builds

pipeTo() marks the source stream as disturbed at once

ReadableStream.prototype.pipeTo() now sets [[disturbed]] synchronously, as the Streams standard requires. A Response body piped into a sink reports bodyUsed immediately, even if the sink never reads.

(() => { const r = new Response('x'); r.body.pipeTo(new WritableStream({}, { highWaterMark: 0 })); return r.bodyUsed; })()
Chrome 155
false
Chrome 156
true

Why it matters: Pure JavaScript with no network access and no permission.

medium impactMeasured on both builds

RegExp: case folding in /iu backreferences, lookbehind alternatives and legacy /i factoring

V8 15.6 fixes three RegExp bugs. Backreferences under /iu use simple case folding, so U+00DF (ß) no longer matches "Ss". Alternatives inside a lookbehind keep their source order. Legacy /i prefix factoring no longer equates U+017F (ſ, long s) with "s".

/^Ā(..)\1$/iu.test('ĀßSsß')
Chrome 155
true
Chrome 156
false
  • /(?<=(ba|a|c))x/.exec('bax')[1]"a"→ "ba"
  • /^(?:ſa|sb|ſc|sd)$/i.test('sb')false→ true

Why it matters: Deterministic one-liners with no side effects, in every realm.

low impactMeasured on both builds

Class fields named with an escaped get or set parse

A class field spelled g\u0065t or s\u0065t followed by a newline is now a field named get or set, with automatic semicolon insertion. 155 threw a SyntaxError.

(() => { try { return Object.keys(new (eval('(class { g\\u0065t\n a; })'))()).join(); } catch (e) { return e.name; } })()
Chrome 155
"SyntaxError"
Chrome 156
"get,a"

Why it matters: A parser difference that needs eval or a script of its own, so pages whose Content Security Policy forbids eval, this one included, cannot run it. The live column skips it.

HTML

low impactMeasured on both builds

CDATA at the start of an SVG or MathML fragment is parsed as text

When innerHTML is parsed with an SVG or MathML context element, a leading <![CDATA[...]]> is now character data instead of a bogus comment, for an SVG path and a MathML mi alike.

(() => { const p = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p.innerHTML = '<![CDATA[y]]>'; return p.textContent; })()
Chrome 155
""
Chrome 156
"y"

Why it matters: Deterministic parser behaviour, but it needs crafted markup.

Parsing and rendering behaviour

high impactMeasured on both builds

DOMParser on the Rust XML parser again: the Chrome 153 error text is back

Documents parsed without a frame, such as DOMParser output and XMLHttpRequest.responseXML, plus SVG documents, go through the Rust xml crate instead of libxml2. Well-formed input gives the same tree, but the <parsererror> text changes: for all four malformed inputs we test it reads byte for byte as on Chrome 153, with the column numbers moved by one and no space before "Below is a rendering of the page up to the first error." Chrome 154 and 155 used libxml2.

new DOMParser().parseFromString('<a><b></a>', 'application/xml').querySelector('parsererror div').textContent.trim()
Chrome 155
"error on line 1 at column 11: Opening and ending tag mismatch: b line 1 and a"
Chrome 156
"error on line 1 at column 10: Unexpected closing tag: a != b"

Why it matters: The text comes straight from the parser, so one malformed string identifies the parser in use. But Google's field-trial configuration controls the switch: the configuration served to the 156 beta put 12 of 21 seeded profiles back on libxml2, while the configuration for stable 156 kept the Rust parser on the beta binary in 8 of 8, and 155 with its stable configuration stayed on libxml2 in 21 of 21. An installed Chrome can read either way, so the live column skips it.

medium impactMeasured on both builds

XSLT output no longer carries the deprecation banner text

Chrome 154 started inserting a red "This site uses XSLT" notice into documents produced by XSLTProcessor.transformToDocument(). On the 156 beta build measured here the notice is an empty <xslt-warning-banner> element without the text (the one-paragraph output shrinks from 947 to 376 characters), so the 154 page's check reads false again. From 156.0.8078.30 the source adds the banner only to documents styled through an xml-stylesheet processing instruction, as a popover inside a shadow root, so on the stable build the element is expected to be gone from transformToDocument() output too. The check reads false either way.

/This site uses XSLT/.test(new XMLSerializer().serializeToString(((p) => (p.importStylesheet(new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><html><body><p><xsl:value-of select="r/v"/></p></body></html></xsl:template></xsl:stylesheet>', 'application/xml')), p.transformToDocument(new DOMParser().parseFromString('<r><v>1</v></r>', 'application/xml'))))(new XSLTProcessor())))
Chrome 155
true
Chrome 156
false

Why it matters: Content hashes over XSLT output change again. The result depends on Google's field-trial configuration on both versions: with its stable configuration applied, 155 has no banner (21 of 21 profiles), and the configuration served to the 156 beta removes XSLTProcessor altogether (21 of 21). The configuration for stable 156 changed nothing on the beta binary (8 of 8). The live column skips it.

low impactMeasured on both builds

text-transform: capitalize after a supplementary character

Capitalisation now sees the whole previous code point when it lies outside the Basic Multilingual Plane. A word that follows a supplementary letter (here U+1D400 in a separate text node) is no longer treated as a new word.

(() => { const p = document.body.appendChild(document.createElement('p')); p.style.textTransform = 'capitalize'; p.innerHTML = '𝐀<span>bc def</span>'; const t = p.innerText; p.remove(); return t; })()
Chrome 155
"𝐀Bc Def"
Chrome 156
"𝐀bc Def"

Why it matters: innerText exposes the transformed text, so no rendering needs to be read.

CSS

high impactMeasured on both builds

CSS random() function

random() from CSS Values 5 parses wherever a math function is allowed. On the beta build it also accepts a dashed-ident sharing key (--x) and fixed 0.5, but not element-shared or a by step.

CSS.supports('width', 'random(1px, 2px)')
Chrome 155
false
Chrome 156
true

Why it matters: A cheap CSS.supports sweep item that splits 155 from 156 deterministically.

medium impactMeasured on both builds

overflow: clip next to a scrolling axis stays clip

Single-axis scroll containers ship: overflow: clip combined with a scrollable value on the other axis keeps its computed value instead of computing to hidden, so overflow: clip auto computes to clip auto instead of hidden auto.

(() => { const d = document.body.appendChild(document.createElement('div')); d.style.overflow = 'clip auto'; const v = getComputedStyle(d).overflowX; d.remove(); return v; })()
Chrome 155
"hidden"
Chrome 156
"clip"

Why it matters: A computed-style read that differs by version, and none of the seeded profiles we measured changed it. But Google's field-trial configuration has a study for this switch on both versions, and the 155 beta build (155.0.8059.12) already read clip while 155 stable reads hidden, so the live column skips it.

medium impactMeasured on both builds

Native radio buttons get a 50% border radius

A natively painted input[type=radio] gets border-radius: 50% from the theme, so box-shadow and outline follow the circle. Checkboxes stay at 0px, and with appearance: none the radius stays as authored.

(() => { const r = document.body.appendChild(document.createElement('input')); r.type = 'radio'; const v = getComputedStyle(r).borderTopLeftRadius; r.remove(); return v; })()
Chrome 155
"0px"
Chrome 156
"50%"

Why it matters: A computed-style read on a default form control; it also changes screenshots of radio buttons with a box-shadow or outline.

Media and codecs

medium impactMeasured on both builds

AudioDecoder.isConfigSupported() no longer requires a description

For FLAC and Vorbis (and, in the source, multi-channel Opus), a missing description no longer makes isConfigSupported() reject with TypeError: Invalid config; description is required. It resolves { supported: true }, and configure() fails later instead.

AudioDecoder.isConfigSupported({ codec: 'flac', sampleRate: 48e3, numberOfChannels: 2 }).then((r) => r.supported, (e) => e.name)
Chrome 155
"TypeError"
Chrome 156
true

Why it matters: WebCodecs capability sweeps call exactly this; it needs a secure context but no media.

Intl, time zones and locale

high impactMeasured on both builds

Time-zone data: Inuvik keeps summer time, and the EST5EDT-style zones stand alone

Chrome's bundled ICU time-zone data moves on from tzdata 2026c. On the beta build measured here, which carries 2026d, America/Inuvik stays at UTC-06:00 after 1 November 2026 instead of falling back to UTC-07:00; EST5EDT, CST6CDT, MST7MDT and PST8PDT become zones of their own on US rules, so they no longer resolve to America/New_York, America/Chicago, America/Denver and America/Los_Angeles; and Colombia's offsets for 1992 are corrected. Intl.supportedValuesOf('timeZone') still lists 418 zones. The stable build carries tzdata 2026e (from 156.0.8078.36), which according to the source also keeps America/Winnipeg at UTC-05:00 after 1 November 2026 and names London's and Dublin's winter time Greenwich Mean Time; both still read as on 155 on the beta (CST and GMT+00:00), so those checks follow the stable re-measure.

new Intl.DateTimeFormat('en-US', { timeZone: 'America/Inuvik', timeZoneName: 'longOffset' }).formatToParts(Date.UTC(2026, 10, 15)).find((p) => p.type === 'timeZoneName').value
Chrome 155
"GMT-07:00"
Chrome 156
"GMT-06:00"
  • new Intl.DateTimeFormat('en-US', { timeZone: 'EST5EDT' }).resolvedOptions().timeZone"America/New_York"→ "EST5EDT"
  • new Date(Date.UTC(1992, 4, 2, 5, 30)).toLocaleString('en-US', { timeZone: 'America/Bogota' })"5/2/1992, 12:30:00 AM"→ "5/2/1992, 1:30:00 AM"

Why it matters: Deterministic in every realm and independent of the operating system, because Chrome carries its own ICU data. Geo-coherence checks compare the offset a browser reports with the one its IP's region should have, so a browser claiming 156 on a Northwest Territories exit must report the new offset from November 2026.

medium impactMeasured on both builds

Temporal formatting keeps the year in Chinese and Korean calendars

Temporal's pattern filter now keeps the related-year and cyclic-year fields, so Temporal dates formatted with -u-ca-chinese or -u-ca-dangi match Date formatting. Temporal.PlainDate.from('2026-08-27').toLocaleString('zh-CN-u-ca-chinese', { dateStyle: 'long' }) reads 七月15日 on 155 and 2026丙午年七月十五 on 156; in ko-KR-u-ca-dangi, 7월 15일 becomes 병오년 7월 15일.

Temporal.PlainDate.from('2026-08-27').toLocaleString('zh-CN-u-ca-chinese', { dateStyle: 'long' }) === new Date(Date.UTC(2026, 7, 27)).toLocaleDateString('zh-CN-u-ca-chinese', { dateStyle: 'long', timeZone: 'UTC' })
Chrome 155
false
Chrome 156
true

Why it matters: Deterministic and permission-free; ICU data is bundled with the engine. It completes the Japanese fix of 155.

What the docs claim that the source does not show

Release notes and Chrome Platform Status describe intent; the source at the release tag is what shipped. These entries differ, so a version check built on the docs alone would be wrong.

Reproduce these results

  1. 1. Get both versions

    Use Google Chrome's own Linux packages of the two exact versions. Google keeps every version in its package pool; until it publishes a stable 156 for Linux, the 156 package is the beta:

    bash
    curl -sSO https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_155.0.8059.84-1_amd64.deb
    curl -sSO https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-beta/google-chrome-beta_156.0.8078.12-1_amd64.deb
    for f in google-chrome-*.deb; do dpkg -x "$f" "${f%.deb}"; done
  2. 2. Start each one clean

    A new profile directory per run and no automation flags. A fresh profile has not applied Google's field-trial configuration yet, which is the state these values describe. The beta binary lives in opt/google/chrome-beta/. Headless (--headless=new) gave the same values as headful wherever we measured both. For the getTimezoneOffset() row, start the browser with TZ=Europe/Amsterdam, as we did; in UTC both versions read the same.

    bash
    ./google-chrome-beta_156.0.8078.12-1_amd64/opt/google/chrome-beta/chrome --user-data-dir="$(mktemp -d)" --no-first-run https://example.com
  3. 3. Run the checks

    Press "Run the checks in this browser" at the top of this page, or paste the script below into the DevTools console on any https:// page. Several checks are asynchronous; the script awaits them.

  4. 4. Check the network side

    Open tls.peet.ws or tls.browserleaks.com in each version and compare the JA4 and HTTP/2 fingerprints and the request headers with the values above. They are the same on 155 and 156 in a fresh profile.

  5. 5. Find a change in the source yourself

    Blink's test expectations record the page-visible surface of each release; the runtime feature list records what is on by default:

    bash
    git diff 155.0.8059.87 156.0.8078.45 -- third_party/blink/web_tests/virtual/stable/webexposed/
    git diff 155.0.8059.87 156.0.8078.45 -- third_party/blink/renderer/platform/runtime_enabled_features.json5

The whole check list as one script

Paste it into the DevTools console on Chrome 155 and on Chrome 156: it prints every check on this page with the value your browser returns.

chrome-156-probe.js
// Chrome 155 vs 156: paste into the DevTools console on any https:// page and press Enter.
// Each line prints "<change>  <check> = <value>". Expected values: clearcotelabs.com/chrome-releases/156
(async () => {
  const checks = [
    ["grease-brand", () => navigator.userAgentData.brands.map((b) => `${b.brand};v=${b.version}`).join(', ')],
    ["tzdata-2026e", () => new Intl.DateTimeFormat('en-US', { timeZone: 'America/Inuvik', timeZoneName: 'longOffset' }).formatToParts(Date.UTC(2026, 10, 15)).find((p) => p.type === 'timeZoneName').value],
    ["tzdata-2026e", () => new Intl.DateTimeFormat('en-US', { timeZone: 'EST5EDT' }).resolvedOptions().timeZone],
    ["tzdata-2026e", () => new Date(Date.UTC(1992, 4, 2, 5, 30)).toLocaleString('en-US', { timeZone: 'America/Bogota' })],
    ["rtc-diagnostic-logging", () => typeof RTCPeerConnection.startDiagnosticLogging],
    ["rtc-diagnostic-logging", () => Object.getOwnPropertyNames(RTCPeerConnection).length],
    ["css-random", () => CSS.supports('width', 'random(1px, 2px)')],
    ["import-text", () => import('data:text/plain,cc156', { with: { type: 'text' } }).then((m) => m.default, (e) => e.name)],
    ["xml-parser-rust", () => new DOMParser().parseFromString('<a><b></a>', 'application/xml').querySelector('parsererror div').textContent.trim()],
    ["window-property-order", () => Object.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('Clipboard') + 1]],
    ["window-property-order", () => Object.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('ServiceWorkerContainer') + 1]],
    ["timezone-offset-fractional", () => Number.isInteger(new Date(Date.UTC(1653, 1, 10, 6, 13, 20)).getTimezoneOffset())],
    ["postmessage-delegate", () => (() => { let read = false; new MessageChannel().port1.postMessage(0, { get delegate() { read = true; } }); return read; })()],
    ["postmessage-delegate", () => (() => { const keys = []; window.postMessage(0, new Proxy({}, { get: (t, k) => { keys.push(String(k)); } })); return keys.join(); })()],
    ["css-scroll-snap-pair", () => CSS.supports('scroll-snap-type', 'pair mandatory')],
    ["css-single-axis-overflow", () => (() => { const d = document.body.appendChild(document.createElement('div')); d.style.overflow = 'clip auto'; const v = getComputedStyle(d).overflowX; d.remove(); return v; })()],
    ["css-navigation-source", () => CSS.supports('selector(:navigation-source)')],
    ["canvas-direction-inherit", () => document.createElement('canvas').getContext('2d').direction],
    ["radio-border-radius", () => (() => { const r = document.body.appendChild(document.createElement('input')); r.type = 'radio'; const v = getComputedStyle(r).borderTopLeftRadius; r.remove(); return v; })()],
    ["streams-pipeto-disturbed", () => (() => { const r = new Response('x'); r.body.pipeTo(new WritableStream({}, { highWaterMark: 0 })); return r.bodyUsed; })()],
    ["webcodecs-audio-config", () => AudioDecoder.isConfigSupported({ codec: 'flac', sampleRate: 48e3, numberOfChannels: 2 }).then((r) => r.supported, (e) => e.name)],
    ["regexp-fixes", () => /^Ā(..)\1$/iu.test('ĀßSsß')],
    ["regexp-fixes", () => /(?<=(ba|a|c))x/.exec('bax')[1]],
    ["regexp-fixes", () => /^(?:ſa|sb|ſc|sd)$/i.test('sb')],
    ["xslt-banner-transform", () => /This site uses XSLT/.test(new XMLSerializer().serializeToString(((p) => (p.importStylesheet(new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><html><body><p><xsl:value-of select="r/v"/></p></body></html></xsl:template></xsl:stylesheet>', 'application/xml')), p.transformToDocument(new DOMParser().parseFromString('<r><v>1</v></r>', 'application/xml'))))(new XSLTProcessor())))],
    ["temporal-non-gregorian", () => Temporal.PlainDate.from('2026-08-27').toLocaleString('zh-CN-u-ca-chinese', { dateStyle: 'long' }) === new Date(Date.UTC(2026, 7, 27)).toLocaleDateString('zh-CN-u-ca-chinese', { dateStyle: 'long', timeZone: 'UTC' })],
    ["html-cdata-foreign-fragment", () => (() => { const p = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p.innerHTML = '<![CDATA[y]]>'; return p.textContent; })()],
    ["capitalize-supplementary", () => (() => { const p = document.body.appendChild(document.createElement('p')); p.style.textTransform = 'capitalize'; p.innerHTML = '𝐀<span>bc def</span>'; const t = p.innerText; p.remove(); return t; })()],
    ["class-escaped-get-set", () => (() => { try { return Object.keys(new (eval('(class { g\\u0065t\n a; })'))()).join(); } catch (e) { return e.name; } })()],
  ];
  const out = [];
  for (const [id, f] of checks) {
    let v;
    try { v = await f(); } catch (e) { v = "throws " + e.name; }
    const expr = f.toString().replace(/^\(\)\s*=>\s*/, "");
    out.push(id.padEnd(28) + expr + " = " + (typeof v === "string" ? JSON.stringify(v) : String(v)));
  }
  console.log(out.join("\n"));
})();

FAQ

When was Chrome 156 released?

Chrome 156 reaches the stable channel on October 20, 2026, two weeks after Chrome 155. An early-stable rollout to a small share of Windows and macOS users started on October 7 with 156.0.8078.12, the build measured on this page, followed by 156.0.8078.35 on October 14.

What is the Chrome 156 user agent string?

On Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36. Only the major version changes; the full version travels in Client Hints. The table above lists every platform.

What is the Sec-CH-UA header in Chrome 156?

"Not:A-Brand";v="8", "Chromium";v="156", "Google Chrome";v="156". Both the fake GREASE brand and the order change with every major; on 156 the GREASE brand comes first.

Did Chrome 156 change the TLS (JA3/JA4) or HTTP/2 fingerprint?

Not in a fresh profile: JA4 t13d1517h2_8daaf6152771_cb7bf5808d99 and the same HTTP/2 settings, as on 152 to 155. With Google's beta field-trial configuration, 5 of 11 Chrome 156 beta profiles added one TLS extension; with the configuration for stable 156, none of 8 did. See the network section.

How can a website tell Chrome 155 from Chrome 156?

The quickest lines are CSS.supports('width', 'random(1px, 2px)'), document.createElement('canvas').getContext('2d').direction, which is "inherit" from 156, and the time-zone checks. None of them needs a permission, and none moved with Google's field-trial configuration in the profiles we measured.

Why does the XML error text match Chrome 153 again?

Chrome 153 switched DOMParser to a Rust XML parser, Chrome 154 went back to libxml2, and Chrome 156 uses the Rust parser again for documents parsed outside a frame. The switch is one Google's field-trial configuration can flip: the configuration served to the 156 beta put 12 of 21 profiles back on libxml2.

Did Chrome 156 change anything for automation?

Not in what a page sees: navigator.webdriver and the headless User-Agent format are unchanged. Over the DevTools protocol, /json/version gains a "File-Path-Style" field ("posix" on Linux), Storage.getRelatedWebsiteSets is gone, and FindInPage.findFirst and FindInPage.stop exist. Anything that can reach the debugging port can read the first.

Clearcote puts this into practice

An open-source Chromium with fingerprint control compiled into the engine. A drop-in for Playwright & Puppeteer.

Free for one browser with GitHub. No card.