fix: import device photos under iOS Lockdown Mode - #26006
Draft
jkmassel wants to merge 2 commits into
Draft
Conversation
Under Lockdown Mode the system's PhotosFileProvider extension performs a hardened full decode when it materializes a picked image, and is killed at its 20 MB memory limit. A 36 MP photo needs ~144 MB, so importing one fails outright: `loadFileRepresentation` returns `NSItemProviderError -1000` over `NSCocoaErrorDomain 4099`. When Lockdown Mode is on, present a library-backed `PHPickerViewController` and read the picked file with `PHAssetResourceManager`, which is serviced by `photolibraryd` and has no such cap. Outside Lockdown Mode nothing changes: the same permission-free picker, the same item-provider path, no prompt. The two routes are exclusive. A failure reading from the library is reported rather than retried through the item provider, which in Lockdown Mode is the thing that cannot serve the file. Limited Photos authorization is refused up front, because the picker shows the whole library whatever the app has been granted and would otherwise offer items it can't read. Builds on #25997, which made the failure graceful and added the detection primitive this gates on.
Collaborator
Generated by 🚫 Danger |
Contributor
|
| App Name | WordPress | |
| Configuration | Release-Alpha | |
| Build Number | 34430 | |
| Version | PR #26006 | |
| Bundle ID | org.wordpress.alpha | |
| Commit | 12ab678 | |
| Installation URL | 063im7442qd50 |
Contributor
|
| App Name | Jetpack | |
| Configuration | Release-Alpha | |
| Build Number | 34430 | |
| Version | PR #26006 | |
| Bundle ID | com.jetpack.alpha | |
| Commit | 12ab678 | |
| Installation URL | 6o3jebh3h1l40 |
Denied access previously fell through to the permission-free picker while limited access was refused outright, which inverted user intent: granting *some* access left you worse off than granting none. Both now get the same answer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Under iOS Lockdown Mode, importing a high-megapixel device photo fails outright. This makes it succeed.
Builds on #25997, which made the failure graceful and added the detection primitive this gates on.
Summary
PhotosFileProviderextension performs a hardened full decode when it materializes a picked image under Lockdown Mode, and is killed at its 20 MB memory limit.PHAssetResourceManager— serviced byphotolibraryd, which has no such cap — instead ofNSItemProvider.loadFileRepresentation.Root Cause
Lockdown Mode forces a hardened full decode when the system materializes an image for
loadFileRepresentation. A 36 MP photo needs roughly36e6 x 4bytes — about 144 MB — against the extension's 20 MBActiveHardlimit, so the extension is killed:which surfaces as
NSItemProviderError -1000wrappingNSCocoaErrorDomain 4099.The cost tracks megapixels, not file size. Videos stream without a decode and are unaffected, which is why the bug looks intermittent: ordinary photos, screenshots and videos all import fine, and only large photos fail.
Fix
1. Read the file from the photo library under Lockdown Mode
WordPress/Classes/Utility/Media/PhotoLibraryFileLoader.swift (new): streams an asset's file with
PHAssetResourceManager.writeData, and loads a bounded image withPHImageManagerfor the flows that crop rather than upload.Resource preference is scoped by the asset's own media type —
.imageprefers[.fullSizePhoto, .photo, .alternatePhoto],.videoprefers[.fullSizeVideo, .video]. ThefullSizevariants are the current rendition of an edited asset, so an edited photo uploads with its edits. Scoping by media type also keeps a Live Photo, which carries both a.photoand a.pairedVideoresource, from uploading the wrong half.2. Reuse the existing export pipeline
WordPress/Classes/Utility/Media/ItemProviderMediaExporter.swift: takes an optional
assetIdentifierand aprefersPhotoLibrarySourceswitch, defaulting toLockdownHelper.isDeviceLockdownModeEnabled.Both routes share
process(fileAt:resourceTypeIdentifier:), so type routing, the unsupported-format fallback to.jpeg, GPS stripping,Progressand #25997's error surfacing are identical either way. Reusing the exporter also gets deterministic cleanup for free — the streamed file lands in the staging directory the exporter already deletes.We deliberately did not hand the streamed file to
coordinator.addMedia(from: fileURL as NSURL). That path usesMediaURLExporter, which has neither the HEIC-to-JPEG fallback nor the same allowable-extension handling, so a HEIC — the iPhone default — would have uploaded as HEIC, which self-hosted sites don't accept.3. Require Full Access under Lockdown Mode
WordPress/Classes/ViewRelated/Media/PhotosPickerPresenter.swift (new): presents a library-backed picker under Lockdown Mode, because only that supplies
result.assetIdentifier.PHPickerViewControllerdisplays the whole library regardless of what the app has been granted. Under limited authorization it would therefore offer items the app can't then read, so anything short of full access is refused up front with an alert offering Open Settings. The prompt only ever appears under Lockdown Mode.Limited and denied get the same answer deliberately. Letting denied fall through to the permission-free picker — as #25997 does today — while blocking limited would invert user intent: granting some access would leave you worse off than granting none.
4. One currency for picked media
WordPress/Classes/ViewRelated/Media/PhotosPickerAsset.swift (new) pairs the item provider with the asset identifier, and
DevicePhotosPickerDelegatereplacesPHPickerViewControllerDelegateas the shared callback. That covers the Media Library+, the SwiftUIMediaPicker, Gutenberg, Aztec's full-screen action, the site icon and the avatar in one shape, and keeps the picker choice an implementation detail.What We Explored
Legacy
UIImagePickerController, so the picker only shows granted assets. This does not work. It runs out-of-process too — it presents_UIImagePickerPlaceholderViewController, a remote view controller host, which produces a long delay and the same full-library UI — and since limited authorization was introduced in iOS 14 it leavesinfo[.phAsset]nil whenever access is.limited, even for assets the app may read. So it supplies an asset only under full authorization, which is exactly where the library-backed picker already works. Documented in the developer forums rather than the API docs; the docs carry only the deprecation.No system picker can be scoped to the app's granted assets. Doing that means building a grid on
PHAsset.fetchAssets, which would also restore multiple selection under Lockdown Mode. Not attempted here.Falling back to the item provider when an asset can't be resolved. Removed: in Lockdown Mode the item provider is precisely what can't serve the file, so retrying through it trades a clear failure for a silent one.
Test plan
Device testing on an iPhone 15 Pro (iOS 27.0) with Lockdown Mode on, against a self-hosted site. The same device reproduced the original failure before the change (
media_import_item_unavailable,lockdown_mode: true).Streaming picked asset 1(.photo), noexceeded mem limit, ~102 ms to disk.bytes: 61651968both times — so the two routes produce the same upload.Streaming picked asset 2(.video).Notes
One gap is known and left in place:
Aztec's inline keyboard picker keeps plain
PHPickerViewController. It is embedded as an input view with.continuousAndOrderedselection, which nothing else provides, so it retains the old behaviour under Lockdown Mode. Aztec's full-screen "Device Photos" action does get the new path.Only
.fullSizeVideo— an edited video — is covered by unit test rather than on device.megapixels: 0in themedia_library_photo_addedevent is a pre-existing analytics bug, present on both builds. Not addressed here.