Skip to content

v11.2.0

Choose a tag to compare

@doronz88 doronz88 released this 28 Aug 14:20
· 49 commits to master since this release
7805eb9

Highlights

webinspector cdp can drive JSContext debuggables

Any process that made a bare JSContext inspectable — jsContext.isInspectable = true on iOS >= 16.4, or a development-signed build — is now listed alongside Safari's tabs and can be debugged from Chrome DevTools, the same way webinspector js-shell and Safari's own Develop menu have always offered it:

pymobiledevice3 webinspector cdp
# then open http://127.0.0.1:9222/ and pick "myapp (1234): JSContext"

Listing it was the easy half. Such a debuggable is JavaScriptCore's inspector, not WebKit's, and it differs in four ways that each had to be bridged:

  • It implements no Target domain. Nothing is announced on attach, and messages are exchanged un-multiplexed — replies carry a top-level id and arrive on the inspector's result table rather than wrapped in Target.dispatchMessageFromTarget.
  • Page identifiers are numbered per application, so every JSContext is page 1 and they all collided under one id. Targets are now addressed as <application>:<page>.
  • It announces no execution context, and Chrome's frontend refuses to evaluate anything until it knows one — the context picker read "Not selected" and the console prompt silently swallowed every line. One is synthesized on Runtime.enable.
  • Its console output is gated behind Console.enable, which Chrome's JavaScript-only frontend never sends (it expects V8's behaviour, where Runtime.enable is enough). The bridge sends it on the frontend's behalf.

JSContexts are advertised as Chrome's node target type and opened with js_app.html, the JavaScript-only DevTools frontend — Console, Sources and Memory, with no Elements or Network panel for a target that has no document behind it. They are titled after the process hosting them, since they all share the name JSContext and have no URL to tell them apart.

WebinspectorService.wir_events changed type as part of this — see Breaking below.

🐛 Concurrent debugger sessions no longer consume each other's events

webinspectord events carry no id, and every receive loop popped them from one shared list. With two sessions open on the same bridge — two JSContexts, or two Safari tabs — whichever loop woke first took the other's events: one client saw everything twice and the other went silent. Every forwarded message is tagged with the session it belongs to (WIRDestinationKey), so they are now queued per session.

🐛 The CDP target listing no longer accumulates dead pages, and refreshes itself

Three fixes to the landing page at http://127.0.0.1:9222/:

  • An application's listing is its complete set of pages, not a delta, but it was merged into the cached one without ever dropping what it no longer mentions. Every tab ever opened stayed listed for the rest of the session, as a dead target that could not be attached to.
  • The page was rendered once, so a tab opened or closed on the device needed a manual reload. It now keeps itself current, and pauses while it is not the visible tab.
  • Serving /json blocked for a fixed half second waiting for listings it had just requested — 503 ms of a 506 ms response. webinspectord pushes a fresh listing on its own whenever a page opens, closes or navigates, so that wait bought nothing; answering from the already-live state takes ~2 ms.

Titles and URLs reported by the device are also escaped now, instead of being interpolated into the landing page verbatim.

🐛 A refused backup reports the free space the device actually requires

DLMessagePurgeDiskSpace's CACHE_DELETE_AMOUNT is not the backup's space requirement — it overstates it by a flat 2 GiB, leaving users to reverse-engineer the real threshold from two runs (#1879). -[MBDriveBackupEngine _prepareFreeSpace] makes the figure recoverable, so it is derived and reported directly, and attached to the MBErrorDomain/105 refusal that follows — which now raises NotEnoughDiskSpaceError instead of surfacing as a raw dict. When the figures don't fit the model, the raw request is reported instead, so an untested iOS version degrades to the old message rather than printing a wrong number.

The error also explains why the requirement can exceed the device's own used space: a 256 GB iPhone asking for ~967 GB reads as a bug until you know the device counts hardlinked and cloned files at full size, and that upload_files really does land a device-side clone family as N full copies.

⚠️ Breaking

WebinspectorService.wir_events is now dict[str, list[Any]], keyed by session id, instead of a single list[Any]. Use WebinspectorService.session_events(session_id) to reach one session's queue. find_page_id() now takes an application-qualified identifier (make_target_id() builds one); a bare page identifier still resolves to the first application reporting it, exactly as before.

For clients of webinspector cdp, /json target ids changed shape from 1 to PID:440:1. CDP clients treat these as opaque, so Chrome DevTools, VS Code's js-debug and Puppeteer are unaffected.

What's Changed

Full Changelog: v11.1.6...v11.2.0