fix(common): skip zero-height warning for detached fill-mode images - #69955
fix(common): skip zero-height warning for detached fill-mode images#69955TheSaiEaranti wants to merge 1 commit into
Conversation
|
There is a subtle but significant issue with this approach: it introduces a false negative (blind spot) for images that load from the cache. The IssueBy adding Because the For these cached images:
Essentially, this PR inadvertently disables the Potential AlternativesTo fix the false positive without losing the warning for cached images, the check might need to be deferred until the element is actually participating in the layout. Some alternative approaches:
|
|
You're right — the guard traded the false positive for a blind spot, since the one-shot load listener means a detached-at-load image never gets re-checked. Reworked in 3263f53 along your first suggested line:
Tests now cover the full space: connected + zero height warns immediately (unchanged), detached → attached with a height stays silent, detached → attached still zero-height warns (the case the previous revision lost), and destruction cancels the deferred check. On the |
| // document, and a detached element always reports a zero height. Defer the | ||
| // check until the element is connected and participates in layout. | ||
| if (!img.isConnected) { | ||
| recheckTimeout = setTimeout(check); |
There was a problem hiding this comment.
I'm not entirely sure that using setTimeout is the correct approach. Based on what was mentioned, we can use requestFrameAnimation. While it's not supported in all environments/browsers, I think we can copy (or perhaps export) the ones used in idle_service.ts. (This won't affect the final bundle; as far as I understand, this only runs under ngDevMode.)
angular/packages/core/src/defer/idle_service.ts
Lines 21 to 30 in 5ad8231
|
Good call — done in the latest commit. I'd gone to plain |
|
You can also please rebase your 4 commits so that there's only one. You can fetch the latest changes, run an interactive rebase on upstream/main to squash your commits, and then force push your branch. |
7359ceb to
cf77672
Compare
|
Done, squashed to a single commit and rebased on the latest main. The commit message describes the final approach (deferred recheck with the shimmed requestAnimationFrame). Tests are green locally, the CI run for the new head just needs a maintainer to approve it. |
| /** | ||
| * Shims for `requestAnimationFrame` and `cancelAnimationFrame` for environments | ||
| * where those functions are not available, mirroring the `requestIdleCallback` | ||
| * shims in `core/src/defer/idle_service.ts`. Only used by dev-mode checks. | ||
| */ | ||
| const _requestAnimationFrame = () => | ||
| (typeof requestAnimationFrame !== 'undefined' | ||
| ? requestAnimationFrame | ||
| : (cb: VoidFunction) => setTimeout(cb) as unknown as number | ||
| ).bind(globalThis) as typeof requestAnimationFrame; | ||
|
|
||
| const _cancelAnimationFrame = () => | ||
| (typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame : clearTimeout).bind( | ||
| globalThis, | ||
| ); | ||
|
|
There was a problem hiding this comment.
We don't need such shims. The framework itself uses requestAnimationFrame for scheduling change detection.
There was a problem hiding this comment.
Potential Alternatives
To fix the false positive without losing the warning for cached images, the check might need to be deferred until the element is actually participating in the layout.
Some alternative approaches:
Defer the check: If !img.isConnected or clientHeight === 0, wait for a macrotask (like setTimeout) or use requestAnimationFrame to check the height slightly later when layout has settled.
According to the Gemini review (if I'm not mistaken), it indicates that if we need to differentiate due to the potential delays of load events, wouldn't it be correct to use raF to allow this in compatible browsers and use setTimeout as a fallback?
I was reviewing and found the following; I understand it would still be necessary since if we used requestAnimationFrame directly it might not work in Safari or other environments that don't support it.
angular/packages/core/src/util/callback_scheduler.ts
Lines 37 to 66 in 5ad8231
cf77672 to
910c303
Compare
|
Makes sense, shims removed in 910c303. The scheduling now calls requestAnimationFrame directly. One detail worth explaining since it's why the shims appeared in the first place: the packages/common node test environment defines requestAnimationFrame but not cancelAnimationFrame, so the first version of this crashed component cleanup there. Rather than shimming around it, destruction now stops the recheck loop with a flag and never calls cancelAnimationFrame at all. A pending frame callback after destroy just returns early, which for a dev-mode-only check seemed like the simplest correct shape. It also answers the fallback question, since scheduling relies on the same rAF assumption the framework already makes. Still a single commit, rebased, tests and tslint green. |
| removeErrorListenerFn(); | ||
| let stopped = false; | ||
| const check = () => { | ||
| if (stopped) { |
There was a problem hiding this comment.
you can use
| if (stopped) { | |
| if (destroyRef.destroyed) { |
910c303 to
c347e1f
Compare
… is connected A memory-cached image can fire its `load` event before the embedded view is attached to the document, for example when the same `ngSrc` appears in more than one `@defer` block. A detached element always reports a zero height, so `assertNonZeroRenderedHeight` emitted false-positive NG02952 warnings that point developers at container CSS that is not broken. Instead of evaluating `clientHeight` at load time, re-schedule the check with `requestAnimationFrame` until the element is connected and participates in layout. The recheck stops once `destroyRef.destroyed` is set, rather than calling `cancelAnimationFrame`, which some test environments do not define. A genuinely zero-height fill image still warns once it attaches, including images served from the browser cache. Fixes angular#69636
|
Nice, that's cleaner. Applied in c347e1f, the local flag is gone and the recheck reads destroyRef.destroyed directly. Tests and tslint green, still one commit. |
c347e1f to
c7e0312
Compare
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
assertNonZeroRenderedHeightemits NG02952 ("the height of the fill-mode image is zero") whenever a fill-mode image firesloadwithclientHeight === 0. A memory-cached image can fireloadbefore its embedded view is attached to the document — for example when the samengSrcappears in more than one@deferblock and later instances are served from memory cache — and a detached element always reports a zero height. The warning then fires in bursts and points developers at container CSS that is not broken (the issue includes document-level capture-phase evidence that every warned image was detached at dispatch time and laid out correctly one tick later).Issue Number: #69636
What is the new behavior?
The zero-height check only warns when
img.isConnectedis true, where a zeroclientHeightis meaningful. Two tests pin both directions: a connected fill-mode image with zero rendered height still warns, and a detached one no longer does. The new detached-image test fails onmain.One design note for review: this skips the check for detached-at-load images rather than deferring it until the element connects, so an image that loads detached and later attaches into a genuinely zero-height container will not warn. If the team prefers re-checking on attachment, I'm happy to rework in that direction. Prior attempt #69809 proposed the same guard but was closed the same day (CLA); this implementation and its tests were written independently, and the positive-direction test is new.
Does this PR introduce a breaking change?
Other information
Dev-mode-only diagnostic; no production behavior change.
pnpm test packages/common/test:testpasses with the fix and fails onmainwith the new test.