Skip to content
Clearcote Labs · Chrome releases

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 release schedule

Chrome 155 is the current stable version, since Oct 6, 2026. Chrome 156 reaches stable on Oct 20, 2026, after an early rollout to a small share of users from Oct 7, 2026. Since Chrome 153, a new version reaches stable every two weeks (it was every four weeks before), with longer gaps around the end of the year. Each version is cut from Chromium's main branch at its branch point, goes to beta, then to early stable, then to everyone over the days after its stable date.

ChromeBranch pointFirst betaEarly stableStableGapStatus
Chrome 155Sep 14, 2026Sep 16, 2026Sep 23, 2026Oct 6, 20262 wkStable now
Chrome 156Sep 28, 2026Sep 30, 2026Oct 7, 2026Oct 20, 20262 wkEarly stable
Chrome 157Oct 12, 2026Oct 14, 2026Oct 21, 2026Nov 3, 20262 wkUpcoming
Chrome 158Oct 26, 2026Oct 28, 2026Nov 4, 2026Nov 17, 20262 wkUpcoming
Chrome 159Nov 9, 2026Nov 11, 2026Nov 18, 2026Dec 8, 20263 wkUpcoming
Chrome 160LTSNov 30, 2026Dec 2, 2026Dec 9, 2026Jan 5, 20274 wkUpcoming
Chrome 161Dec 14, 2026Dec 16, 2026Jan 6, 2027Jan 19, 20272 wkUpcoming

