Skip to content

Media: Enable client-side media uploads in the Media Library grid#12585

Open
adamsilverstein wants to merge 4 commits into
WordPress:trunkfrom
adamsilverstein:add/media-library-client-side-uploads
Open

Media: Enable client-side media uploads in the Media Library grid#12585
adamsilverstein wants to merge 4 commits into
WordPress:trunkfrom
adamsilverstein:add/media-library-client-side-uploads

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jul 17, 2026

Copy link
Copy Markdown
Member

Trac ticket: https://core.trac.wordpress.org/ticket/65661

What

WordPress 7.1's client-side media pipeline (wasm-vips) only runs in the block editor: it swaps the editor's mediaUpload setting and never touches the Media Library grid, which uploads via wp.Uploader/plupload to async-upload.php. This PR extends the pipeline to the Media Library grid at upload.php, so grid uploads are processed in the browser (REST upload of the original, client-side thumbnails, sideload, finalize) instead of server-side.

This ports client-side-media-everywhere PR #49 (see issue #44) into core, adapted to core's DIP-only isolation model.

How

Commit 1 - Extend cross-origin isolation to the grid. Hooks a new wp_set_up_media_library_cross_origin_isolation() on load-upload.php, reusing core's existing wp_start_cross_origin_isolation_output_buffer() (Document-Isolation-Policy, Chromium 137+). It is gated to grid mode (resolved the same way upload.php resolves it later in the request, via a new wp_get_media_library_mode()) and to users with upload_files. Unlike the plugin, no COEP/COOP fallback path is needed: core's isolation is DIP-only across all screens.

Commit 2 - Route grid uploads through the pipeline. A new media-library-upload script (vanilla IIFE in src/js/_enqueues/admin/, matching the surrounding admin scripts) configures the @wordpress/upload-media store via MediaUploadProvider (useSubRegistry: false), wraps wp.Uploader.prototype.init to intercept FilesAdded at a higher plupload priority, and routes each file through the store while mirroring wp-plupload's placeholder tiles, progress, queue reset, and error sidebar so the grid UI works unchanged. mediaSideload/mediaFinalize are thin apiFetch wrappers rather than the private @wordpress/media-utils APIs. The script is enqueued from the upload.php grid branch via wp_enqueue_media_library_upload(), with pipeline settings (wp_get_media_library_upload_settings(): max upload size, allowed mime types, registered sub-sizes, big-image threshold, strip-meta and bit-depth filters) passed as an inline script. When the browser is not cross-origin isolated or lacks client-side support, the script no-ops and classic plupload keeps handling uploads - degradation, never data loss.

Commit 3 - E2E coverage. New spec asserting the DIP header on upload.php grid (and its absence in list mode), the happy-path upload (create + sideload + finalize via REST, zero async-upload.php requests), 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 assertions always run.

Testing

  • 17 new PHPUnit tests (wpMediaLibraryCrossOriginIsolation.php, wpEnqueueMediaLibraryUpload.php) pass locally, as does the existing wpCrossOriginIsolation.php suite.
  • PHPCS: no new issues on the touched files. JSHint passes on the new script.
  • The equivalent implementation passed the plugin's E2E suite on chromium, firefox, and webkit against core trunk (7.1-alpha), including full-pipeline runs on browsers where isolation is available.

Scope

In: upload.php grid mode - drag-and-drop and "Add New", both of which flow through the one grid wp.Uploader.

Out: list-mode "Add New" / media-new.php (separate uploader path) and a beforeunload guard for in-progress uploads; both are follow-up candidates.

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.
@github-actions

Copy link
Copy Markdown

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

The ticket did not exist yet when the tests were written; annotate all
new test methods now that it has been filed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant