You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(Platform): allow DI override of userAgent #33751
However in SSR there's a way to get userAgent from the request and accessing global entities is bad practice. Would be great if there was a token for userAgent like we do here, or maybe at least inject(DOCUMENT).defaultView?.navigator?.userAgent way of accessing it, rather than just global navigator.userAgent
Worth noting that every navigator.userAgent read in there is short-circuited by this.isBrowser, which comes from PLATFORM_ID. On the server none of them execute and all the flags are already false, so a userAgent token by itself wouldn't change anything under SSR. To get value out of it you'd also have to drop the isBrowser gate, and that's the risky half: the flags would start coming back true during server rendering, and everything that consumes them (tooltip, input, slider, paginated-tab-header, interactivity-checker, text-field/autosize, overlay-outside-click-dispatcher, layout/media-matcher) uses them for event wiring, focus handling and DOM measurement that only happens in the browser anyway. New hydration-mismatch surface, no change to the rendered markup.
For the override itself, Platform is a plain root-provided service, so replacing it wholesale already works today and is how the CDK's own tests do it:
See focus-monitor.spec.ts. That gets you server-detected values without a new token.
The inject(DOCUMENT).defaultView?.navigator half seems like the more defensible ask on its own, since ViewportRuler, FocusMonitor and HighContrastModeDetector already reach the window through _document.defaultView || window rather than the global.
platform-server's DOCUMENT isn't a bare document. parseDocument builds it with domino.createWindow(html, url) and returns window.document, so defaultView is a live domino Window. It does carry a navigator (domino's NavigatorID), but its userAgent is the empty string, and there's no chrome or CSS on that window, so under the default server setup the regexes would come out false the way you describe.
Where it stops holding is the case the comment above isBrowser is about, a document shimmed without you knowing. platform-server can run with DOM emulation off against a global jsdom or happy-dom, and both of those ship a default UA along the lines of Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/30.0.1. defaultView?.navigator?.userAgent picks that up, WEBKIT goes true on the server, and everything downstream of it branches the wrong way during rendering. Same class of false positive PLATFORM_ID was brought in for in #10659.
One more wrinkle: IOS uses !('MSStream' in window), and in throws on undefined, so that one needs an actual guard, optional chaining won't cover it.
Moving the global reads to inject(DOCUMENT).defaultView still looks worth doing to me, it lines up with what ViewportRuler and FocusMonitor already do. It just makes the reads safer rather than making the platform check redundant.
Activity
lazerg commentedon Aug 31, 2026
Worth noting that every
navigator.userAgentread in there is short-circuited bythis.isBrowser, which comes fromPLATFORM_ID. On the server none of them execute and all the flags are alreadyfalse, so a userAgent token by itself wouldn't change anything under SSR. To get value out of it you'd also have to drop theisBrowsergate, and that's the risky half: the flags would start coming back true during server rendering, and everything that consumes them (tooltip,input,slider,paginated-tab-header,interactivity-checker,text-field/autosize,overlay-outside-click-dispatcher,layout/media-matcher) uses them for event wiring, focus handling and DOM measurement that only happens in the browser anyway. New hydration-mismatch surface, no change to the rendered markup.For the override itself,
Platformis a plain root-provided service, so replacing it wholesale already works today and is how the CDK's own tests do it:See focus-monitor.spec.ts. That gets you server-detected values without a new token.
The
inject(DOCUMENT).defaultView?.navigatorhalf seems like the more defensible ask on its own, sinceViewportRuler,FocusMonitorandHighContrastModeDetectoralready reach the window through_document.defaultView || windowrather than the global.waterplea commentedon Aug 31, 2026
isBrowser guard can be safely removed if the userAgent part was properly handled, for example it can be:
And then all the regex tests would return false. Accessing window and CSS can also be rewritten as
inject(DOCUMENT).defaultViewetc.lazerg commentedon Aug 31, 2026
platform-server's
DOCUMENTisn't a bare document.parseDocumentbuilds it withdomino.createWindow(html, url)and returnswindow.document, sodefaultViewis a live dominoWindow. It does carry anavigator(domino'sNavigatorID), but itsuserAgentis the empty string, and there's nochromeorCSSon that window, so under the default server setup the regexes would come out false the way you describe.Where it stops holding is the case the comment above
isBrowseris about, a document shimmed without you knowing. platform-server can run with DOM emulation off against a global jsdom or happy-dom, and both of those ship a default UA along the lines ofMozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/30.0.1.defaultView?.navigator?.userAgentpicks that up,WEBKITgoes true on the server, and everything downstream of it branches the wrong way during rendering. Same class of false positivePLATFORM_IDwas brought in for in #10659.One more wrinkle:
IOSuses!('MSStream' in window), andinthrows onundefined, so that one needs an actual guard, optional chaining won't cover it.Moving the global reads to
inject(DOCUMENT).defaultViewstill looks worth doing to me, it lines up with whatViewportRulerandFocusMonitoralready do. It just makes the reads safer rather than making the platform check redundant.