From Google's Chromium Dash, checked every six hours. Dates further ahead are Google's plan and can move.

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 151 → Chrome 152 → Chrome 153 → Chrome 154 → Chrome 155 · 153 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 151Chrome 152Chrome 153Chrome 154Chrome 155This browser
TLS handshake
TLS ClientHello gains the trust_anchors extension and a GREASE signature algorithmJSON.parse(document.querySelector('pre').textContent).tls.ja4"t13d1516h2_8daaf6152771_806a8c22fdea""t13d1517h2_8daaf6152771_cb7bf5808d99"n/a
TLS ClientHello gains the trust_anchors extension and a GREASE signature algorithmJSON.parse(document.querySelector('pre').textContent).tls.extensions.some((e) => e.name === 'Unknown extension 51764')falsetruen/a
Header order
Connection-Allowlist response header is enforced for every sitefetch('https://example.com/', { mode: 'no-cors' }).then(() => 'sent', (e) => e.name)"sent""TypeError"n/a
Topics and Attribution Reporting request options and headers removed(() => { let read = false; new Request('/', { get attributionReporting() { read = true; } }); return read; })()truefalse–
Topics and Attribution Reporting request options and headers removed(() => { let read = false; new Request('/', { get browsingTopics() { read = true; } }); return read; })()truefalse–
JPEG XL decoded by default, and image/jxl added to the Accept headerImageDecoder.isTypeSupported('image/jxl')falsetrue–
Client Hints
New GREASE brand and brand order in User-Agent Client Hintsnavigator.userAgentData.brands.map((b) => `${b.brand};v=${b.version}`).join(', ')"Not=A?Brand;v=99, Google Chrome;v=151, Chromium;v=151""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""Google Chrome;v=155, Chromium;v=155, Not(A:Brand;v=24"–
JavaScript and DOM
navigator.cpuPerformance (CPU Performance API)'cpuPerformance' in navigatorfalsetrue–
navigator.cpuPerformance (CPU Performance API)typeof navigator.cpuPerformance"undefined""number"–
OpaqueRange, NodeRange and createValueRange(): the Range prototype chain changestypeof OpaqueRange"undefined""function"–
OpaqueRange, NodeRange and createValueRange(): the Range prototype chain changesObject.getPrototypeOf(Range.prototype).constructor.name"AbstractRange""NodeRange"–
OpaqueRange, NodeRange and createValueRange(): the Range prototype chain changes'startContainer' in AbstractRange.prototypetruefalse–
OpaqueRange, NodeRange and createValueRange(): the Range prototype chain changestypeof HTMLInputElement.prototype.createValueRange"undefined""function"–
FeaturePolicy is renamed PermissionsPolicytypeof PermissionsPolicy"undefined""function"–
FeaturePolicy is renamed PermissionsPolicyFeaturePolicy.name"FeaturePolicy""PermissionsPolicy"–
FeaturePolicy is renamed PermissionsPolicyObject.prototype.toString.call(document.featurePolicy)"[object FeaturePolicy]""[object PermissionsPolicy]"–
window.chrome is now a configurable property, created laterObject.getOwnPropertyDescriptor(window, 'chrome').configurablefalsetrue–
window.chrome is now a configurable property, created laterObject.getOwnPropertyNames(window).indexOf('chrome') < Object.getOwnPropertyNames(window).indexOf('WebAssembly')truefalse–
Protected Audience, Topics and Shared Storage are stubbed out: still exposed, no longer working(() => { try { return navigator.adAuctionComponents(1).length; } catch (e) { return e.name; } })()"InvalidStateError"0–
Protected Audience, Topics and Shared Storage are stubbed out: still exposed, no longer workingdocument.browsingTopics().then(() => 'resolved', (e) => e.name)"resolved""NotSupportedError"–
Protected Audience, Topics and Shared Storage are stubbed out: still exposed, no longer workingnavigator.protectedAudience.queryFeatureSupport('sellerNonce')truefalse–
Protected Audience, Topics and Shared Storage are stubbed out: still exposed, no longer workingnavigator.canLoadAdAuctionFencedFrame()truefalsen/a
DeviceOrientationEvent.requestPermission() and DeviceMotionEvent.requestPermission()typeof DeviceOrientationEvent.requestPermission"undefined""function"–
DeviceOrientationEvent.requestPermission() and DeviceMotionEvent.requestPermission()typeof DeviceMotionEvent.requestPermission"undefined""function"–
console.context() objects get dirxml instead of dirXmltypeof console.context().dirxml"undefined""function"–
console.context() objects get dirxml instead of dirXmltypeof console.context().dirXml"function""undefined"–
V8's internal built-in closures are strict: .caller throws, f.caller is null(() => { try { return typeof Proxy.revocable({}, {}).revoke.caller; } catch (e) { return e.name; } })()"object""TypeError"–
V8's internal built-in closures are strict: .caller throws, f.caller is nullnew Promise((r) => Promise.resolve().finally(function f() { r(typeof f.caller); }))"function""object"n/a
Property order moved on window, Document, PointerEvent and the Performance interfacesObject.getOwnPropertyNames(window).indexOf('onpagereveal') < Object.getOwnPropertyNames(window).indexOf('onpageswap')falsetrue–
Property order moved on window, Document, PointerEvent and the Performance interfacesObject.getOwnPropertyNames(PointerEvent.prototype).indexOf('persistentDeviceId') < Object.getOwnPropertyNames(PointerEvent.prototype).indexOf('getPredictedEvents')falsetrue–
Property order moved on window, Document, PointerEvent and the Performance interfacesObject.getOwnPropertyNames(PerformanceNavigationTiming.prototype).indexOf('confidence') < Object.getOwnPropertyNames(PerformanceNavigationTiming.prototype).indexOf('toJSON')falsetrue–
Property order moved on window, Document, PointerEvent and the Performance interfacesObject.getOwnPropertyNames(PerformancePaintTiming.prototype)[0]"paintTime""toJSON"–
Property order moved on window, Document, PointerEvent and the Performance interfacesObject.getOwnPropertyNames(window).indexOf('PerformanceLongAnimationFrameTiming') < Object.getOwnPropertyNames(window).indexOf('Performance')truefalse–
The attribution-reporting permissions-policy feature is removeddocument.featurePolicy.features().includes('attribution-reporting')truefalse–
Shadow DOM reference target: ShadowRoot.referenceTarget'referenceTarget' in ShadowRoot.prototypefalsetrue–
Shadow DOM reference target: ShadowRoot.referenceTarget'shadowRootReferenceTarget' in HTMLTemplateElement.prototypefalsetrue–
Declarative shadow DOM: shadowrootslotassignment'shadowRootSlotAssignment' in HTMLTemplateElement.prototypefalsetrue–
Sanitizer API: processing instructions in the configuration, streaming sanitizer on'processingInstructions' in new Sanitizer().get()falsetrue–
Sanitizer API: processing instructions in the configuration, streaming sanitizer onDocument.parseHTML('a<!--x-->b').body.childNodes.length21–
RegExp split with the v flag steps by code point on the spec path(() => { const v = []; class R extends RegExp { exec() { v.push(this.lastIndex); return null; } } ('a' + String.fromCodePoint(128512) + 'b').split(new R('x', 'v')); return v.join(); })()"0,1,2,3""0,1,3"–
WebAssembly compile errors name --wasm-* flags instead of --experimental-wasm-*(() => { try { new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 10, 6, 1, 4, 0, 224, 0, 11])); return 'compiled'; } catch (e) { return e.message.slice(e.message.indexOf('(enable')); } })()"(enable with --experimental-wasm-wasmfx) @+23""(enable with --wasm-wasmfx) @+23"–
Error stacks include WebAssembly frames across JSPI suspensions(async () => { const b = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 2, 7, 1, 1, 109, 1, 102, 0, 0, 3, 2, 1, 0, 7, 7, 1, 3, 114, 117, 110, 0, 1, 10, 6, 1, 4, 0, 16, 0, 11]); const { instance: i } = await WebAssembly.instantiate(b, { m: { f: new WebAssembly.Suspending(async () => { await 0; throw Error(); }) } }); try { await WebAssembly.promising(i.exports.run)(); } catch (e) { return e.stack.includes('wasm-function'); } })()falsetrue–
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 windowtruefalse–
Shared Storage removed, with its permissions-policy featurestypeof SharedStorageWorklet"function""undefined"–
Shared Storage removed, with its permissions-policy features'sharedStorageWritable' in HTMLIFrameElement.prototypetruefalse–
Shared Storage removed, with its permissions-policy features'canLoadOpaqueURL' in HTMLFencedFrameElementtruefalse–
Shared Storage removed, with its permissions-policy featuresdocument.featurePolicy.features().includes('run-ad-auction')truefalse–
HTMLElement.autocorrect'autocorrect' in HTMLElement.prototypefalsetrue–
BaseAudioContext.renderQuantumSize'renderQuantumSize' in BaseAudioContext.prototypefalsetrue–
Property order moved on Element, Document and CustomElementRegistryObject.getOwnPropertyNames(Element.prototype).indexOf('customElementRegistry') < Object.getOwnPropertyNames(Element.prototype).indexOf('onfullscreenchange')falsetrue–
Property order moved on Element, Document and CustomElementRegistryObject.getOwnPropertyNames(Document.prototype).indexOf('customElementRegistry') < Object.getOwnPropertyNames(Document.prototype).indexOf('fonts')falsetrue–
Property order moved on Element, Document and CustomElementRegistryObject.getOwnPropertyNames(CustomElementRegistry.prototype).indexOf('initialize') < Object.getOwnPropertyNames(CustomElementRegistry.prototype).indexOf('upgrade')falsetrue–
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')falsetrue–
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')falsetrue–
Promise.try returns the promise it was given((p) => Promise.try(() => p) === p)(Promise.resolve(1))falsetrue–
Iterator.prototype.includestypeof Iterator.prototype.includes"undefined""function"–
window.requestResize (responsive iframes)typeof window.requestResize"undefined""function"–
FontFace.prototype.width'width' in FontFace.prototypefalsetrue–
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 SVGTextPathElementfalsetrue–
Property order moved: Document.prototype.activeViewTransitionObject.getOwnPropertyNames(Document.prototype).indexOf('activeViewTransition') < Object.getOwnPropertyNames(Document.prototype).indexOf('fonts')falsetrue–
document.requestStorageAccessFor and FencedFrameConfig.setSharedStorageContext removed'requestStorageAccessFor' in documenttruefalse–
document.requestStorageAccessFor and FencedFrameConfig.setSharedStorageContext removed'setSharedStorageContext' in FencedFrameConfig.prototypetruefalse–
WebCrypto: ML-KEM, ML-DSA, ChaCha20-Poly1305 and X-Wing, with SubtleCrypto.supports()typeof SubtleCrypto.supports"undefined""function"–
WebCrypto: ML-KEM, ML-DSA, ChaCha20-Poly1305 and X-Wing, with SubtleCrypto.supports()crypto.subtle.generateKey('ChaCha20-Poly1305', false, ['encrypt']).then((k) => k.algorithm.name, (e) => e.name)"NotSupportedError""ChaCha20-Poly1305"–
WebCrypto: ML-KEM, ML-DSA, ChaCha20-Poly1305 and X-Wing, with SubtleCrypto.supports()Object.getOwnPropertyNames(SubtleCrypto.prototype).length1318–
Three more permissions-policy features, and Digital Credentials issuance['digital-credentials-create', 'media-playback-while-not-visible', 'speaker-selection'].map((f) => document.featurePolicy.features().includes(f)).join()"false,false,false""true,true,true"–
Three more permissions-policy features, and Digital Credentials issuance(() => { let read = false; navigator.credentials.create({ get digital() { read = true; } }).catch(() => {}); return read; })()falsetrue–
IntersectionObserver margins accept absolute length units(() => { try { return new IntersectionObserver(() => {}, { rootMargin: '1in' }).rootMargin; } catch (e) { return e.name; } })()"SyntaxError""96px 96px 96px 96px"–
fetch() of a blob: URL rejects an invalid Range headerfetch(URL.createObjectURL(new Blob(['abc'])), { headers: { Range: 'bytes=x' } }).then((r) => r.status, (e) => e.name)200"TypeError"n/a
SVGAElement.referrerPolicy only reflects known values(() => { const a = document.createElementNS('http://www.w3.org/2000/svg', 'a'); a.setAttribute('referrerpolicy', 'bogus'); return a.referrerPolicy; })()"bogus"""–
RTCDTMFToneChangeEvent no longer needs an init dictionaryRTCDTMFToneChangeEvent.length21–
document.<name> finds object and embed elements that are not 'exposed'(() => { const o = document.body.appendChild(document.createElement('object')); o.name = 'cc155probe'; o.appendChild(document.createElement('embed')); const r = document.cc155probe === o; o.remove(); return r; })()falsetrue–
Empty SVG number, integer, angle and length attributes fall back to the initial value(() => { const t = document.createElementNS('http://www.w3.org/2000/svg', 'feTurbulence'); t.setAttribute('numOctaves', ''); return t.numOctaves.baseVal; })()01–
fetchLater() accepts an empty body(() => { const c = new AbortController(); try { fetchLater(location.href, { method: 'POST', body: '', signal: c.signal }); return 'ok'; } catch (e) { return e.name; } finally { c.abort(); } })()"TypeError""ok"–
<body> margin attributes parse as whole pixelsnew Promise((r) => { const f = document.createElement('iframe'); f.srcdoc = '<body marginwidth="10.5">'; f.onload = () => { r(getComputedStyle(f.contentDocument.body).marginLeft); f.remove(); }; document.body.appendChild(f); })"10.5px""10px"–
Up to 8 gamepads instead of 4navigator.getGamepads().length48–
Soft Navigations interfaces move to their regular place in windowObject.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('PerformanceTiming') + 1]"PerformanceServerTiming""PerformanceSoftNavigation"–
Soft Navigations interfaces move to their regular place in windowObject.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('InterestEvent') + 1]"InputEvent""InteractionContentfulPaint"–
Soft Navigations interfaces move to their regular place in windowObject.getOwnPropertyNames(PerformanceEntry.prototype).indexOf('navigationId') < Object.getOwnPropertyNames(PerformanceEntry.prototype).indexOf('toJSON')falsetrue–
Parsing and rendering
HTML parser: a truncated <! markup declaration becomes a comment instead of eating the restnew DOMParser().parseFromString('a<!d>b', 'text/html').body.innerHTML"a""a<!--d-->b"–
Processing instructions keep their casedocument.createRange().createContextualFragment('<?Foo bar?>').firstChild.target"foo""Foo"–
Processing instructions keep their casedocument.createProcessingInstruction('x', 'A="1"').getAttribute('A')null"1"–
URL parser keeps newlines in DATA: URLs regardless of casenew URL('DATA:,a' + String.fromCharCode(10) + 'b').href"data:,ab""data:,a%0Ab"–
requestAnimationFrame IDs are no longer consumed by internal callbacks[requestAnimationFrame(() => {})].map((a) => { const m = document.createElement('marquee'); m.start(); m.stop(); return requestAnimationFrame(() => {}) - a; })[0]21–
createImageBitmap resolves one microtask later, and a zero resize has its own error(async () => { const o = []; createImageBitmap(new ImageData(1, 1)).then(() => o.push(1)); Promise.resolve().then(() => o.push(2)); await new Promise((r) => setTimeout(r, 50)); return o.join(); })()"1,2""2,1"–
createImageBitmap resolves one microtask later, and a zero resize has its own errorcreateImageBitmap(new ImageData(1, 1), { resizeWidth: 0 }).then(() => 'ok', (e) => e.message.includes('resizeWidth'))falsetrue–
Disabling a focused form control no longer blurs it synchronously((i) => { document.body.append(i); i.focus(); i.disabled = true; const r = document.activeElement === i; i.remove(); return r; })(document.createElement('input'))falsetrue–
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]; })()2550–
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())))falsetruen/a
HarfBuzz 14.4: U+180F is treated as an invisible character(() => { const x = document.createElement('canvas').getContext('2d'); x.font = '20px sans-serif'; return x.measureText('a' + String.fromCharCode(6159) + 'b').width === x.measureText('ab').width; })()falsetruen/a
CSS
New CSS property: window-dragCSS.supports('window-drag', 'move')falsetrue–
New CSS property: window-drag'windowDrag' in document.body.stylefalsetrue–
corner-shape computed values always serialise as superellipse()getComputedStyle(document.documentElement).cornerShape"round""superellipse(1)"–
CSS relative alpha colours: alpha()CSS.supports('color', 'alpha(from red / 50%)')falsetrue–
CSS serialisation: background-size keeps its implicit auto, calc() division reads a / bObject.assign(document.createElement('div').style, { backgroundSize: '10px' }).backgroundSize"10px""10px auto"–
CSS serialisation: background-size keeps its implicit auto, calc() division reads a / bObject.assign(document.createElement('div').style, { width: 'calc(1px / sibling-index())' }).width"calc(1px * (1 / sibling-index()))""calc(1px / sibling-index())"–
Attribute selectors match SVG attribute values case-sensitivelydocument.createRange().createContextualFragment('<svg type=A>').firstChild.matches('[type=a]')truefalse–
CSS Typed OM accepts percentages for opacity and clamps out-of-range computed valuesnew Promise((r) => r(document.createElement('div').attributeStyleMap.set('opacity', CSS.percent(50)))).then(() => 'ok', (e) => e.name)"TypeError""ok"–
Element.pseudo() accepts ::backdrop and ::view-transitiondocument.documentElement.pseudo('::backdrop') !== nullfalsetrue–
Element.pseudo() accepts ::backdrop and ::view-transitiondocument.documentElement.pseudo('::view-transition') !== nullfalsetrue–
Smaller parsing changes: negative overflow-clip-margin, SVG currentTranslate is the same objectCSS.supports('overflow-clip-margin', '-1px')falsetrue–
Smaller parsing changes: negative overflow-clip-margin, SVG currentTranslate is the same object((s) => s.currentTranslate === s.currentTranslate)(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))falsetrue–
New CSS property: scroll-axis-lockCSS.supports('scroll-axis-lock', 'auto')falsetrue–
scroll-marker-group: the tabs and links modes turned offCSS.supports('scroll-marker-group', 'after tabs')truefalsetrue–
<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')falsetrue–
Two new CSS properties: margin-trim and text-decoration-skip-spacesCSS.supports('margin-trim', 'block')falsetrue–
Two new CSS properties: margin-trim and text-decoration-skip-spacesCSS.supports('text-decoration-skip-spaces', 'all')falsetrue–
@media (dynamic-range) with no value now matchesmatchMedia('(dynamic-range)').matchesfalsetrue–
CSS symbols() for list-style-typeCSS.supports('list-style-type', 'symbols(cyclic "*")')falsetrue–
CSS alpha() now requires an explicit alphaCSS.supports('color', 'alpha(from red)')truefalse–
color-mix() and palette-mix() accept percentages that add up to zeroCSS.supports('color', 'color-mix(in srgb, red 0%, blue 0%)')falsetrue–
CSSPerspective.toMatrix() clamps depths below 1pxnew CSSPerspective(CSS.px(0.5)).toMatrix().m34-2-1–
::first-line styles decide the first line's height on their own(() => { const h = document.body.appendChild(document.createElement('div')); const r = h.attachShadow({ mode: 'open' }); const s = new CSSStyleSheet(); s.replaceSync('p { font: 16px serif; margin: 0 } p::first-line { font-size: 40px; line-height: 10px }'); r.adoptedStyleSheets = [s]; const p = r.appendChild(document.createElement('p')); p.textContent = 'x'; const v = p.getBoundingClientRect().height === 10; h.remove(); return v; })()falsetrue–
text-decoration-thickness joins transition: all(() => { const d = document.body.appendChild(document.createElement('div')); d.style.textDecorationThickness = '0px'; getComputedStyle(d).textDecorationThickness; d.style.transition = 'all 10s'; d.style.textDecorationThickness = '10px'; const n = d.getAnimations().length; d.remove(); return n; })()01–
stroke-dasharray animations no longer add to the underlying value(() => { const s = document.body.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg')); s.style.strokeDasharray = '1'; const a = s.animate({ strokeDasharray: ['2', '2'] }, { duration: 1e3, composite: 'add' }); a.pause(); const v = getComputedStyle(s).strokeDasharray; a.cancel(); s.remove(); return v; })()"3px""2px"–
Media and GPU
Web Audio FFT moved to a Rust library: oscillator output changes in the last bits((c) => { const o = c.createOscillator(); o.type = 'triangle'; o.frequency.value = 1e4; const k = c.createDynamicsCompressor(); k.threshold.value = -50; k.knee.value = 40; k.ratio.value = 12; k.attack.value = 0; k.release.value = 0.25; o.connect(k); k.connect(c.destination); o.start(0); return c.startRendering().then((b) => b.getChannelData(0).slice(4500, 5e3).reduce((s, x) => s + Math.abs(x), 0)); })(new OfflineAudioContext(1, 44100, 44100))124.04347527516074124.04347776696522n/a
IIRFilterNode normalises its coefficients in double precision(() => { const m = new Float32Array(1); new IIRFilterNode(new OfflineAudioContext(1, 1, 44100), { feedforward: [1], feedback: [1.1, -1.0999] }).getFrequencyResponse(new Float32Array(1), m, new Float32Array(1)); return m[0]; })()9997.616210937510000–
AudioBufferSourceNode honours loopStart when loopEnd is 0(async () => { const c = new OfflineAudioContext(1, 8, 8192), b = c.createBuffer(1, 4, 8192); b.getChannelData(0).set([1, 2, 3, 4]); const s = new AudioBufferSourceNode(c, { buffer: b, loop: true, loopStart: 2 / 8192 }); s.connect(c.destination); s.start(); return [...(await c.startRendering()).getChannelData(0)]; })()[1,2,3,4,1,2,3,4][1,2,3,4,3,4,3,4]–
getDisplayMedia() reads a new audioSelection option(() => { let r = false; navigator.mediaDevices.getDisplayMedia({ get audioSelection() { r = true; } }).catch(() => {}); return r; })()falsetrue–
RTCIceCandidate parses tls candidates that carry a fingerprintnew RTCIceCandidate({ candidate: 'candidate:1 1 tls 1 1.2.3.4 443 typ host fingerprint sha-256;AA', sdpMid: '0' }).addressnull"1.2.3.4"–
WebGL: stricter texSubImage2D type validation and array attribute locations (ANGLE)(() => { const g = document.createElement('canvas').getContext('webgl2'); g.bindTexture(g.TEXTURE_2D, g.createTexture()); g.texImage2D(g.TEXTURE_2D, 0, g.RGBA, 1, 1, 0, g.RGBA, g.UNSIGNED_BYTE, null); g.texSubImage2D(g.TEXTURE_2D, 0, 0, 0, 1, 1, g.RGBA, g.UNSIGNED_SHORT_4_4_4_4, new Uint16Array(1)); return g.getError(); })()01282–
Software WebGL 2 (SwiftShader) reports 8 draw buffers instead of 6(() => { const g = document.createElement('canvas').getContext('webgl2'); return g.getParameter(g.MAX_DRAW_BUFFERS); })()68n/a
WebGPU: subgroup-size-control ships, two feature names are recognisednavigator.gpu.requestAdapter().then((a) => !!a && a.features.has('subgroup-size-control'))falsetruen/a
IAMF immersive audio in MP4document.createElement('video').canPlayType('audio/mp4; codecs="iamf"')"""probably"–
IAMF immersive audio in MP4MediaSource.isTypeSupported('audio/mp4; codecs="iamf"')falsetrue–
New WGSL language features: buffer_view and texture_formats_tier1navigator.gpu.wgslLanguageFeatures.has('buffer_view')falsetrue–
New WGSL language features: buffer_view and texture_formats_tier1navigator.gpu.wgslLanguageFeatures.has('texture_formats_tier1')falsetrue–
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; })()truefalse–
New WGSL language feature: swizzle_assignmentnavigator.gpu.wgslLanguageFeatures.has('swizzle_assignment')falsetrue–
Web Audio loops short buffers without repeating a sample(async () => { const c = new OfflineAudioContext(1, 16, 48e3); const b = c.createBuffer(1, 7, 48e3); b.getChannelData(0).set([1, 2, 3, 4, 5, 6, 7]); const s = c.createBufferSource(); s.buffer = b; s.loop = true; s.connect(c.destination); s.start(); const r = await c.startRendering(); return Array.from(r.getChannelData(0)).join(); })()"1,2,3,4,5,6,7,7,2,3,4,5,6,7,8,2""1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2"–
WebGPU vertex format snorm10-10-10-2 works without a flagnavigator.gpu.requestAdapter().then((a) => a.requestDevice()).then(async (d) => { d.pushErrorScope('validation'); const m = d.createShaderModule({ code: '@vertex fn v(@location(0) p: vec4f) -> @builtin(position) vec4f { return p; } @fragment fn f() -> @location(0) vec4f { return vec4f(1); }' }); d.createRenderPipeline({ layout: 'auto', vertex: { module: m, buffers: [{ arrayStride: 4, attributes: [{ format: 'snorm10-10-10-2', offset: 0, shaderLocation: 0 }] }] }, fragment: { module: m, targets: [{ format: 'rgba8unorm' }] } }); const e = await d.popErrorScope(); d.destroy(); return e ? e.constructor.name : 'ok'; })"GPUValidationError""ok"n/a
Automation
DevTools no longer runs Proxy traps in a logged object's prototype chain(() => { let hit = false; console.log(Object.create(new Proxy({}, { ownKeys() { hit = true; return []; } }))); return hit; })()truefalsen/a
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; })()truefalsen/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"–
Temporal dates keep the locale's own pattern in Japanese and ChineseTemporal.PlainDate.from('2026-08-27').toLocaleString('ja-JP', { dateStyle: 'long' })"2026/8/27""2026年8月27日"–
Temporal dates keep the locale's own pattern in Japanese and Chinese((f) => f.format(Temporal.PlainDate.from('2026-08-27')) === f.format(new Date(Date.UTC(2026, 7, 27, 12))))(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long', timeZone: 'UTC' }))falsetrue–
Intl.Locale rejects an underscore in the variants option((f) => { try { return f(); } catch (e) { return e.name; } })(() => new Intl.Locale('en', { variants: 'fonipa_scouse' }).toString())"en-fonipa-scouse""RangeError"–
Intl.DateTimeFormat reports the year for week-year patterns'year' in new Intl.DateTimeFormat('te-u-ca-buddhist', { year: 'numeric', month: 'long' }).resolvedOptions()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.

Chrome version history

Every Chrome version since Chrome 100, with the date it reached stable and its newest build on each platform. For every version before the current one that is its final build, the one to compare against when a browser claims that version. Linux often ends a patch or two behind Windows and macOS.

ChromeStable sinceWindowsmacOSLinuxAndroid
Chrome 155currentOct 6, 2026155.0.8059.40155.0.8059.40155.0.8059.39155.0.8059.40
Chrome 154Sep 22, 2026154.0.8037.100154.0.8037.100154.0.8037.97154.0.8037.127
Chrome 153Sep 8, 2026153.0.8010.55153.0.8010.55153.0.8010.52153.0.8010.53
Chrome 152Aug 25, 2026152.0.7977.85152.0.7977.85152.0.7977.82152.0.7977.84
Chrome 151Jul 28, 2026151.0.7922.176151.0.7922.176151.0.7922.173151.0.7922.175
Chrome 150Jun 30, 2026150.0.7871.189150.0.7871.189150.0.7871.186150.0.7871.189
Chrome 149Jun 2, 2026149.0.7827.201149.0.7827.201149.0.7827.200149.0.7827.201
Chrome 148May 5, 2026148.0.7778.218148.0.7778.217148.0.7778.215148.0.7778.217
Chrome 147Apr 7, 2026147.0.7727.139147.0.7727.139147.0.7727.137147.0.7727.138
Chrome 146Mar 10, 2026146.0.7680.180146.0.7680.180146.0.7680.177146.0.7680.178
Chrome 145Feb 10, 2026145.0.7632.162145.0.7632.162145.0.7632.159145.0.7632.161
Chrome 144Jan 13, 2026144.0.7559.135144.0.7559.135144.0.7559.132144.0.7559.133
Older versions: Chrome 100 to 143
ChromeStable sinceWindowsmacOSLinuxAndroid
Chrome 143Dec 2, 2025143.0.7499.194143.0.7499.194143.0.7499.192143.0.7499.194
Chrome 142Oct 28, 2025142.0.7444.177142.0.7444.177142.0.7444.175142.0.7444.173
Chrome 141Sep 30, 2025141.0.7390.125141.0.7390.124141.0.7390.122141.0.7390.123
Chrome 140Sep 2, 2025140.0.7339.210140.0.7339.215140.0.7339.207140.0.7339.208
Chrome 139Aug 5, 2025139.0.7258.157139.0.7258.157139.0.7258.154139.0.7258.160
Chrome 138Jun 24, 2025138.0.7204.185138.0.7204.185138.0.7204.183138.0.7204.180
Chrome 137May 27, 2025137.0.7151.122137.0.7151.122137.0.7151.119137.0.7151.117
Chrome 136Apr 29, 2025136.0.7103.116136.0.7103.116136.0.7103.113136.0.7103.127
Chrome 135Apr 1, 2025135.0.7049.117135.0.7049.117135.0.7049.114135.0.7049.113
Chrome 134Mar 4, 2025134.0.6998.179134.0.6998.167134.0.6998.165134.0.6998.136
Chrome 133Feb 4, 2025133.0.6943.143133.0.6943.143133.0.6943.141133.0.6943.138
Chrome 132Jan 14, 2025132.0.6834.197132.0.6834.162132.0.6834.159132.0.6834.165
Chrome 131Nov 12, 2024131.0.6778.267131.0.6778.267131.0.6778.264131.0.6778.261
Chrome 130Oct 15, 2024130.0.6723.119130.0.6723.119130.0.6723.116130.0.6723.103
Chrome 129Sep 17, 2024129.0.6668.103129.0.6668.103129.0.6668.100129.0.6668.102
Chrome 128Aug 20, 2024128.0.6613.139128.0.6613.139128.0.6613.137128.0.6613.148
Chrome 127Jul 23, 2024127.0.6533.122127.0.6533.122127.0.6533.119127.0.6533.106
Chrome 126Jun 11, 2024126.0.6478.185126.0.6478.185126.0.6478.182126.0.6478.188
Chrome 125May 14, 2024125.0.6422.176125.0.6422.176125.0.6422.141125.0.6422.186
Chrome 124Apr 16, 2024124.0.6367.210124.0.6367.210124.0.6367.207124.0.6367.179
Chrome 123Mar 19, 2024123.0.6312.124123.0.6312.124123.0.6312.122123.0.6312.121
Chrome 122Feb 20, 2024122.0.6261.131122.0.6261.129122.0.6261.128122.0.6261.120
Chrome 121Jan 23, 2024121.0.6167.189121.0.6167.184121.0.6167.184121.0.6167.180
Chrome 120Dec 5, 2023120.0.6099.227120.0.6099.234120.0.6099.224120.0.6099.231
Chrome 119Oct 31, 2023119.0.6045.202119.0.6045.199119.0.6045.199119.0.6045.194
Chrome 118Oct 10, 2023118.0.5993.120118.0.5993.117118.0.5993.117118.0.5993.112
Chrome 117Sep 12, 2023117.0.5938.152117.0.5938.149117.0.5938.149117.0.5938.156
Chrome 116Aug 15, 2023116.0.5845.190116.0.5845.187116.0.5845.187116.0.5845.173
Chrome 115Jul 18, 2023115.0.5790.173115.0.5790.170115.0.5790.170115.0.5790.168
Chrome 114May 30, 2023114.0.5735.200114.0.5735.198114.0.5735.198114.0.5735.197
Chrome 113May 2, 2023113.0.5672.128113.0.5672.126113.0.5672.126113.0.5672.163
Chrome 112Apr 4, 2023112.0.5615.139112.0.5615.137112.0.5615.165112.0.5615.136
Chrome 111Mar 7, 2023111.0.5563.148111.0.5563.146111.0.5563.146111.0.5563.116
Chrome 110Feb 7, 2023110.0.5481.179110.0.5481.177110.0.5481.177110.0.5481.154
Chrome 109Jan 10, 2023109.0.5414.121109.0.5414.119109.0.5414.119109.0.5414.118
Chrome 108Nov 29, 2022108.0.5359.126108.0.5359.124108.0.5359.124108.0.5359.128
Chrome 107Oct 25, 2022107.0.5304.123107.0.5304.121107.0.5304.121107.0.5304.141
Chrome 106Sep 27, 2022106.0.5249.120106.0.5249.119106.0.5249.119106.0.5249.126
Chrome 105Aug 30, 2022105.0.5195.128105.0.5195.125105.0.5195.125105.0.5195.136
Chrome 104Aug 2, 2022104.0.5112.102104.0.5112.101104.0.5112.101104.0.5112.97
Chrome 103Jun 21, 2022103.0.5060.134103.0.5060.134103.0.5060.134103.0.5060.129
Chrome 102May 24, 2022102.0.5005.115102.0.5005.115102.0.5005.115102.0.5005.125
Chrome 101Apr 26, 2022101.0.4951.67101.0.4951.64101.0.4951.64101.0.4951.61
Chrome 100Mar 29, 2022100.0.4896.127100.0.4896.127100.0.4896.127100.0.4896.127

From Chromium Dash's stable release lists, checked every six hours.

How these pages are made

Every comparison uses the same method, so anyone can repeat it. We run Google Chrome's own stable 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. We do not measure on Chrome for Testing: its default testing configuration changes what a page and a server see (see what differs). Each build runs on a fresh profile, before it has applied Google's field-trial configuration; where that configuration changes a check on an installed Chrome, the check says so. 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.