Skip to content

fix: surface an error when a picked media item can't be loaded - #25997

Open
jkmassel wants to merge 8 commits into
trunkfrom
jkmassel/implement-handoff
Open

fix: surface an error when a picked media item can't be loaded#25997
jkmassel wants to merge 8 commits into
trunkfrom
jkmassel/implement-handoff

Conversation

@jkmassel

@jkmassel jkmassel commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes a bug where a device photo that fails to load from the Photos picker leaves a Media Library upload spinning forever instead of reporting a failure — and distinguishes the specific failure that motivated this from ordinary load errors.

Root Cause

ItemProviderMediaExporter.export loads the picked file via NSItemProvider.loadFileRepresentation. On failure its completion handler logged at debug level and returned without calling onError, so the export never completed and the import stayed pending indefinitely — the spinner never resolved.

Fix

The load-failure path now always calls onError, and splits by cause:

  • Provider process died (XPC). When the error chain contains an XPC connection failure (NSCocoaErrorDomain xpcConnectionInterrupted / Invalid / ReplyInvalid, nested under the NSItemProviderError), surface a user-facing error — a Lockdown-Mode-specific message ("…while Lockdown Mode is on.") when Lockdown Mode is enabled, otherwise the cannotLoadItem size hint ("…It may be too large to import.") — and emit a media_import_item_unavailable Tracks event carrying the error domains/codes, the provider's registered type identifiers, and whether Lockdown Mode is enabled (LDMGlobalEnabled). This is the case that motivated the PR.
  • Everything else surfaces underlyingError, showing the system's own message. No event.
  • underlyingError now carries a non-optional Error; a new unknown case covers the (contract-violating) no-URL/no-error result.

What triggers it

This only reproduces with iOS Lockdown Mode enabled. Lockdown Mode hardens how the system materializes media for apps; loading the photo fails and the PhotosFileProvider process is killed, so loadFileRepresentation returns NSItemProviderError -1000 with an underlying NSCocoaErrorDomain 4099 — the provider's XPC connection dying. The repro used a 36 MP JPEG; outside Lockdown Mode the same photo imports normally.

This PR makes the failure graceful and observable; it does not change what Lockdown Mode allows. Actually importing these under Lockdown Mode would need an import path that streams the original (e.g. PHAsset / PHAssetResourceManager) — a separate follow-up.

Test plan

  • Device-verified (iPhone 15 Pro, iOS 27, Lockdown Mode on): a 36 MP photo via My Site → Media → Choose from Device shows "1 file not uploaded" immediately instead of spinning. Log: Failed to load file representation … NSItemProviderError -1000 / 4099. (This is the XPC path, so it resolves to cannotLoadItem.)
  • Unit tests cover the XPC-error detection: nested chains, all three XPC codes, and negatives (an unrelated Cocoa error, a matching code in a different domain).
  • Confirm media_import_item_unavailable fires in Tracks (with lockdown_mode = true) when reproducing the case.
  • A normal photo still imports successfully (outside Lockdown Mode).

`ItemProviderMediaExporter.export` loads the picked file via
`NSItemProvider.loadFileRepresentation`. On failure its completion handler
logged at debug level and returned without calling `onError`, so the export
never completed and the Media Library import stayed pending indefinitely — the
upload spinner never resolved.

Call `onError` on the failure path (and log at error level), and add
`ExportError.cannotLoadItem` with a user-facing message instead of surfacing the
raw `NSItemProviderError` text ("Cannot load representation of type public.data").
@jkmassel jkmassel self-assigned this Sep 4, 2026
@jkmassel jkmassel added this to the 27.3 milestone Sep 4, 2026
@wpmobilebot

wpmobilebot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number34393
VersionPR #25997
Bundle IDorg.wordpress.alpha
Commitf83fdd8
Installation URL499nhnufpedq0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number34393
VersionPR #25997
Bundle IDcom.jetpack.alpha
Commitf83fdd8
Installation URL6ekure78210gg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

Only route a picked-media load failure to `cannotLoadItem` when the item
provider's process died — an XPC connection error (`NSCocoaErrorDomain`
`xpcConnectionInterrupted`/`Invalid`/`ReplyInvalid`) nested under the
`NSItemProviderError`, which is the "too large to import" case. Emit a
`media_import_item_unavailable` Tracks event on that path, with the error
domains/codes and type identifiers, so it can be told apart from ordinary load
failures — which now surface as `underlyingError` with the raw message.

Make `underlyingError` carry a non-optional `Error` and add an explicit
`unknown` case for the (unexpected) no-URL/no-error result.

Add a Swift Testing suite covering the XPC-error detection: nested chains, all
three XPC codes, and negative cases (unrelated Cocoa error, matching code in a
different domain).
Read the system's global LDMGlobalEnabled user-defaults flag and attach it as
a lockdown_mode property, so Tracks can confirm this failure correlates with
Lockdown Mode being enabled.
When the provider-death path is hit with Lockdown Mode enabled, surface a
message naming Lockdown Mode instead of the generic 'may be too large' hint.
The size hint remains the fallback for an XPC failure outside Lockdown Mode.
Lockdown Mode can only change with a device restart, so the LDMGlobalEnabled
default is constant for the process lifetime. LockdownHelper reads it once via a
static let — lazy, thread-safe, and immutable — instead of hitting UserDefaults
on each failure. ItemProviderMediaExporter now uses it.
@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

@jkmassel
jkmassel requested a review from dcalhoun September 4, 2026 23:48
WPAnalytics is vended by WordPressShared; ItemProviderMediaExporter imported
only Foundation/PhotosUI/WordPressData, so the app target failed to compile with
'cannot find WPAnalytics in scope'. Every other WPAnalytics caller imports
WordPressShared.
Two fixes from reviewing the load-failure handling on this branch:

- handleLoadFailure now ignores cancellations (CancellationError,
  NSUserCancelledError, NSURLErrorCancelled) rather than surfacing them.
  Cancelling an in-flight import cancels this request's Progress, which
  fires the load completion with a cancellation error; the upload
  coordinator already reports the cancellation, so calling onError here
  produced a spurious "failed" notice (and a re-wrapped error whose code
  no longer matched the editors' NSURLErrorCancelled filter). The
  load-failure logging also moves into handleLoadFailure so a cancel
  logs at info instead of a false error.

- errorChain no longer reads NSUnderlyingErrorKey separately.
  NSError.underlyingErrors already includes it, so the extra read
  visited every wrapped error twice and halved the depth reachable
  before the 16-node bound, letting deeply nested XPC errors slip past.

Adds tests for cancellation classification and a five-level-deep XPC
error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants