What each Chrome release changed
Chrome ships a new version every two weeks, and each one changes something a website or a server can see: new APIs, removed ones, Client Hints, the network handshake. Each page here lists those changes for one version, measured on real builds, with a one-line check and a Chromium source link for every item.
- Chrome 153 → 154
Chrome 154: what changed for fingerprinting and automation
Stable Sep 22, 2026 · 16 changes (7 high, 5 medium, 4 low impact)
- Chrome 152 → 153
Chrome 153: what changed for fingerprinting and automation
Stable Sep 8, 2026 · 18 changes (6 high, 8 medium, 4 low impact)
Every check across the series, and which version your browser matches
One row per check, one column per Chrome version. A tinted cell differs from the version before it, so a row tinted in one column only identifies that version. Run the checks to see where this browser's engine lands.
Chrome 152 → Chrome 153 → Chrome 154 · 49 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.
| Check | Chrome 152 | Chrome 153 | Chrome 154 | This browser |
|---|---|---|---|---|
| Client Hints | ||||
New GREASE brand and brand order in User-Agent Client Hintsnavigator.userAgentData.brands.map((b) => `${b.brand};v=${b.version}`).join(', ') | "Chromium;v=152, Not?A_Brand;v=24, Google Chrome;v=152" | "Google Chrome;v=153, Not_A Brand;v=8, Chromium;v=153" | "Chromium;v=154, Google Chrome;v=154, Not A(Brand;v=99" | – |
| JavaScript and DOM | ||||
New Iterator helpers: Iterator.zip, Iterator.zipKeyed and Iterator.prototype.jointypeof Iterator.zip | "undefined" | "function" | – | |
New Iterator helpers: Iterator.zip, Iterator.zipKeyed and Iterator.prototype.jointypeof Iterator.zipKeyed | "undefined" | "function" | – | |
New Iterator helpers: Iterator.zip, Iterator.zipKeyed and Iterator.prototype.jointypeof Iterator.prototype.join | "undefined" | "function" | – | |
New <camera> and <microphone> elementstypeof HTMLCameraElement | "undefined" | "function" | – | |
New <camera> and <microphone> elementstypeof HTMLMicrophoneElement | "undefined" | "function" | – | |
Shared Storage removed, with its permissions-policy features'sharedStorage' in window | true | false | – | |
Shared Storage removed, with its permissions-policy featurestypeof SharedStorageWorklet | "function" | "undefined" | – | |
Shared Storage removed, with its permissions-policy features'sharedStorageWritable' in HTMLIFrameElement.prototype | true | false | – | |
Shared Storage removed, with its permissions-policy features'canLoadOpaqueURL' in HTMLFencedFrameElement | true | false | – | |
Shared Storage removed, with its permissions-policy featuresdocument.featurePolicy.features().includes('run-ad-auction') | true | false | – | |
HTMLElement.autocorrect'autocorrect' in HTMLElement.prototype | false | true | – | |
BaseAudioContext.renderQuantumSize'renderQuantumSize' in BaseAudioContext.prototype | false | true | – | |
Property order moved on Element, Document and CustomElementRegistryObject.getOwnPropertyNames(Element.prototype).indexOf('customElementRegistry') < Object.getOwnPropertyNames(Element.prototype).indexOf('onfullscreenchange') | false | true | – | |
Property order moved on Element, Document and CustomElementRegistryObject.getOwnPropertyNames(Document.prototype).indexOf('customElementRegistry') < Object.getOwnPropertyNames(Document.prototype).indexOf('fonts') | false | true | – | |
Property order moved on Element, Document and CustomElementRegistryObject.getOwnPropertyNames(CustomElementRegistry.prototype).indexOf('initialize') < Object.getOwnPropertyNames(CustomElementRegistry.prototype).indexOf('upgrade') | false | true | – | |
RTCDataChannel options now reject out-of-range values(() => { const pc = new RTCPeerConnection(); try { pc.createDataChannel('x', { maxRetransmits: -1 }); return 'no throw'; } catch (e) { return e.name; } finally { pc.close(); } })() | "no throw" | "TypeError" | – | |
V8 15.4 reorders the global object: Float16Array, SuppressedError and friends move to their spec positionsObject.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('Float64Array') + 1] | "DataView" | "Float16Array" | – | |
V8 15.4 reorders the global object: Float16Array, SuppressedError and friends move to their spec positionsObject.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('URIError') + 1] | "globalThis" | "SuppressedError" | – | |
V8 15.4 reorders the global object: Float16Array, SuppressedError and friends move to their spec positionsObject.getOwnPropertyNames(Math).slice(-2).join(',') | "f16round,sumPrecise" | "SQRT1_2,SQRT2" | – | |
V8 15.4 reorders the global object: Float16Array, SuppressedError and friends move to their spec positionsObject.getOwnPropertyNames(DataView.prototype).indexOf('getFloat16') < Object.getOwnPropertyNames(DataView.prototype).indexOf('getFloat32') | false | true | – | |
V8 15.4 reorders the global object: Float16Array, SuppressedError and friends move to their spec positionsObject.getOwnPropertyNames(Intl.Locale.prototype).indexOf('variants') < Object.getOwnPropertyNames(Intl.Locale.prototype).indexOf('baseName') | false | true | – | |
Promise.try returns the promise it was given((p) => Promise.try(() => p) === p)(Promise.resolve(1)) | false | true | – | |
Iterator.prototype.includestypeof Iterator.prototype.includes | "undefined" | "function" | – | |
window.requestResize (responsive iframes)typeof window.requestResize | "undefined" | "function" | – | |
FontFace.prototype.width'width' in FontFace.prototype | false | true | – | |
CSS Typed OM in dedicated workersnew Promise((r) => { const w = new Worker(URL.createObjectURL(new Blob(['postMessage(typeof CSSStyleValue)']))); w.onmessage = (e) => r(e.data); }) | "undefined" | "function" | – | |
SVGTextPathElement gains the TEXTPATH_SIDETYPE constants'TEXTPATH_SIDETYPE_LEFT' in SVGTextPathElement | false | true | – | |
Property order moved: Document.prototype.activeViewTransitionObject.getOwnPropertyNames(Document.prototype).indexOf('activeViewTransition') < Object.getOwnPropertyNames(Document.prototype).indexOf('fonts') | false | true | – | |
document.requestStorageAccessFor and FencedFrameConfig.setSharedStorageContext removed'requestStorageAccessFor' in document | true | false | – | |
document.requestStorageAccessFor and FencedFrameConfig.setSharedStorageContext removed'setSharedStorageContext' in FencedFrameConfig.prototype | true | false | – | |
| Parsing and rendering | ||||
XML parsing moved to a Rust parser, and its error messages changednew 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" | "error on line 1 at column 11: Opening and ending tag mismatch: b line 1 and a" | – |
Canvas 2D no longer draws a zero-length stroke(() => { const c = document.createElement('canvas'); c.width = c.height = 20; const x = c.getContext('2d'); x.lineWidth = 10; x.lineCap = 'round'; x.beginPath(); x.moveTo(10, 10); x.lineTo(10, 10); x.stroke(); return x.getImageData(10, 10, 1, 1).data[3]; })() | 255 | 0 | – | |
XSLT output carries a deprecation banner/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()))) | false | true | – | |
| CSS | ||||
New CSS property: scroll-axis-lockCSS.supports('scroll-axis-lock', 'auto') | false | true | – | |
scroll-marker-group: the tabs and links modes turned offCSS.supports('scroll-marker-group', 'after tabs') | true | false | true | – |
<area> is display: none by default(() => { const a = document.body.appendChild(document.createElement('area')); const d = getComputedStyle(a).display; a.remove(); return d; })() | "inline" | "none" | – | |
color-mix() now serialises the implied percentage((s) => { s.color = 'color-mix(in oklab, red 30%, blue)'; return s.color; })(document.createElement('i').style) | "color-mix(red 30%, blue)" | "color-mix(red 30%, blue 70%)" | – | |
New CSS property: frame-sizingCSS.supports('frame-sizing', 'content-height') | false | true | – | |
| Media and GPU | ||||
IAMF immersive audio in MP4document.createElement('video').canPlayType('audio/mp4; codecs="iamf"') | "" | "probably" | – | |
IAMF immersive audio in MP4MediaSource.isTypeSupported('audio/mp4; codecs="iamf"') | false | true | – | |
New WGSL language features: buffer_view and texture_formats_tier1navigator.gpu.wgslLanguageFeatures.has('buffer_view') | false | true | – | |
New WGSL language features: buffer_view and texture_formats_tier1navigator.gpu.wgslLanguageFeatures.has('texture_formats_tier1') | false | true | – | |
WebGL shader and program info logs no longer end in a NUL character(() => { const gl = document.createElement('canvas').getContext('webgl'); const s = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(s, 'void main(){ undeclared_x; }'); gl.compileShader(s); const l = gl.getShaderInfoLog(s); return l.charCodeAt(l.length - 1) === 0; })() | true | false | – | |
New WGSL language feature: swizzle_assignmentnavigator.gpu.wgslLanguageFeatures.has('swizzle_assignment') | false | true | – | |
| Automation | ||||
DevTools no longer runs Proxy getters when it serialises a logged object(() => { let hit = false; const e = new Error('x'); Object.defineProperty(e, 'stack', { configurable: true, get: new Proxy(function () {}, { apply() { hit = true; return ''; } }) }); console.log(e); return hit; })() | true | false | n/a | |
| Intl and time zones | ||||
Time zone data updated to tzdata 2026cnew Intl.DateTimeFormat('en-US', { timeZone: 'Africa/Casablanca', timeZoneName: 'longOffset' }).formatToParts(Date.UTC(2026, 9, 1)).find((p) => p.type === 'timeZoneName').value | "GMT+01:00" | "GMT+00:00" | – | |
Time zone data updated to tzdata 2026cnew Intl.DateTimeFormat('en-US', { timeZone: 'America/Vancouver', timeZoneName: 'longOffset' }).formatToParts(Date.UTC(2026, 10, 15)).find((p) => p.type === 'timeZoneName').value | "GMT-08:00" | "GMT-07:00" | – | |
Time zone data updated to tzdata 2026cnew Intl.DateTimeFormat('en-US', { timeZone: 'America/Edmonton', timeZoneName: 'longOffset' }).formatToParts(Date.UTC(2026, 10, 15)).find((p) => p.type === 'timeZoneName').value | "GMT-07:00" | "GMT-06:00" | – | |
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.
How these pages are made
Every comparison uses the same method, so anyone can repeat it. We run Google's own Chrome for Testing builds of both versions on one machine, dump what a page can observe and what the server receives, and diff the two. We then find each difference in Chromium's source at the two release tags and link it. A change Google announces but the source does not turn on by default is marked as unverified, not listed as fact.
To see how your own browser measures up against these values, run the browser fingerprint test, or read how detection uses them in Research.