Fix incorrect iOS version detection on Safari 26 due to UA reduction (#2863)

* fix: incorrect iOS version detection on Safari 26 due to UA reduction
This commit is contained in:
iamkun-2
2025-12-23 19:34:32 +08:00
committed by GitHub
parent 01a56df5d1
commit 5af843440e
2 changed files with 149 additions and 5 deletions

View File

@@ -1,8 +1,8 @@
import { AssetPromise } from "./asset/AssetPromise";
import { GLCapabilityType } from "./base/Constant";
import { Engine } from "./Engine";
import { Platform } from "./Platform";
import { TextureFormat } from "./texture";
import { AssetPromise } from "./asset/AssetPromise";
/**
* Access operating system, platform and hardware information.
@@ -28,6 +28,16 @@ export class SystemInfo {
return window.devicePixelRatio;
}
private static _parseAppleMobileOSVersion(userAgent: string, osPrefix: string): string {
// Since iOS 26, Safari freezes UA OS version at 18.6, so Version/xx is more reliable
// Use Version/ if available, otherwise fallback to OS version
let v = userAgent.match(/Version\/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
if (v) return `${osPrefix} ${v[1]}.${v[2] || 0}.${v[3] || 0}`;
v = userAgent.match(/OS (\d+)_(\d+)(?:_(\d+))?/);
return v ? `${osPrefix} ${v[1]}.${v[2]}.${v[3] || 0}` : osPrefix;
}
/**
* @internal
*/
@@ -53,12 +63,10 @@ export class SystemInfo {
let v: RegExpMatchArray;
switch (SystemInfo.platform) {
case Platform.IPhone:
v = userAgent.match(/OS (\d+)_?(\d+)?_?(\d+)?/);
this.operatingSystem = v ? `iPhone OS ${v[1]}.${v[2] || 0}.${v[3] || 0}` : "iPhone OS";
this.operatingSystem = this._parseAppleMobileOSVersion(userAgent, "iPhone OS");
break;
case Platform.IPad:
v = userAgent.match(/OS (\d+)_?(\d+)?_?(\d+)?/);
this.operatingSystem = v ? `iPad OS ${v[1]}.${v[2] || 0}.${v[3] || 0}` : "iPad OS";
this.operatingSystem = this._parseAppleMobileOSVersion(userAgent, "iPad OS");
break;
case Platform.Android:
v = userAgent.match(/Android (\d+).?(\d+)?.?(\d+)?/);

View File

@@ -0,0 +1,136 @@
import { Platform, SystemInfo } from "@galacean/engine-core";
import { describe, expect, it, beforeEach, afterEach } from "vitest";
// Cast to access internal methods
const SystemInfoInternal = SystemInfo as typeof SystemInfo & {
_initialize(): void;
};
describe("SystemInfo", () => {
let originalUserAgent: string;
beforeEach(() => {
originalUserAgent = navigator.userAgent;
});
afterEach(() => {
// Restore original userAgent
Object.defineProperty(navigator, "userAgent", {
value: originalUserAgent,
configurable: true
});
// Reset SystemInfo state
SystemInfo.platform = Platform.Unknown;
SystemInfo.operatingSystem = "";
});
const mockUserAgent = (ua: string) => {
Object.defineProperty(navigator, "userAgent", {
value: ua,
configurable: true
});
};
describe("_parseAppleMobileOSVersion for iPhone", () => {
// iOS 26+ Safari: UA freezes OS version at 18.6, use Version/xx to infer real iOS version
it("Safari on iOS 26 (UA frozen at 18.6)", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 26.0.0");
});
it("Safari on iOS 26.1.2 (UA frozen at 18.6)", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.1.2 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 26.1.2");
});
// Chrome on iOS: OS version is accurate
it("Chrome on iOS 26", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 26_0_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.119 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 26.0.0");
});
// Firefox on iOS: OS version is accurate
it("Firefox on iOS 26", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/140.2 Mobile/15E148 Safari/605.1.15"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 26.0.0");
});
// Edge on iOS: Use Version/ (may lose patch version, but acceptable)
it("Edge on iOS 26", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 26_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 EdgiOS/46.3.30 Mobile/15E148 Safari/605.1.15"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 26.0.0");
});
// Opera on iOS: OS version is accurate
it("Opera on iOS 26", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 OPiOS/16.0.14 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 26.0.0");
});
// Legacy Safari (before iOS 26): OS version is accurate
it("Safari on iOS 18.4", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 18.4.0");
});
// WebView or in-app browser: No Version/, fallback to OS version
it("WebView on iOS (no Version/)", () => {
mockUserAgent(
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPhone);
expect(SystemInfo.operatingSystem).to.eq("iPhone OS 18.6.0");
});
});
describe("_parseAppleMobileOSVersion for iPad", () => {
// iPad Safari with frozen UA
it("Safari on iPadOS 26 (UA frozen at 18.6)", () => {
mockUserAgent(
"Mozilla/5.0 (iPad; CPU OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPad);
expect(SystemInfo.operatingSystem).to.eq("iPad OS 26.0.0");
});
// Chrome on iPad
it("Chrome on iPadOS 26", () => {
mockUserAgent(
"Mozilla/5.0 (iPad; CPU OS 26_0_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.156 Mobile/15E148 Safari/604.1"
);
SystemInfoInternal._initialize();
expect(SystemInfo.platform).to.eq(Platform.IPad);
expect(SystemInfo.operatingSystem).to.eq("iPad OS 26.0.0");
});
});
});