Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion goldens/size-tracking/integration-payloads.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
"defer": {
"uncompressed": {
"main": 12709,
"main": 12014,
"polyfills": 33807,
"defer.component": 345
}
Expand Down
24 changes: 0 additions & 24 deletions packages/platform-browser/src/dom/dom_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ export class DomRendererFactory2 implements RendererFactory2, OnDestroy {
case ViewEncapsulation.ShadowDom:
return new ShadowDomRenderer(
eventManager,
sharedStylesHost,
element,
type,
doc,
Expand Down Expand Up @@ -496,7 +495,6 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {

constructor(
eventManager: EventManager,
private sharedStylesHost: SharedStylesHost,
private hostEl: any,
component: RendererType2,
doc: Document,
Expand All @@ -507,7 +505,6 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
) {
super(eventManager, doc, ngZone, platformIsServer, tracingService);
this.shadowRoot = (hostEl as any).attachShadow({mode: 'open'});
this.sharedStylesHost.addHost(this.shadowRoot);
let styles = component.styles;
if (ngDevMode) {
// We only do this in development, as for production users should not add CSS sourcemaps to components.
Expand All @@ -527,23 +524,6 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
styleEl.textContent = style;
this.shadowRoot.appendChild(styleEl);
}

// Apply any external component styles to the shadow root for the component's element.
// The ShadowDOM renderer uses an alternative execution path for component styles that
// does not use the SharedStylesHost that other encapsulation modes leverage. Much like
// the manual addition of embedded styles directly above, any external stylesheets
// must be manually added here to ensure ShadowDOM components are correctly styled.
// TODO: Consider reworking the DOM Renderers to consolidate style handling.
const styleUrls = component.getExternalStyles?.();
if (styleUrls) {
for (const styleUrl of styleUrls) {
const linkEl = createLinkElement(styleUrl, doc);
if (nonce) {
linkEl.setAttribute('nonce', nonce);
}
this.shadowRoot.appendChild(linkEl);
}
}
}

private nodeOrShadowRoot(node: any): any {
Expand All @@ -562,10 +542,6 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
override parentNode(node: any): any {
return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(node)));
}

override destroy() {
this.sharedStylesHost.removeHost(this.shadowRoot);
}
}

class NoneEncapsulationDomRenderer extends DefaultDomRenderer2 {
Expand Down
124 changes: 111 additions & 13 deletions packages/platform-browser/test/dom/dom_renderer_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ describe('DefaultDomRendererV2', () => {
declarations: [
TestCmp,
SomeApp,
ShadowComponentParentApp,
SomeAppForCleanUp,
CmpEncapsulationEmulated,
CmpEncapsulationNone,
CmpEncapsulationShadow,
CmpEncapsulationShadowWithChildren,
],
});
renderer = TestBed.createComponent(TestCmp).componentInstance.renderer;
Expand Down Expand Up @@ -109,22 +111,66 @@ describe('DefaultDomRendererV2', () => {
});
});

it('should allow to style components with emulated encapsulation and no encapsulation inside of components with shadow DOM', () => {
it('should style non-descendant components correctly with different types of encapsulation', () => {
const fixture = TestBed.createComponent(SomeApp);
fixture.detectChanges();

const cmp = fixture.debugElement.query(By.css('cmp-shadow')).nativeElement;
const shadow = cmp.shadowRoot.querySelector('.shadow');

const shadowRoot = cmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).color).toEqual('rgb(255, 0, 0)');

const emulated = cmp.shadowRoot.querySelector('.emulated');
const emulated = fixture.debugElement.query(By.css('.emulated')).nativeElement;
expect(window.getComputedStyle(emulated).color).toEqual('rgb(0, 0, 255)');

const none = cmp.shadowRoot.querySelector('.none');
const none = fixture.debugElement.query(By.css('.none')).nativeElement;
expect(window.getComputedStyle(none).color).toEqual('rgb(0, 255, 0)');
});

it('should encapsulate shadow DOM components, with child components inheriting from shadow styles not global styles', () => {
const fixture = TestBed.createComponent(ShadowComponentParentApp);
fixture.detectChanges();

const shadowcmp = fixture.debugElement.query(By.css('cmp-shadow-children')).nativeElement;
const shadowRoot = shadowcmp.shadowRoot;

const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).color).toEqual('rgb(255, 0, 0)');

const emulated = fixture.debugElement.query(By.css('.emulated')).nativeElement;
expect(window.getComputedStyle(emulated).color).toEqual('rgb(255, 0, 0)');

const none = fixture.debugElement.query(By.css('.none')).nativeElement;
expect(window.getComputedStyle(none).color).toEqual('rgb(255, 0, 0)');
});

