Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 98 additions & 2 deletions packages/upload-media/src/feature-detection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
interface NavigatorNetworkInformation {
saveData: boolean;
effectiveType: 'slow-2g' | '2g' | '3g' | '4g';
}

interface NavigatorExtended extends Navigator {
deviceMemory?: number;
connection?: NavigatorNetworkInformation;
}

/**
* Result of client-side media processing support detection.
*/
Expand All @@ -24,6 +34,17 @@ let cachedResult: FeatureDetectionResult | null = null;
* 1. WebAssembly support (required for wasm-vips)
* 2. SharedArrayBuffer support (required for WASM threading)
* 3. CSP compatibility for blob URL workers (required for inline worker creation)
* 4. Credentialless iframe support (required so cross-origin isolation does not
* break third-party embeds). Browsers that lack `credentialless` iframe support
* (currently Firefox and Safari) will have client-side media processing disabled
* by default. Developers can re-enable the feature via the server-side
* `wp_client_side_media_processing_enabled` filter if it works for their site.
* 5. Device memory (disables on devices with ≤2 GB RAM)
* 6. Hardware concurrency (disables on devices with fewer than 4 CPU cores)
* 7. Network conditions (disables when data saver / reduced data mode is on or connection is 3g/2g/slow-2g)
* 8. Web Worker support (baseline requirement)
*
* Results are cached after the first call. Use `clearFeatureDetectionCache()` to reset.
*
* @return Feature detection result with supported status and reason if not supported.
*/
Expand All @@ -37,7 +58,7 @@ export function detectClientSideMediaSupport(): FeatureDetectionResult {
if ( typeof WebAssembly === 'undefined' ) {
cachedResult = {
supported: false,
reason: 'WebAssembly is not supported in this browser',
reason: 'WebAssembly is not supported in this browser.',
};
return cachedResult;
}
Expand All @@ -51,10 +72,85 @@ export function detectClientSideMediaSupport(): FeatureDetectionResult {
return cachedResult;
}

// Check Web Worker support.
if ( typeof Worker === 'undefined' ) {
cachedResult = {
supported: false,
reason: 'Web Workers are not supported in this browser.',
};
return cachedResult;
}

// Check credentialless iframe support.
// Browsers without this (Firefox, Safari) cannot use cross-origin isolation
// without breaking third-party embeds.
if (
typeof window !== 'undefined' &&
window.HTMLIFrameElement &&
! ( 'credentialless' in window.HTMLIFrameElement.prototype )
) {
cachedResult = {
supported: false,
reason: 'Browser does not support credentialless iframes. Cross-origin isolation would break third-party embeds',
};
return cachedResult;
}

// Check device memory.
if (
typeof navigator !== 'undefined' &&
'deviceMemory' in navigator &&
( navigator as NavigatorExtended ).deviceMemory! <= 2
) {
cachedResult = {
supported: false,
reason: 'Device has insufficient memory for client-side media processing.',
};
return cachedResult;
}
Comment thread
adamsilverstein marked this conversation as resolved.

// Check hardware concurrency (number of CPU cores).
if (
typeof navigator !== 'undefined' &&
'hardwareConcurrency' in navigator &&
navigator.hardwareConcurrency < 4
) {
cachedResult = {
supported: false,
reason: 'Device has insufficient CPU cores for client-side media processing.',
};
return cachedResult;
}

// Check network conditions.
if ( typeof navigator !== 'undefined' ) {
const connection = ( navigator as NavigatorExtended ).connection;
if ( connection ) {
if ( connection.saveData ) {
cachedResult = {
supported: false,
reason: 'Data saver mode is enabled.',
};
return cachedResult;
}
if (
connection.effectiveType === 'slow-2g' ||
connection.effectiveType === '2g' ||
connection.effectiveType === '3g'
) {
Comment thread
adamsilverstein marked this conversation as resolved.
cachedResult = {
supported: false,
reason: 'Network connection is too slow for client-side media processing.',
};
return cachedResult;
}
}
}

// Check that blob URL workers are allowed by CSP.
// Security plugins often set a strict worker-src directive that blocks blob: URLs,
// which would prevent creating the WASM processing worker at runtime.
if ( typeof window !== 'undefined' && typeof Worker !== 'undefined' ) {
if ( typeof window !== 'undefined' ) {
try {
const testBlob = new Blob( [ '' ], {
type: 'application/javascript',
Expand Down
Loading
Loading