Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/canary' into vary-accept
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk committed Jul 1, 2021
2 parents b62ee67 + 93f6254 commit d57a03c
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 41 deletions.
5 changes: 5 additions & 0 deletions docs/api-reference/next/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ The image position when using `layout="fill"`.

[Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position)

### onLoadingComplete

A callback function that is invoked once the image is completely loaded and the placeholder has been removed.

### loading

> **Attention**: This property is only meant for advanced usage. Switching an
Expand Down Expand Up @@ -242,6 +246,7 @@ Other properties on the `<Image />` component will be passed to the underlying
- `srcSet`. Use
[Device Sizes](/docs/basic-features/image-optimization.md#device-sizes)
instead.
- `ref`. Use [`onLoadingComplete`](#onloadingcomplete) instead.
- `decoding`. It is always `"async"`.

## Related
Expand Down
54 changes: 34 additions & 20 deletions packages/next/client/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export type ImageProps = Omit<
unoptimized?: boolean
objectFit?: ImgElementStyle['objectFit']
objectPosition?: ImgElementStyle['objectPosition']
onLoadingComplete?: () => void
} & (StringImageProps | ObjectImageProps)

const {
Expand Down Expand Up @@ -261,30 +262,37 @@ function defaultImageLoader(loaderProps: ImageLoaderProps) {

// See https://stackoverflow.com/q/39777833/266535 for why we use this ref
// handler instead of the img's onLoad attribute.
function removePlaceholder(
function handleLoading(
img: HTMLImageElement | null,
placeholder: PlaceholderValue
placeholder: PlaceholderValue,
onLoadingComplete?: () => void
) {
if (placeholder === 'blur' && img) {
const handleLoad = () => {
if (!img.src.startsWith('data:')) {
const p = 'decode' in img ? img.decode() : Promise.resolve()
p.catch(() => {}).then(() => {
if (!img) {
return
}
const handleLoad = () => {
if (!img.src.startsWith('data:')) {
const p = 'decode' in img ? img.decode() : Promise.resolve()
p.catch(() => {}).then(() => {
if (placeholder === 'blur') {
img.style.filter = 'none'
img.style.backgroundSize = 'none'
img.style.backgroundImage = 'none'
})
}
}
if (img.complete) {
// If the real image fails to load, this will still remove the placeholder.
// This is the desired behavior for now, and will be revisited when error
// handling is worked on for the image component itself.
handleLoad()
} else {
img.onload = handleLoad
}
if (onLoadingComplete) {
onLoadingComplete()
}
})
}
}
if (img.complete) {
// If the real image fails to load, this will still remove the placeholder.
// This is the desired behavior for now, and will be revisited when error
// handling is worked on for the image component itself.
handleLoad()
} else {
img.onload = handleLoad
}
}

export default function Image({
Expand All @@ -299,6 +307,7 @@ export default function Image({
height,
objectFit,
objectPosition,
onLoadingComplete,
loader = defaultImageLoader,
placeholder = 'empty',
blurDataURL,
Expand Down Expand Up @@ -401,6 +410,11 @@ export default function Image({
)
}
}
if ('ref' in rest) {
console.warn(
`Image with src "${src}" is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead.`
)
}
}
let isLazy =
!priority && (loading === 'lazy' || typeof loading === 'undefined')
Expand Down Expand Up @@ -589,9 +603,9 @@ export default function Image({
{...imgAttributes}
decoding="async"
className={className}
ref={(element) => {
setRef(element)
removePlaceholder(element, placeholder)
ref={(img) => {
setRef(img)
handleLoading(img, placeholder, onLoadingComplete)
}}
style={imgStyle}
/>
Expand Down
24 changes: 14 additions & 10 deletions test/integration/custom-error-page-exception/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,29 @@

import { join } from 'path'
import webdriver from 'next-webdriver'
import { nextBuild, nextStart, findPort, killApp } from 'next-test-utils'
import { nextBuild, nextStart, findPort, killApp, check } from 'next-test-utils'

jest.setTimeout(1000 * 60 * 1)

const appDir = join(__dirname, '..')
const navSel = '#nav'
const errorMessage = 'Application error: a client-side exception has occurred'
let appPort
let app

describe('Custom error page exception', () => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should handle errors from _error render', async () => {
const { code } = await nextBuild(appDir)
const appPort = await findPort()
const app = await nextStart(appDir, appPort)
const navSel = '#nav'
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss(navSel).elementByCss(navSel).click()
const text = await (await browser.elementByCss('#__next')).text()
killApp(app)

expect(code).toBe(0)
expect(text).toMatch(errorMessage)
await check(
() => browser.eval('document.documentElement.innerHTML'),
/Application error: a client-side exception has occurred/
)
})
})
12 changes: 6 additions & 6 deletions test/integration/fallback-modules/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ describe('Build Output', () => {
const indexSize = parsePageSize('/')
const indexFirstLoad = parsePageFirstLoad('/')

expect(parseFloat(indexSize)).toBeLessThanOrEqual(3.1)
expect(parseFloat(indexSize)).toBeGreaterThanOrEqual(2)
// expect(parseFloat(indexSize)).toBeLessThanOrEqual(3.1)
// expect(parseFloat(indexSize)).toBeGreaterThanOrEqual(2)
expect(indexSize.endsWith('kB')).toBe(true)

expect(parseFloat(indexFirstLoad)).toBeLessThanOrEqual(
process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE ? 68.1 : 67.9
)
expect(parseFloat(indexFirstLoad)).toBeGreaterThanOrEqual(60)
// expect(parseFloat(indexFirstLoad)).toBeLessThanOrEqual(
// process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE ? 68.1 : 67.9
// )
// expect(parseFloat(indexFirstLoad)).toBeGreaterThanOrEqual(60)
expect(indexFirstLoad.endsWith('kB')).toBe(true)
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
[
{
"url": "https://fonts.googleapis.com/css?family=Voces",
"content": "@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v12/-F6_fjJyLyU8d7PGDmk.woff) format('woff')}@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v12/-F6_fjJyLyU8d7PIDm_6pClI_ik.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v12/-F6_fjJyLyU8d7PGDm_6pClI.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}"
},
{
"url": "https://fonts.googleapis.com/css2?family=Modak",
"content": "@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEsnME.woff) format('woff')}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMB-hR77LKVTy8.woff2) format('woff2');unicode-range:U+0900-097F,U+1CD0-1CF6,U+1CF8-1CF9,U+200C-200D,U+20A8,U+20B9,U+25CC,U+A830-A839,U+A8E0-A8FB}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMO-hR77LKVTy8.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMA-hR77LKV.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}"
"content": "@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v15/-F6_fjJyLyU8d7PGDmk.woff) format('woff')}@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v15/-F6_fjJyLyU8d7PIDm_6pClI_ik.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v15/-F6_fjJyLyU8d7PGDm_6pClI.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}"
},
{
"url": "https://fonts.googleapis.com/css2?family=Modak",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from 'react'
import Image from 'next/image'

const Page = () => (
<div>
<h1>On Loading Complete Test</h1>
<ImageWithMessage id="1" src="/test.jpg" />
<ImageWithMessage
id="2"
src={require('../public/test.png')}
placeholder="blur"
/>
</div>
)

function ImageWithMessage({ id, src }) {
const [msg, setMsg] = useState('[LOADING]')
return (
<>
<Image
id={`img${id}`}
src={src}
width="400"
height="400"
onLoadingComplete={() => setMsg(`loaded img${id}`)}
/>
<p id={`msg${id}`}>{msg}</p>
</>
)
}

export default Page
29 changes: 29 additions & 0 deletions test/integration/image-component/default/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,35 @@ function runTests(mode) {
}
})

it('should callback onLoadingComplete when image is fully loaded', async () => {
let browser
try {
browser = await webdriver(appPort, '/on-loading-complete')

await check(
() => browser.eval(`document.getElementById("img1").src`),
/test(.*)jpg/
)

await check(
() => browser.eval(`document.getElementById("img2").src`),
/test(.*).png/
)
await check(
() => browser.eval(`document.getElementById("msg1").textContent`),
'loaded img1'
)
await check(
() => browser.eval(`document.getElementById("msg2").textContent`),
'loaded img2'
)
} finally {
if (browser) {
await browser.close()
}
}
})

it('should work when using flexbox', async () => {
let browser
try {
Expand Down

0 comments on commit d57a03c

Please sign in to comment.