Skip to content

iOS/macOS: align user agent with analytics-swift instead of creating a WKWebView on the main thread #221

Closed

Description

@zachdean

Summary

The plugin's iOS and macOS getContext still reads the user agent from a WKWebView, using code copied from an old version of analytics-swift. analytics-swift replaced that code in 2024 with a generated string (analytics-swift#341, released in 1.5.12) after it caused hangs and crashes. This plugin never picked up the change. I'd like to bring it in line with analytics-swift's UserAgent.swift.

Current behaviour

packages/core/ios/segment_analytics/Sources/segment_analytics/AppleUtils.swift (same code in macos/):

override var userAgent: String? {
    if Thread.isMainThread {
        userAgent = WKWebView().value(forKey: "userAgent") as? String
    } else {
        DispatchQueue.main.sync { userAgent = WKWebView().value(forKey: "userAgent") as? String }
    }
}

The first WKWebView in a process has to start WebKit. In a profile build of our app on an iPhone 12 (iOS 26.3, Flutter 3.41.7, segment_analytics 1.1.12), this blocked the main thread for about 290ms at launch and froze our splash animation. Since Flutter 3.29 (iOS) and 3.35 (macOS), Dart runs on the main thread, so a blocked main thread stops Flutter frames directly.

As a workaround we overrode AnalyticsPlatform.instance so getContext generates the string the way analytics-swift does. On the same device, getContext main-thread time dropped from ~290ms to ~0.5ms, and dropped frames during the splash went from 47 to 13.

How analytics-swift got here

  • analytics-swift#69 (2021): crash creating the WKWebView off the main thread. #70 added the main.sync fallback this plugin still has.
  • analytics-swift#229 (2023): hangs of 10s+ at launch from the same call. Made lazy in 1.4.6.
  • analytics-swift#353 (2024): the lazy WKWebView still crashed with MallocStackLogging enabled. The maintainer pointed to #341.
  • analytics-swift#341 (2024): "Start generating UserAgent instead of relying on Webkit". Stops importing WebKit.

#205 is the Android equivalent: it moves slow getContext work off the main thread.

Proposal

Generate the user agent exactly as analytics-swift does, and drop import WebKit on iOS and macOS:

  • iOS: Mozilla/5.0 ({model}; CPU {model} OS {version} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148, where {model} is iPhone or iPad, and {version} is major_minor, with _patch added when the patch is non-zero. Computed once.
  • macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko), which WebKit hard-codes on macOS.
  • tvOS: unknown, unchanged.

Because this follows analytics-swift rather than WebKit, the string changes slightly in two cases. Native apps on analytics-swift already send these values:

Today (WKWebView) After (analytics-swift)
iPhone, iOS 26.5 iPhone; CPU iPhone OS 18_7 iPhone; CPU iPhone OS 26_5
iPad, iOS 26.5 iPad; CPU OS 18_7 iPad; CPU iPad OS 26_5
iPhone, iOS 16.0 identical identical
macOS 26.5 identical identical

On iOS 26, WebKit freezes the OS version at 18_x, and analytics-swift reports the real version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions