Skip to content

Commit

Permalink
πŸ› [RUM-1863] fix iOS webview detection (#2486)
Browse files Browse the repository at this point in the history
* βœ… [RUM-1863] add tests on browser detection

* πŸ› [RUM-1863] fix iOS webview detection

* πŸ“ add a bit of documentation
  • Loading branch information
BenoitZugmeyer authored Oct 31, 2023
1 parent 5d5935c commit 874ebff
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 9 deletions.
134 changes: 134 additions & 0 deletions packages/core/src/tools/utils/browserDetection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { combine } from '../mergeInto'
import { Browser, detectBrowser } from './browserDetection'

describe('browserDetection', () => {
it('detects IE', () => {
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko',
},
document: { documentMode: 11 },
})
)
).toBe(Browser.IE)
})

it('detects Safari', () => {
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15',
vendor: 'Apple Computer, Inc.',
},
})
)
).toBe(Browser.SAFARI)

// Emulates Safari detection if 'navigator.vendor' is removed one day
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15',
},
})
)
).toBe(Browser.SAFARI)

// Webview on iOS
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/20B110 [FBAN/FBIOS;FBDV/iPhone14,5;FBMD/iPhone;FBSN/iOS;FBSV/16.1.2;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5]',
vendor: 'Apple Computer, Inc.',
},
})
)
).toBe(Browser.SAFARI)
})

it('detects Chromium', () => {
// Google Chrome 118
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
vendor: 'Google Inc.',
},
chrome: {},
})
)
).toBe(Browser.CHROMIUM)

// Headless chrome
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4512.0 Safari/537.36',
vendor: 'Google Inc.',
},
})
)
).toBe(Browser.CHROMIUM)

// Microsoft Edge 89
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36 Edg/89.0.774.54',
vendor: 'Google Inc.',
},
chrome: {},
})
)
).toBe(Browser.CHROMIUM)
})

it('other browsers', () => {
// Firefox 10
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: { userAgent: 'Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0' },
})
)
).toBe(Browser.OTHER)

// Firefox 120
expect(
detectBrowser(
fakeWindowWithDefaults({
navigator: {
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0',
},
})
)
).toBe(Browser.OTHER)
})

function fakeWindowWithDefaults(partial: any): Window {
return combine(
{
navigator: {
userAgent: '',
},
document: {},
},
partial
) as Window
}
})
49 changes: 40 additions & 9 deletions packages/core/src/tools/utils/browserDetection.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
let browserIsIE: boolean | undefined
// Exported only for tests
export const enum Browser {
IE,
CHROMIUM,
SAFARI,
OTHER,
}

export function isIE() {
return browserIsIE ?? (browserIsIE = Boolean((document as any).documentMode))
return detectBrowserCached() === Browser.IE
}

let browserIsChromium: boolean | undefined
export function isChromium() {
return (
browserIsChromium ??
(browserIsChromium = !!(window as any).chrome || /HeadlessChrome/.test(window.navigator.userAgent))
)
return detectBrowserCached() === Browser.CHROMIUM
}

let browserIsSafari: boolean | undefined
export function isSafari() {
return browserIsSafari ?? (browserIsSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent))
return detectBrowserCached() === Browser.SAFARI
}

let browserCache: Browser | undefined
function detectBrowserCached() {
return browserCache ?? (browserCache = detectBrowser())
}

// Exported only for tests
export function detectBrowser(browserWindow: Window = window) {
const userAgent = browserWindow.navigator.userAgent
if ((browserWindow as any).chrome || /HeadlessChrome/.test(userAgent)) {
return Browser.CHROMIUM
}

if (
// navigator.vendor is deprecated, but it is the most resilient way we found to detect
// "Apple maintained browsers" (AKA Safari). If one day it gets removed, we still have the
// useragent test as a semi-working fallback.
browserWindow.navigator.vendor?.indexOf('Apple') === 0 ||
(/safari/i.test(userAgent) && !/chrome|android/i.test(userAgent))
) {
return Browser.SAFARI
}

if ((browserWindow.document as any).documentMode) {
return Browser.IE
}

return Browser.OTHER
}

0 comments on commit 874ebff

Please sign in to comment.