mirror of
https://github.com/galacean/engine.git
synced 2026-06-01 16:21:19 +08:00
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:
@@ -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+)?/);
|
||||
|
||||
136
tests/src/core/base/SystemInfo.test.ts
Normal file
136
tests/src/core/base/SystemInfo.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user