-
Notifications
You must be signed in to change notification settings - Fork 26.3k
fix(platform-browser): unencapsulated styles leaking out of shadow dom host #42112
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
base: main
Are you sure you want to change the base?
Conversation
61e188e
to
4ac9d7f
Compare
// Maps all registered host nodes to a list of style nodes that have been added to the host node. | ||
private _hostNodes = new Map<Node, Node[]>(); | ||
// Mapping of component host nodes to the nodes in which their styles are inserted. | ||
private _hostNodes = new Map<Node, Map<Node, Node[]>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there really a need to store a 1:n mapping from host node to insertion node? Isn't there always a single insertion node per host node?
IIUC, there are now multiple host nodes associated with a single insertion node (either the shadow root or the document head) but this mapping stores the reverse relation; from host node to their insertion point, which I think is singular.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is, but how would we distinguish which styles belong to which host node? I was trying to solve the issue where multiple host nodes point to a single insertion node and then one of the hosts is removed. In this case we don't want to clear the entire insertion node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be sufficient:
private _hostNodes = new Map<Node, { insertionPoint: Node; styles: Node[] }>();
Then removing a host node can remove the corresponding styles from its insertion point. But I realize that the above is still insufficient when there's multiple host nodes that share styles that they have attached into the same insertion point; that is a bit harder to track.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think that we could do it by changing SharedStylesHost._stylesSet
to a Map<string, Set<Node>>
where the keys are the inserted styles and the set is the insertion nodes that have received the styles. My concern is that this will slow down cleanup, because we'd have to look through all the styles in order to remove the insertion node reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a change that should simplify this and resolve the issue where the same styles are inserted multiple times. It uses the insertion node as the key and keeps a counter of host nodes associated with the insertion node. The only downside is that we'll only clear the styles once every host node has been destroyed, but that doesn't seem like it would be too much of an issue?
// styles leak out into the document. | ||
const rootNode = element.getRootNode?.(); | ||
if (typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot) { | ||
return new ShadowDomRenderer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that ShadowDomRenderer
is doing style flattening in its constructor for every instantiation, instead of doing it only once and caching the result somewhere. This includes the regex replace for the %COMP%
pattern, but that won't even be present as that is only applied by the compiler for components that use emulated style encapsulation. We can probably optimize this a bit, which has become more relevant now that the shadow DOM renderer is used more often (also because this can now trigger usage of ShadowDomRenderer
even if not used in the Angular app itself, but bootstrapped into a shadow tree).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Similar flattening is done in the EmulatedEncapsulationDomRenderer2
but there it is less of an issue as rendered instances are cached per component.id. I wonder why the "flattening" isn't done in the compiler, TBH.
Also "flatten" is confusing right now as it does both flattening and %COMP%
replacement - we should probably split those 2 different responsibilities.
Having said this I think that it is not strictly related to this PR - we can do those improvements in a separate PR (I'm tempted to do it so it is clearer what is going on....)
fabe899
to
8456b8b
Compare
8456b8b
to
5aaf89a
Compare
…m host Currently when a component with `ViewEncapsulation.None` is inserted, it always adds its styles to `document.head`. This can be problematic when the component is inside the shadow DOM, because it can cause styles to leak out of it. These changes resolve the issue by adding some extra logic to the `SharedStylesHost` that allows for multiple "insertion nodes" to be associated with a component host node. Then we use this new functionality to associate the unencapsulated component styles with the closest shadow root. Note that in most cases we'll have one insertion node per host node, but we have to support multiple for the case where several unencapsulated components are inserted inside a shadow DOM host. Fixes angular#35039.
d980eb4
to
4448e75
Compare
4448e75
to
a8e7b5f
Compare
@crisbeto Did you still want to land this PR? |
Yes, I think that all the comments were addressed but the discussion has stalled. |
Any news ? |
Also waiting for this to be merged. It would be great to have this soon in Angular. Are there any news on it? |
Hi, any news on this? We ran into the same issue, when providing our Angular webcomponents which led to break the style of the host-application. (both applications used Angular Material, but in different versions). Thank you! |
shouldn't this be closed because of #58482 |
Currently when a component with
ViewEncapsulation.None
is inserted, it always adds its styles todocument.head
. This can be problematic when the component is inside the shadow DOM, because it can cause styles to leak out of it.These changes resolve the issue by adding some extra logic to the
SharedStylesHost
that allows for multiple "insertion nodes" to be associated with a component host node. Then we use this new functionality to associate the unencapsulated component styles with the closest shadow root.Note that in most cases we'll have one insertion node per host node, but we have to support multiple for the case where several unencapsulated components are inserted inside a shadow DOM host.
Fixes #35039.