Skip to content

Commit c3115b8

Browse files
arturovtalxhub
authored andcommitted
fix(common): execute checks and remove placeholder when image is already loaded (angular#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 angular#55444
1 parent 1549afe commit c3115b8

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
@@ -708,6 +708,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
708708

709709
const removeLoadListenerFn = this.renderer.listen(img, 'load', callback);
710710
const removeErrorListenerFn = this.renderer.listen(img, 'error', callback);
711+
712+
callOnLoadIfImageIsLoaded(img, callback);
711713
}
712714

713715
/** @nodoc */
@@ -1025,7 +1027,7 @@ function assertNoImageDistortion(
10251027
img: HTMLImageElement,
10261028
renderer: Renderer2,
10271029
) {
1028-
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
1030+
const callback = () => {
10291031
removeLoadListenerFn();
10301032
removeErrorListenerFn();
10311033
const computedStyle = window.getComputedStyle(img);
@@ -1118,7 +1120,9 @@ function assertNoImageDistortion(
11181120
);
11191121
}
11201122
}
1121-
});
1123+
};
1124+
1125+
const removeLoadListenerFn = renderer.listen(img, 'load', callback);
11221126

11231127
// We only listen to the `error` event to remove the `load` event listener because it will not be
11241128
// fired if the image fails to load. This is done to prevent memory leaks in development mode
@@ -1128,6 +1132,8 @@ function assertNoImageDistortion(
11281132
removeLoadListenerFn();
11291133
removeErrorListenerFn();
11301134
});
1135+
1136+
callOnLoadIfImageIsLoaded(img, callback);
11311137
}
11321138

11331139
/**
@@ -1173,7 +1179,7 @@ function assertNonZeroRenderedHeight(
11731179
img: HTMLImageElement,
11741180
renderer: Renderer2,
11751181
) {
1176-
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
1182+
const callback = () => {
11771183
removeLoadListenerFn();
11781184
removeErrorListenerFn();
11791185
const renderedHeight = img.clientHeight;
@@ -1189,13 +1195,17 @@ function assertNonZeroRenderedHeight(
11891195
),
11901196
);
11911197
}
1192-
});
1198+
};
1199+
1200+
const removeLoadListenerFn = renderer.listen(img, 'load', callback);
11931201

11941202
// See comments in the `assertNoImageDistortion`.
11951203
const removeErrorListenerFn = renderer.listen(img, 'error', () => {
11961204
removeLoadListenerFn();
11971205
removeErrorListenerFn();
11981206
});
1207+
1208+
callOnLoadIfImageIsLoaded(img, callback);
11991209
}
12001210

12011211
/**
@@ -1338,6 +1348,22 @@ function assertPlaceholderDimensions(dir: NgOptimizedImage, imgElement: HTMLImag
13381348
}
13391349
}
13401350

1351+
function callOnLoadIfImageIsLoaded(img: HTMLImageElement, callback: VoidFunction): void {
1352+
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-complete
1353+
// The spec defines that `complete` is truthy once its request state is fully available.
1354+
// The image may already be available if it’s loaded from the browser cache.
1355+
// In that case, the `load` event will not fire at all, meaning that all setup
1356+
// callbacks listening for the `load` event will not be invoked.
1357+
// In Safari, there is a known behavior where the `complete` property of an
1358+
// `HTMLImageElement` may sometimes return `true` even when the image is not fully loaded.
1359+
// Checking both `img.complete` and `img.naturalWidth` is the most reliable way to
1360+
// determine if an image has been fully loaded, especially in browsers where the
1361+
// `complete` property may return `true` prematurely.
1362+
if (img.complete && img.naturalWidth) {
1363+
callback();
1364+
}
1365+
}
1366+
13411367
function round(input: number): number | string {
13421368
return Number.isInteger(input) ? input : input.toFixed(2);
13431369
}

0 commit comments

Comments
 (0)