it('child components of shadow components should inherit browser defaults rather than their component styles', () => {
const fixture = TestBed.createComponent(ShadowComponentParentApp);
fixture.detectChanges();

const shadowcmp = fixture.debugElement.query(By.css('cmp-shadow-children')).nativeElement;
const shadowRoot = shadowcmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).backgroundColor).toEqual('rgba(0, 0, 0, 0)');

const emulated = fixture.debugElement.query(By.css('.emulated')).nativeElement;
expect(window.getComputedStyle(emulated).backgroundColor).toEqual('rgba(0, 0, 0, 0)');

const none = fixture.debugElement.query(By.css('.none')).nativeElement;
expect(window.getComputedStyle(none).backgroundColor).toEqual('rgba(0, 0, 0, 0)');
});

it('shadow components should not be polluted by child components styles', () => {
const fixture = TestBed.createComponent(ShadowComponentParentApp);
fixture.detectChanges();

const cmp = fixture.debugElement.query(By.css('cmp-shadow-children')).nativeElement;
const shadowRoot = cmp.shadowRoot;
const shadow = shadowRoot.querySelector('.shadow');
expect(window.getComputedStyle(shadow).backgroundColor).not.toEqual('rgb(0, 0, 255)');
expect(window.getComputedStyle(shadow).backgroundColor).not.toEqual('rgb(0, 255, 0)');
});

it('should be able to append children to a <template> element', () => {
const template = document.createElement('template');
const child = document.createElement('div');
Expand Down Expand Up @@ -378,40 +424,82 @@ async function styleCount(

@Component({
selector: 'cmp-emulated',
template: `<div class="emulated"></div>`,
styles: [`.emulated { color: blue; }`],
template: `
<div class="emulated"></div>`,
styles: [
`.emulated {
background-color: blue;
color: blue;
}`,
],
encapsulation: ViewEncapsulation.Emulated,
standalone: false,
})
class CmpEncapsulationEmulated {}

@Component({
selector: 'cmp-none',
template: `<div class="none"></div>`,
styles: [`.none { color: lime; }`],
template: `
<div class="none"></div>`,
styles: [
`.none {
background-color: lime;
color: lime;
}`,
],
encapsulation: ViewEncapsulation.None,
standalone: false,
})
class CmpEncapsulationNone {}

@Component({
selector: 'cmp-none',
template: `<div class="none"></div>`,
styles: [`.none { color: lime; }\n/*# sourceMappingURL=cmp-none.css.map */`],
template: `
<div class="none"></div>`,
styles: [
`.none {
background-color: lime;
color: lime;
}

/*# sourceMappingURL=cmp-none.css.map */`,
],
encapsulation: ViewEncapsulation.None,
standalone: false,
})
class CmpEncapsulationNoneWithSourceMap {}

@Component({
selector: 'cmp-shadow',
template: `<div class="shadow"></div><cmp-emulated></cmp-emulated><cmp-none></cmp-none>`,
styles: [`.shadow { color: red; }`],
template: `
<div class="shadow"></div>`,
styles: [
`.shadow {
color: red;
}`,
],
encapsulation: ViewEncapsulation.ShadowDom,
standalone: false,
})
class CmpEncapsulationShadow {}

@Component({
selector: 'cmp-shadow-children',
template: `
<div class="shadow">
<cmp-emulated></cmp-emulated>
<cmp-none></cmp-none>
</div>`,
styles: [
`.shadow {
color: red;
}`,
],
encapsulation: ViewEncapsulation.ShadowDom,
standalone: false,
})
class CmpEncapsulationShadowWithChildren {}

@Component({
selector: 'some-app',
template: `
Expand All @@ -423,6 +511,16 @@ class CmpEncapsulationShadow {}
})
export class SomeApp {}

@Component({
selector: 'shadow-parent-app-with-children',
template: `
<cmp-shadow-children>
</cmp-shadow-children>
`,
standalone: false,
})
export class ShadowComponentParentApp {}

@Component({
selector: 'test-cmp',
template: '',
Expand Down