Fix image format for Safari and old browsers (#18646)

Older versions of Safari (and other browsers) do not support webp format so we were incorrectly falling back to png.

This PR fixes #18509 so that we fallback to the original image format if a modern format like webp was not explicitly provided in the Accept header.

Tests were added to ensure that the Accept header for Safari, Firefox, and Chrome work properly.
This commit is contained in:
Steven 2020-11-01 18:53:36 -05:00 committed by GitHub
parent 5c97b97755
commit 377aa4b650
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 33 additions and 15 deletions

View file

@ -18,8 +18,8 @@ const PNG = 'image/png'
const JPEG = 'image/jpeg'
const GIF = 'image/gif'
const SVG = 'image/svg+xml'
const MIME_TYPES = [/* AVIF, */ WEBP, PNG, JPEG]
const CACHE_VERSION = 1
const MODERN_TYPES = [/* AVIF, */ WEBP]
const ANIMATABLE_TYPES = [WEBP, PNG, GIF]
const VECTOR_TYPES = [SVG]
@ -49,7 +49,7 @@ export async function imageOptimizer(
const { headers } = req
const { url, w, q } = parsedUrl.query
const mimeType = mediaType(headers.accept, MIME_TYPES) || ''
const mimeType = getSupportedMimeType(MODERN_TYPES, headers.accept)
let href: string
if (!url) {
@ -294,6 +294,11 @@ export async function imageOptimizer(
return { finished: true }
}
function getSupportedMimeType(options: string[], accept = ''): string {
const mimeType = mediaType(accept, options)
return accept.includes(mimeType) ? mimeType : ''
}
function getHash(items: (string | number | undefined)[]) {
const hash = createHash('sha256')
for (let item of items) {

View file

@ -88,6 +88,26 @@ function runTests({ w, isDev, domains }) {
expect(actual).toMatch(expected)
})
it('should maintain jpg format for old Safari', async () => {
const accept =
'image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5'
const query = { w, q: 90, url: '/test.jpg' }
const opts = { headers: { accept } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toContain('image/jpeg')
})
it('should maintain png format for old Safari', async () => {
const accept =
'image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5'
const query = { w, q: 75, url: '/test.png' }
const opts = { headers: { accept } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toContain('image/png')
})
it('should fail when url is missing', async () => {
const query = { w, q: 100 }
const res = await fetchViaHTTP(appPort, '/_next/image', query, {})
@ -173,24 +193,15 @@ function runTests({ w, isDev, domains }) {
)
})
it('should resize relative url and webp accept header', async () => {
it('should resize relative url and webp Firefox accept header', async () => {
const query = { url: '/test.png', w, q: 80 }
const opts = { headers: { accept: 'image/webp' } }
const opts = { headers: { accept: 'image/webp,*/*' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/webp')
await expectWidth(res, w)
})
it('should resize relative url and jpeg accept header', async () => {
const query = { url: '/test.png', w, q: 80 }
const opts = { headers: { accept: 'image/jpeg' } }
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
await expectWidth(res, w)
})
it('should resize relative url and png accept header', async () => {
const query = { url: '/test.png', w, q: 80 }
const opts = { headers: { accept: 'image/png' } }
@ -227,9 +238,11 @@ function runTests({ w, isDev, domains }) {
await expectWidth(res, w)
})
it('should resize relative url and wildcard accept header as webp', async () => {
it('should resize relative url and Chrome accept header as webp', async () => {
const query = { url: '/test.png', w, q: 80 }
const opts = { headers: { accept: 'image/*' } }
const opts = {
headers: { accept: 'image/avif,image/webp,image/apng,image/*,*/*;q=0.8' },
}
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/webp')