Fix macOS detection in desktop app and Chromium

This commit addresses an issue where macOS was incorrectly identified as
iPadOS in Chromium-based browsers. The root cause was related to touch
support detection being inaccurately triggered on Chromium browsers,
leading to misidentification.

The bug caused two issues:

1. Desktop version: Script execution on macOS did not work as the
   desktop app wrongly assumed that it was running on iPadOS.
2. Web and desktop version: The UI didn't default to macOS, presuming an
   iPadOS environment.

This bug was exclusive to Chromium browsers on macOS. Firefox and Safari
didn't exhibit this behavior, as they handle touch event browser API
as differently and initially expected.

Key changes:

- Improve touch support detection to accurately differentiate between
  macOS and iPadOS by removing an identification method used that is not
  reliable for Chromium-based browsers.
- Update user agent detection to correctly identify Electron-based
  applications as macOS even without needing the information from the
  preloader context.
This commit is contained in:
undergroundwires
2024-01-03 11:00:34 +01:00
parent 40f5eb8334
commit dc30825232
5 changed files with 33 additions and 22 deletions

View File

@@ -57,6 +57,7 @@ export const BrowserConditions: readonly BrowserCondition[] = [
{
operatingSystem: OperatingSystem.iPadOS,
existingPartsInSameUserAgent: ['Macintosh'], // Reported by Safari on iPads running ≥ iPadOS 13
notExistingPartsInUserAgent: ['Electron'], // Electron supports only macOS, not iPadOS
touchSupport: TouchSupportExpectation.MustExist, // Safari same user agent as desktop macOS
},
{

View File

@@ -10,43 +10,48 @@ export interface BrowserTouchSupportAccessor {
navigatorMaxTouchPoints: () => number | undefined;
windowMatchMediaMatches: (query: string) => boolean;
documentOntouchend: () => undefined | unknown;
windowTouchEvent: () => undefined | unknown;
}
/*
Touch support checks are inconsistent across different browsers and OS.
`✅` and `❌` indicate correct and incorrect detections, respectively.
*/
const TouchSupportChecks: ReadonlyArray<(accessor: BrowserTouchSupportAccessor) => boolean> = [
/*
Mobile: Chrome, Safari, Firefox on iOS and Android
Touch-enabled Windows laptop: Chrome
(Chromium has removed ontouch* events on desktop since Chrome 70+.)
❌ Touch-enabled Windows laptop: Firefox
Mobile (iOS & Android): ✅ Chrome, Safari, Firefox
Touch-enabled Windows laptop: Chrome (reports no touch), ❌ Firefox (reports no touch)
Chromium has removed ontouch* events on desktop since Chrome 70+
Non-touch macOS: ✅ Firefox, ✅ Safari, ✅ Chromium
*/
(accessor) => accessor.documentOntouchend() !== undefined,
/*
Mobile: Chrome, Safari, Firefox on iOS and Android
Touch-enabled Windows laptop: Chrome
❌ Touch-enabled Windows laptop: Firefox
Mobile (iOS & Android): ✅ Chrome, Safari, Firefox
Touch-enabled Windows laptop: Chrome, ❌ Firefox (reports no touch)
Non-touch macOS: ✅ Firefox, ✅ Safari, ✅ Chromium
*/
(accessor) => {
const maxTouchPoints = accessor.navigatorMaxTouchPoints();
return maxTouchPoints !== undefined && maxTouchPoints > 0;
},
/*
Mobile: Chrome, Safari, Firefox on iOS and Android
Touch-enabled Windows laptop: Chrome
❌ Touch-enabled Windows laptop: Firefox
Mobile (iOS & Android): ✅ Chrome, Safari, Firefox
Touch-enabled Windows laptop: Chrome, ❌ Firefox (reports no touch)
Non-touch macOS: ✅ Firefox, ✅ Safari, ✅ Chromium
*/
(accessor) => accessor.windowMatchMediaMatches('(any-pointer: coarse)'),
/*
✅ Mobile: Chrome, Safari, Firefox on iOS and Android
✅ Touch-enabled Windows laptop: Chrome
❌ Touch-enabled Windows laptop: Firefox
Do not check window.TouchEvent === undefined, as it incorrectly
reports touch support on Chromium macOS even though there is no
touch support.
Mobile (iOS & Android): ✅ Chrome, ✅ Safari, ✅ Firefox
Touch-enabled Windows laptop: ✅ Chrome, ❌ Firefox (reports no touch)
Non-touch macOS: ✅ Firefox, ✅ Safari, ❌ Chromium (reports touch)
*/
(accessor) => accessor.windowTouchEvent() !== undefined,
];
const GlobalTouchSupportAccessor: BrowserTouchSupportAccessor = {
navigatorMaxTouchPoints: () => navigator.maxTouchPoints,
windowMatchMediaMatches: (query: string) => window.matchMedia(query)?.matches,
documentOntouchend: () => document.ontouchend,
windowTouchEvent: () => window.TouchEvent,
} as const;

View File

@@ -37,6 +37,8 @@ export const BrowserOsTestCases: ReadonlyArray<BrowserOsTestCase> = [
'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1',
// UC Browser:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 UBrowser/6.0.1308.1016 Safari/537.36',
// Electron:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.54 Electron/27.0.0 Safari/537.36',
],
}),
...createTests({
@@ -56,6 +58,8 @@ export const BrowserOsTestCases: ReadonlyArray<BrowserOsTestCase> = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.82 Safari/537.36 OPR/29.0.1795.41 (Edition beta)',
// Edge:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0',
// Electron:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.54 Electron/27.0.0 Safari/537.36',
],
}),
...createTests({
@@ -68,6 +72,8 @@ export const BrowserOsTestCases: ReadonlyArray<BrowserOsTestCase> = [
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
// Edge:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.188',
// Electron:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.54 Electron/27.0.0 Safari/537.36',
],
}),
...createTests({

View File

@@ -58,6 +58,11 @@ export const MobileSafariDetectionTestCases: ReadonlyArray<PlatformTestCase> = [
operatingSystem: OperatingSystem.macOS,
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
},
{
deviceInfo: 'macOS (Electron)',
operatingSystem: OperatingSystem.macOS,
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.54 Electron/27.0.0 Safari/537.36',
},
{
deviceInfo: 'iPad (iPadOS 17)',
operatingSystem: OperatingSystem.iPadOS,

View File

@@ -32,11 +32,6 @@ describe('TouchSupportDetection', () => {
}),
expectedTouch: true,
},
{
description: 'detects touch capability with defined window.TouchEvent',
expectedTouch: true,
accessor: createMockAccessor({ windowTouchEvent: () => class {} }),
},
];
testScenarios.forEach(({ description, accessor, expectedTouch }) => {
it(`${description} - returns ${expectedTouch}`, () => {
@@ -56,7 +51,6 @@ function createMockAccessor(
navigatorMaxTouchPoints: () => undefined,
windowMatchMediaMatches: () => false,
documentOntouchend: () => undefined,
windowTouchEvent: () => undefined,
};
return {
...defaultTouchSupport,