Skip to content

Commit 249d026

Browse files
arturovtalxhub
authored andcommitted
fix(common): execute checks and remove placeholder when image is already loaded (#55444)
With this commit, we're now able to perform checks even when the image has already been loaded (e.g., from the browser cache), and its `load` event would never be triggered. We use the [complete](https://html.spec.whatwg.org/#dom-img-complete) property, as specified, which indicates that the image state is fully available when the user agent has retrieved all the image data. This approach effectively triggers checks, as we no longer solely rely on the `load` event and consider that the image may already be loaded. This will not remove the placeholder until the `load` event fires (and it won't fire if the image is already "there"). This prevents memory leaks in development mode, as `load` and `error` event listeners are still attached to the image element. PR Close #55444
1 parent 1391928 commit 249d026

File tree

1 file changed

+30
-4
lines changed

1 file changed

+30
-4
lines changed

packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
695695

696696
const removeLoadListenerFn = this.renderer.listen(img, 'load', callback);
697697
const removeErrorListenerFn = this.renderer.listen(img, 'error', callback);
698+
699+
callOnLoadIfImageIsLoaded(img, callback);
698700
}
699701

700702
/** @nodoc */
@@ -1012,7 +1014,7 @@ function assertNoImageDistortion(
10121014
img: HTMLImageElement,
10131015
renderer: Renderer2,
10141016
) {
1015-
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
1017+
const callback = () => {
10161018
removeLoadListenerFn();
10171019
removeErrorListenerFn();
10181020
const computedStyle = window.getComputedStyle(img);
@@ -1105,7 +1107,9 @@ function assertNoImageDistortion(
11051107
);
11061108
}
11071109
}
1108-
});
1110+
};
1111+
1112+
const removeLoadListenerFn = renderer.listen(img, 'load', callback);
11091113

11101114
// We only listen to the `error` event to remove the `load` event listener because it will not be
11111115
// fired if the image fails to load. This is done to prevent memory leaks in development mode
@@ -1115,6 +1119,8 @@ function assertNoImageDistortion(
11151119
removeLoadListenerFn();
11161120
removeErrorListenerFn();
11171121
});
1122+
1123+
callOnLoadIfImageIsLoaded(img, callback);
11181124
}
11191125

11201126
/**
@@ -1160,7 +1166,7 @@ function assertNonZeroRenderedHeight(
11601166
img: HTMLImageElement,
11611167
renderer: Renderer2,
11621168
) {
1163-
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
1169+
const callback = () => {
11641170
removeLoadListenerFn();
11651171
removeErrorListenerFn();
11661172
const renderedHeight = img.clientHeight;
@@ -1176,13 +1182,17 @@ function assertNonZeroRenderedHeight(
11761182
),
11771183
);
11781184
}
1179-
});
1185+
};
1186+
1187+
const removeLoadListenerFn = renderer.listen(img, 'load', callback);
11801188

11811189
// See comments in the `assertNoImageDistortion`.
11821190
const removeErrorListenerFn = renderer.listen(img, 'error', () => {
11831191
removeLoadListenerFn();
11841192
removeErrorListenerFn();
11851193
});
1194+
1195+
callOnLoadIfImageIsLoaded(img, callback);
11861196
}
11871197

11881198
/**
@@ -1325,6 +1335,22 @@ function assertPlaceholderDimensions(dir: NgOptimizedImage, imgElement: HTMLImag
13251335
}
13261336
}
13271337

1338+
function callOnLoadIfImageIsLoaded(img: HTMLImageElement, callback: VoidFunction): void {
1339+
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-complete
1340+
// The spec defines that `complete` is truthy once its request state is fully available.
1341+
// The image may already be available if it’s loaded from the browser cache.
1342+
// In that case, the `load` event will not fire at all, meaning that all setup
1343+
// callbacks listening for the `load` event will not be invoked.
1344+
// In Safari, there is a known behavior where the `complete` property of an
1345+
// `HTMLImageElement` may sometimes return `true` even when the image is not fully loaded.
1346+
// Checking both `img.complete` and `img.naturalWidth` is the most reliable way to
1347+
// determine if an image has been fully loaded, especially in browsers where the
1348+
// `complete` property may return `true` prematurely.
1349+
if (img.complete && img.naturalWidth) {
1350+
callback();
1351+
}
1352+
}
1353+
13281354
function round(input: number): number | string {
13291355
return Number.isInteger(input) ? input : input.toFixed(2);
13301356
}

0 commit comments

Comments
 (0)