Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c0368f2

Browse filesBrowse files
SkyZeroZxpkozlowski-opensource
authored andcommitted
fix(common): preserve crossorigin on image preloads
Propagate the crossorigin attribute from priority NgOptimizedImage hosts to SSR-generated preload links. Keep preload and image requests in the same credentials mode to avoid an anonymous image issuing an earlier credentialed request. (cherry picked from commit d14696e)
1 parent e62905e commit c0368f2
Copy full SHA for c0368f2

4 files changed

+65-10Lines changed: 65 additions & 10 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

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

Copy file name to clipboardExpand all lines: packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ export class NgOptimizedImage implements OnInit, OnChanges {
528528
this.getRewrittenSrc(),
529529
rewrittenSrcset,
530530
this.sizes,
531+
this.imgElement.getAttribute('crossorigin'),
531532
);
532533
}
533534
}
Collapse file

‎packages/common/src/directives/ng_optimized_image/preload-link-creator.ts‎

Copy file name to clipboardExpand all lines: packages/common/src/directives/ng_optimized_image/preload-link-creator.ts
+24-3Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ export class PreloadLinkCreator {
4747
* @param src The original src of the image that is set on the `ngSrc` input.
4848
* @param srcset The parsed and formatted srcset created from the `ngSrcset` input
4949
* @param sizes The value of the `sizes` attribute passed in to the `<img>` tag
50+
* @param crossOrigin The value of the `crossorigin` attribute passed in to the `<img>` tag
5051
*/
51-
createPreloadLinkTag(renderer: Renderer2, src: string, srcset?: string, sizes?: string): void {
52+
createPreloadLinkTag(
53+
renderer: Renderer2,
54+
src: string,
55+
srcset?: string,
56+
sizes?: string,
57+
crossOrigin?: string | null,
58+
): void {
59+
const preloadKey = `${src}:${getCrossOriginMode(crossOrigin)}`;
60+
5261
if (
5362
ngDevMode &&
5463
!this.errorShown &&
@@ -66,18 +75,22 @@ export class PreloadLinkCreator {
6675
);
6776
}
6877

69-
if (this.preloadedImages.has(src)) {
78+
if (this.preloadedImages.has(preloadKey)) {
7079
return;
7180
}
7281

73-
this.preloadedImages.add(src);
82+
this.preloadedImages.add(preloadKey);
7483

7584
const preload = renderer.createElement('link');
7685
renderer.setAttribute(preload, 'as', 'image');
7786
renderer.setAttribute(preload, 'href', src);
7887
renderer.setAttribute(preload, 'rel', 'preload');
7988
renderer.setAttribute(preload, 'fetchpriority', 'high');
8089

90+
if (crossOrigin != null) {
91+
renderer.setAttribute(preload, 'crossorigin', crossOrigin);
92+
}
93+
8194
if (sizes) {
8295
renderer.setAttribute(preload, 'imageSizes', sizes);
8396
}
@@ -89,3 +102,11 @@ export class PreloadLinkCreator {
89102
renderer.appendChild(this.document.head, preload);
90103
}
91104
}
105+
106+
function getCrossOriginMode(crossOrigin?: string | null): string | null {
107+
if (crossOrigin == null) {
108+
return null;
109+
}
110+
111+
return crossOrigin.toLowerCase() === 'use-credentials' ? 'use-credentials' : 'anonymous';
112+
}
Collapse file

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

Copy file name to clipboardExpand all lines: packages/common/src/directives/ng_optimized_image/tokens.ts
+3-5Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ import {InjectionToken} from '@angular/core';
1717
export const DEFAULT_PRELOADED_IMAGES_LIMIT = 5;
1818

1919
/**
20-
* Helps to keep track of priority images that already have a corresponding
21-
* preload tag (to avoid generating multiple preload tags with the same URL).
22-
*
23-
* This Set tracks the original src passed into the `ngSrc` input not the src after it has been
24-
* run through the specified `IMAGE_LOADER`.
20+
* Helps to keep track of priority images that already have a corresponding preload tag. Each key
21+
* identifies the rewritten image URL and its CORS mode, since preload tags with different CORS
22+
* modes are not interchangeable.
2523
*/
2624
export const PRELOADED_IMAGES = new InjectionToken<Set<string>>(
2725
typeof ngDevMode === 'undefined' || ngDevMode ? 'NG_OPTIMIZED_PRELOADED_IMAGES' : '',
Collapse file

‎packages/common/test/directives/ng_optimized_image_spec.ts‎

Copy file name to clipboardExpand all lines: packages/common/test/directives/ng_optimized_image_spec.ts
+37-2Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('Image directive', () => {
6767
],
6868
});
6969

70-
const template = `<img ngSrc="${src}" width="150" height="50" priority sizes="10vw" ngSrcset="100w">`;
70+
const template = `<img ngSrc="${src}" width="150" height="50" priority sizes="10vw" ngSrcset="100w" crossorigin="anonymous">`;
7171
TestBed.overrideComponent(TestComponent, {set: {template: template}});
7272

7373
const _document = TestBed.inject(DOCUMENT);
@@ -98,6 +98,7 @@ describe('Image directive', () => {
9898
expect(preloadLink!.getAttribute('imagesizes')).toEqual('10vw');
9999
expect(preloadLink!.getAttribute('imagesrcset')).toEqual(`${rewrittenSrc}?width=100 100w`);
100100
expect(preloadLink!.getAttribute('fetchpriority')).toEqual('high');
101+
expect(preloadLink!.getAttribute('crossorigin')).toEqual('anonymous');
101102

102103
preloadLink!.remove();
103104
});
@@ -133,7 +134,7 @@ describe('Image directive', () => {
133134

134135
const preloadImages = TestBed.inject(PRELOADED_IMAGES);
135136

136-
expect(preloadImages.has(rewrittenSrc)).toBeTruthy();
137+
expect(preloadImages.has(`${rewrittenSrc}:null`)).toBeTruthy();
137138

138139
const preloadLinks = head.querySelectorAll(`link[href="${rewrittenSrc}"]`);
139140

@@ -142,6 +143,40 @@ describe('Image directive', () => {
142143
preloadLinks[0]!.remove();
143144
});
144145

146+
it('should create separate preload links for images with different CORS modes', async () => {
147+
if (!isBrowser) return;
148+
149+
const src = 'preload-cors/img.png';
150+
const rewrittenSrc = `https://angular.dev/${src}`;
151+
152+
setupTestingModule({
153+
extraProviders: [
154+
{provide: PLATFORM_ID, useValue: PLATFORM_SERVER_ID},
155+
{
156+
provide: IMAGE_LOADER,
157+
useValue: (config: ImageLoaderConfig) => `https://angular.dev/${config.src}`,
158+
},
159+
],
160+
});
161+
162+
const template = `
163+
<img ngSrc="${src}" width="150" height="50" priority>
164+
<img ngSrc="${src}" width="150" height="50" priority crossorigin="anonymous">
165+
`;
166+
TestBed.overrideComponent(TestComponent, {set: {template}});
167+
168+
const _document = TestBed.inject(DOCUMENT);
169+
const fixture = TestBed.createComponent(TestComponent);
170+
await fixture.whenStable();
171+
172+
const preloadLinks = _document.head.querySelectorAll(`link[href="${rewrittenSrc}"]`);
173+
expect(preloadLinks.length).toEqual(2);
174+
expect(preloadLinks[0]!.hasAttribute('crossorigin')).toBeFalse();
175+
expect(preloadLinks[1]!.getAttribute('crossorigin')).toEqual('anonymous');
176+
177+
preloadLinks.forEach((link) => link.remove());
178+
});
179+
145180
it('should warn when the number of preloaded images is larger than the limit', () => {
146181
// Only run this test in a browser since the Node-based DOM mocks don't
147182
// allow to override `HTMLImageElement.prototype.setAttribute` easily.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.