Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular does not always load CSS bundles properly on sites with multiple stylesheets and CSP nonces enabled #26932

Closed
christiaanverwijs opened this issue Jan 23, 2024 · 7 comments · Fixed by #26941

Comments

@christiaanverwijs
Copy link

christiaanverwijs commented Jan 23, 2024

Which @angular/* package(s) are the source of the bug?

common

Is this a regression?

Yes

Description

I recently enabled CSP with nonces for one of our web projects. After that, I started noticing that some styling wasn't always properly loaded. For example, I noticed some styling for our dialogs was sometimes missing. The screenshot below shows a test page with and without the issue:

issue

I noticed that "media=print" was not replaced with "media=all" for the styles.css bundle in the incorrect version. It happened frequently, but not all the time. So it seemed to be some sort of timing issue. Eventually, I narrowed it down to three conditions that need to be met:

  1. CSP nonce is set (e.g. )
  2. Part of the styling has to be loaded from an external stylesheet, in our case a .css file provided by a CDN. The second part of the styling is contained in a local bundle (e.g. styles.css) and injected automatically by Angular.
  3. The external stylesheet has to take at least 50ms to load.

This might sound quite rare. But this issue is now surfacing frequently across seven web projects we run. I suspect many developers will stylesheets provided by external sources, amended by local stylesheets, and combined with CSP nonces.

It isn't easy to reproduce the issue because it requires a server that adds the correct CSP headers. I set up a temporary site at https://test-cspissue.theliberatorsimproveyourteam.com to demonstrate the issue. The source code for the Angular site is available at https://github.com/christiaanverwijs/angular-csp-issue. To reproduce:

  1. In Chrome, use the Developers Tools. Go to "network" and "throttle" to "3G Fast" (this is only to slow the load of the CDN stylesheet to at least 50ms). Also, disable the local cache. Alternatively, just reload the page a few times in a new tab or window (CTRL+F5/F5) and the issue will show up.
  2. Load https://test-cspissue.theliberatorsimproveyourteam.com
  3. Click 'Open dialog'
  4. If the issue is present, the dialog box will appear immediately below the button, and not centered on a darker background (as it should be by the node_modules/@angular/material/prebuilt-themes/indigo-pink.css that we include in the local bundle).
  5. If the issue is not present, the dialog box appears nicely in the middle of the page.

Note that this isn't an issue with Material Design, but with Angular. The issue just surfaced most visibly first with a Material Design component.

Please provide a link to a minimal reproduction of the bug

https://github.com/christiaanverwijs/angular-csp-issue

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 17.1.0
Node: 18.17.1
Package Manager: npm 9.6.7
OS: win32 x64

Angular: 17.1.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, language-service, material, material-moment-adapter
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1701.0
@angular-devkit/build-angular   17.1.0
@angular-devkit/core            17.1.0
@angular-devkit/schematics      17.1.0
@schematics/angular             17.1.0
rxjs                            6.6.7
typescript                      5.3.3
zone.js                         0.14.3

Anything else?

I found that I could circumvent the issue by defining the "styles" as follows in angular.json. Not ideal, but it seems to work:

UPDATE JAN 23: As it happens, the solution below doesn't consistently resolve the issue on a production environment with a full-blown webapp.

                  "styles": [
                     {
                        // the first injected bundle isn't always loaded. so I added a dummy one. 
                        "input": "src/less/styles.less",
                        "bundleName": "dummy"
                     },
                     {
                        "input": "node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
                        "bundleName": "material"
                     },
                     {
                        "input": "src/less/styles.less",
                        "bundleName": "app"
                     }
                  ],

My hunch is that the script that is inserted into index.html by Angular when CSP nonces are active executes too quickly.

<script nonce="">(() => {
  const children = document.head.children;
  function onLoad() {this.media = this.getAttribute('ngCspMedia');}
  for (let i = 0; i < children.length; i++) {
    const child = children[i];
    child.hasAttribute('ngCspMedia') && child.addEventListener('load', onLoad);
  }
})();</script>

@christiaanverwijs christiaanverwijs changed the title Angular does not load first CSS bundle properly on slow connections for sites with multiple stylesheets and CSP nonces enabled Angular does not always load first CSS bundle properly on sites with multiple stylesheets and CSP nonces enabled Jan 23, 2024
@JoostK JoostK transferred this issue from angular/angular Jan 23, 2024
@christiaanverwijs christiaanverwijs changed the title Angular does not always load first CSS bundle properly on sites with multiple stylesheets and CSP nonces enabled Angular does not always load CSS bundles properly on sites with multiple stylesheets and CSP nonces enabled Jan 23, 2024
@alan-agius4
Copy link
Collaborator

@christiaanverwijs, can you replicate this issue locally using the provided Github reproduction? I could successfully reproduce it on the public site (https://test-cspissue.theliberatorsimproveyourteam.com/), but encountered difficulty when attempting to do so with the GitHub reproduction.

@alan-agius4 alan-agius4 added the needs: more info Reporter must clarify the issue label Jan 24, 2024
@christiaanverwijs
Copy link
Author

christiaanverwijs commented Jan 24, 2024

@alan-agius4 To reproduce it locally, the code would need to be run through a local webserver that adds the proper CSP headers with nonces for styles. Or at least, the proper CSP headers would need to be added.

I do know how to set up a local webserver and set CSP headers with .NET, though not directly in Typescript/Angular. So I was not able to include that as part of the Github repo that I shared. Perhaps someone can help with this?

@alan-agius4 alan-agius4 added type: bug/fix freq1: low Only reported by a handful of users who observe it rarely severity3: broken and removed needs: more info Reporter must clarify the issue labels Jan 24, 2024
@alan-agius4 alan-agius4 self-assigned this Jan 24, 2024
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…iple stylesheets and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…iple stylesheets and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…iple stylesheets and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…iple stylesheets and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 24, 2024
…and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes angular#26932
alan-agius4 added a commit that referenced this issue Jan 24, 2024
…iple stylesheets and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes #26932
alan-agius4 added a commit that referenced this issue Jan 24, 2024
…and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes #26932
alan-agius4 added a commit that referenced this issue Jan 24, 2024
…iple stylesheets and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes #26932

(cherry picked from commit c93ea15)
alan-agius4 added a commit that referenced this issue Jan 24, 2024
…and CSP nonces

The `load` event for each stylesheet may not always be triggered by Google Chrome's handling. Refer to: https://crbug.com/1521256

This results in the media attribute persistently being set to print, leading to distorted styles in the UI. To address this issue, we substitute the onload logic by replacing `link.addEventListener('load', ...` with `document.documentElement.addEventListener('load', ...` and filtering for link tags.

Closes #26932

(cherry picked from commit eef1aa8)
@christiaanverwijs
Copy link
Author

christiaanverwijs commented Jan 24, 2024

Thank you for resolving this @alan-agius4. I'm glad it will allow us to re-enable nonce-based CSP security. Once the new version is released, I will update the test environment and verify it there also again. If its all good, I will tear that environment down.

@alan-agius4
Copy link
Collaborator

Hi @christiaanverwijs, a new version 17.1.1 which contains the fixes has been just released.

@christiaanverwijs
Copy link
Author

@alan-agius4 I deployed an updated version of the test setup. The issue is gone. Thanks again. Great job fixing this so quickly. It was a headscratcher for a day what was causing the styles to break seemingly randomly.

@alan-agius4
Copy link
Collaborator

Glad to hear that the issue is now fixed.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.