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 157 is the current stable version, since Nov 3, 2026. Chrome 158 reaches stable on Nov 17, 2026, after an early rollout to a small share of users from Nov 4, 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 157Oct 12, 2026Oct 14, 2026Oct 21, 2026Nov 3, 20262 wkStable now
Chrome 158Oct 26, 2026Oct 28, 2026Nov 4, 2026Nov 17, 20262 wkEarly stable
Chrome 159Nov 9, 2026Nov 11, 2026Nov 18, 2026Dec 8, 20263 wkBeta
Chrome 160LTSNov 30, 2026Dec 2, 2026Dec 9, 2026Jan 5, 20274 wkUpcoming
Chrome 161Dec 14, 2026Dec 16, 2026Jan 6, 2027Jan 19, 20272 wkUpcoming
Chrome 162Jan 11, 2027Jan 13, 2027Jan 20, 2027Feb 2, 20272 wkUpcoming
Chrome 163Jan 25, 2027Jan 27, 2027Feb 3, 2027Feb 16, 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 → Chrome 156 → Chrome 157 · 208 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 155Chrome 156Chrome 157This 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""Not:A-Brand;v=8, Chromium;v=156, Google Chrome;v=156""Not-A.Brand;v=99, Google Chrome;v=157, Chromium;v=157"–
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""toJSON""paintTime"–
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–
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
Protected Audience (interest-group and ad-auction) methods removed from navigatortypeof navigator.joinAdInterestGroup"function""undefined"n/a
Protected Audience (interest-group and ad-auction) methods removed from navigator'runAdAuction' in navigatortruefalsen/a
HTML-in-canvas: drawElementImage, ElementImage and canvas paint eventstypeof CanvasRenderingContext2D.prototype.drawElementImage"undefined""function"–
HTML-in-canvas: drawElementImage, ElementImage and canvas paint eventstypeof ElementImage"undefined""function"–
HTML-in-canvas: drawElementImage, ElementImage and canvas paint events'onpaint' in HTMLCanvasElement.prototypefalsetrue–
New HTML insertion and streaming methods, and Trusted Types parser optionstypeof Element.prototype.appendHTML"undefined""function"–
New HTML insertion and streaming methods, and Trusted Types parser optionstypeof Element.prototype.streamHTML"undefined""function"–
New HTML insertion and streaming methods, and Trusted Types parser optionstypeof TrustedTypePolicy.prototype.createParserOptions"undefined""function"–
window's property order moves againObject.getOwnPropertyNames(window).indexOf('ProgressEvent') > Object.getOwnPropertyNames(window).indexOf('Lock')falsetrue–
srgb-linear and display-p3-linear color spaces(() => { try { return new ImageData(1, 1, { colorSpace: 'srgb-linear' }).colorSpace; } catch (e) { return e.name; } })()"TypeError""srgb-linear"–
WebTransport request headers, responseHeaders and reliability'responseHeaders' in (globalThis.WebTransport?.prototype ?? {})falsetrue–
WebTransport request headers, responseHeaders and reliabilitytypeof globalThis.WebTransport?.supportsReliableOnly"undefined""boolean"–
Sanitizer setters return booleanstypeof new Sanitizer().removeUnsafe()"undefined""boolean"–
HTML parser ignores a head tag inside noscriptnew DOMParser().parseFromString('<head><noscript><head><style></style>', 'text/html').querySelector('noscript').childElementCount01–
Uint8Array.setFromHex writes the valid prefix before throwing(() => { const u = new Uint8Array(16); try { u.setFromHex('aa'.repeat(4) + 'zz' + 'aa'.repeat(11)); } catch (e) {} return u[0]; })()0170–
RegExp: class strings fold case under the v and i flagsnew RegExp('[\\q{a}]', 'vi').test('A')falsetrue–
JavaScript parser: await in static fields, conditional chains after arrow functions(() => { try { return (0, eval)('(function () { var await = 7; return class { static x = await; }; })().x'); } catch (e) { return e.name; } })()"SyntaxError"7n/a
JavaScript parser: await in static fields, conditional chains after arrow functions(() => { try { (0, eval)('0 ? 0 : () => {} ? 1 : 2'); return 'parsed'; } catch (e) { return e.name; } })()"parsed""SyntaxError"n/a
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""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"–
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())))falsetruetruefalsen/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
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"–
Text inputs measure clientWidth and clientLeft from the content box(() => { const i = document.body.appendChild(document.createElement('input')); i.style.cssText = 'appearance: none; box-sizing: content-box; width: 100px; border: 5px solid; padding: 0 20px'; const v = `${i.clientWidth},${i.clientLeft}`; i.remove(); return v; })()"140,5""100,25"–
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')falsetruefalse–
@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"–
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%"–
CSS text-decoration-insetCSS.supports('text-decoration-inset', '1px')falsetrue–
CSS text-decoration-insetgetComputedStyle(document.documentElement).length479480n/a
CSS text-overflow with a string valueCSS.supports('text-overflow', '"x"')falsetrue–
position-visibility: anchor-visible and anchor-validCSS.supports('position-visibility', 'anchor-visible')falsetrue–
position-visibility: anchor-visible and anchor-validCSS.supports('position-visibility', 'anchor-valid')falsetrue–
position-visibility: anchor-visible and anchor-valid(() => { const d = document.createElement('div'); d.style.positionVisibility = 'anchors-visible'; return d.style.positionVisibility; })()"anchors-visible""anchor-visible"–
appearance no longer changes display on non-widget elements(() => { const s = document.body.appendChild(document.createElement('span')); s.style.appearance = 'auto'; const v = getComputedStyle(s).display; s.remove(); return v; })()"inline-block""inline"–
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
AudioDecoder.isConfigSupported() no longer requires a descriptionAudioDecoder.isConfigSupported({ codec: 'flac', sampleRate: 48e3, numberOfChannels: 2 }).then((r) => r.supported, (e) => e.name)"TypeError"true–
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–
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"–
Time-zone data: Inuvik keeps summer time, and the EST5EDT-style zones stand alonenew Intl.DateTimeFormat('en-US', { timeZone: 'America/Winnipeg', dateStyle: 'medium', timeStyle: 'long' }).format(new Date(Date.UTC(2026, 11, 1, 8)))"Dec 1, 2026, 2:00:00 AM CST""Dec 1, 2026, 3:00:00 AM CDT"–
Time-zone data: Inuvik keeps summer time, and the EST5EDT-style zones stand alonenew Intl.DateTimeFormat('en-US', { timeZone: 'Europe/London', timeZoneName: 'long' }).format(new Date(Date.UTC(2026, 0, 15, 12)))"1/15/2026, GMT+00:00""1/15/2026, Greenwich Mean Time"–
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–
Temporal toLocaleString with only era keeps the date fieldsTemporal.PlainDate.from('2026-11-03').toLocaleString('en-US', { era: 'short' }).includes('2026')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 157currentNov 3, 2026157.0.8103.51157.0.8103.51157.0.8103.50157.0.8103.50
Chrome 156Oct 20, 2026156.0.8078.82156.0.8078.82156.0.8078.79156.0.8078.81
Chrome 155Oct 6, 2026155.0.8059.86155.0.8059.86155.0.8059.84155.0.8059.88
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
Older versions: Chrome 100 to 145
ChromeStable sinceWindowsmacOSLinuxAndroid
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
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.