Media: Client-side uploads for media-new.php and a guard for in-flight uploads#12586
Media: Client-side uploads for media-new.php and a guard for in-flight uploads#12586adamsilverstein wants to merge 7 commits into
Conversation
The client-side media pipeline needs SharedArrayBuffer, which requires a cross-origin isolated context. Core only isolates the block editor screens, so uploads from the Media Library grid cannot use the pipeline. Hook the existing Document-Isolation-Policy output buffer on load-upload.php, gated to grid mode for users who can upload files. The mode is resolved the same way upload.php resolves it later in the request, without updating the saved user option. List mode has no pipeline integration and stays untouched, avoiding isolation side effects on a screen that gets no benefit.
Grid uploads go through wp.Uploader/plupload to async-upload.php, doing all image processing server-side even when the browser could handle it. Add a media-library-upload script that configures the @wordpress/upload-media store and intercepts plupload's FilesAdded at a higher priority, routing each file through the pipeline: REST upload of the original, client-side thumbnails via wasm-vips, then sideload and finalize. The grid UI is preserved by mirroring wp-plupload's placeholder tiles, progress, queue reset, and error sidebar. mediaSideload/mediaFinalize are thin apiFetch wrappers because the @wordpress/media-utils equivalents are private APIs. When the browser is not cross-origin isolated or lacks client-side support, the script no-ops and classic plupload keeps handling uploads, so degraded environments lose nothing.
Assert the Document-Isolation-Policy header is sent on the grid and not in list mode, that a JPEG upload flows through the REST create, sideload, and finalize endpoints with no async-upload.php requests, and that a disallowed file type surfaces in the error sidebar. Playwright's Chromium build ships without Document-Isolation-Policy support, so the upload assertions skip when the context is not cross-origin isolated; the header assertions still run everywhere.
The ticket did not exist yet when the tests were written; annotate all new test methods now that it has been filed.
Interrupting a client-side pipeline upload is worse than interrupting a classic plupload one: classic uploads complete server-side once the bytes arrive, but an interrupted pipeline upload loses browser-generated thumbnails that were not sideloaded yet and leaves the attachment unfinalized. Trigger the browser's leave confirmation while the progress map is non-empty; the guard is scoped to the pipeline, so classic uploads behave exactly as before. See #65662
…eline media-new.php was the last admin upload surface without pipeline integration: plupload-handlers creates a raw plupload.Uploader (wp.Uploader never loads there) posting to async-upload.php with all image processing server-side. Extend cross-origin isolation to the screen via a new wp_set_up_media_new_cross_origin_isolation() on load-media-new.php, gated on client-side processing being enabled and the upload_files capability (the screen itself already requires it). Add a new media-new-upload admin script that binds a higher-priority FilesAdded handler on the plupload-handlers uploader instance and routes files through the @wordpress/upload-media store, sharing its settings with the grid integration via wp_get_media_library_upload_settings(). The screen's existing UI helpers are reused rather than replicated: fileQueued() builds the progress item, uploadSuccess() renders the finished attachment row through the existing async-upload.php markup endpoint, itemAjaxError() surfaces per-file errors, and uploadComplete() runs when the queue drains, so the screen looks and behaves unchanged. The same beforeunload guard as the grid warns while pipeline uploads are in flight. When the browser is not cross-origin isolated or lacks client-side support the script no-ops and the classic plupload flow (and the browser-uploader HTML fallback form) keep working unchanged. See #65662
Assert the Document-Isolation-Policy header on media-new.php, the happy-path pipeline upload (create, sideload, and finalize via REST with no file upload through async-upload.php; the fetch=3 markup POST is expected and excluded), and the disallowed-file-type error path. Playwright's Chromium ships without Document-Isolation-Policy support, so the upload assertions skip when the context is not isolated; the header assertion always runs. The existing media-upload spec keeps covering the classic path as the degradation check. See #65662
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
Trac ticket: https://core.trac.wordpress.org/ticket/65662
Note
Stacked on #12585 (Trac #65661): the branch builds on
add/media-library-client-side-uploads, so only the last 3 commits are new here.What
Two follow-ups that #12585 explicitly left out of scope:
media-new.php). It uses a separate uploader path:plupload-handlerscreates a rawplupload.Uploader(wp.Uploadernever loads there) posting toasync-upload.phpwith all image processing server-side. It was the last remaining admin upload surface without client-side pipeline integration.beforeunloadguard while pipeline uploads are in flight. Interrupting a pipeline upload is worse than interrupting a classic one: classic uploads complete server-side once the bytes arrive, but an interrupted pipeline upload loses browser-generated thumbnails that were not sideloaded yet and leaves the attachment unfinalized.How
Commit 1 - beforeunload guard for the grid.
media-library-upload.jstriggers the browser's leave confirmation while its progress map is non-empty (models stay there from interception until success or error). Scoped to the pipeline; classic uploads behave exactly as before.Commit 2 - media-new.php pipeline routing. A new
wp_set_up_media_new_cross_origin_isolation()onload-media-new.phpextends core's Document-Isolation-Policy output buffer to the screen, gated on client-side processing being enabled andupload_files(the screen itself already requires it; no mode gating applies). A newmedia-new-uploadadmin script binds a higher-priorityFilesAddedhandler on theplupload-handlersuploader instance and routes files through the@wordpress/upload-mediastore, sharing settings with the grid integration viawp_get_media_library_upload_settings().The screen's existing UI helpers are reused rather than replicated:
fileQueued()builds the progress item,uploadSuccess()renders the finished attachment row through the existingasync-upload.php?fetch=3markup endpoint,itemAjaxError()surfaces per-file errors, anduploadComplete()runs when the queue drains - so the screen looks and behaves unchanged. Store progress is mirrored onto the screen's progress bars, and the same beforeunload guard applies. When the browser is not cross-origin isolated or lacks client-side support, the script no-ops and the classic plupload flow (and the?browser-uploaderHTML fallback form) keep working unchanged - degradation, never data loss.The
mediaSideload/mediaFinalizeapiFetch wrappers are intentionally duplicated from the grid script rather than extracted into a shared handle: they are thin, and the two screens share no other loadable script (the grid script requireswp.Uploader/media-views, whichmedia-new.phpnever loads).Commit 3 - E2E coverage. New spec asserting the DIP header on
media-new.php, the happy-path upload (create + sideload + finalize via REST, no file upload throughasync-upload.php; thefetch=3markup POST is expected and excluded), and the disallowed-file-type error path. As in #12585, Playwright's Chromium lacks Document-Isolation-Policy support, so upload assertions skip when the context is not isolated; the header assertion always runs.One deliberate scope choice: the built-in
FilesAddedhandler blocks WebP/AVIF (and warns for HEIC) when the server cannot edit them; the pipeline handler bypasses those checks because conversion happens client-side - the same behavior the grid integration has.Testing
wpMediaNewCrossOriginIsolation.php,wpEnqueueMediaNewUpload.php) pass locally, as do the two suites from Media: Enable client-side media uploads in the Media Library grid #12585.async-upload.php; the progress item resolves to the normal row with Edit and Copy URL links; closing the tab mid-upload prompts for confirmation; Firefox/Safari and?browser-uploaderkeep the classic path.