Skip to content

Commit

Permalink
Fix[ISSUE_1819]: Support for serialising ShadowRoot Element for Chrom…
Browse files Browse the repository at this point in the history
…e 131 onwards (#1820)

* feat: added support for shadowRoot elements for custom templates and slot elements

* chore: test fix
  • Loading branch information
this-is-shivamsingh authored Dec 11, 2024
1 parent 4951bc4 commit 8d278a2
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 10 deletions.
16 changes: 8 additions & 8 deletions packages/dom/src/clone-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,20 @@ export function cloneNodeAndShadow(ctx) {
/**
* Use `getInnerHTML()` to serialize shadow dom as <template> tags. `innerHTML` and `outerHTML` don't do this. Buzzword: "declarative shadow dom"
*/
export function getOuterHTML(docElement) {
// All major browsers in latest versions supports getHTML API to get serialized DOM
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getHTML
// old firefox doesn't serialize shadow DOM, we're awaiting API's by firefox to become ready and are not polyfilling it.
// new firefox from 128 onwards serializes it using getHTML
/* istanbul ignore if: Only triggered in firefox <= 128 and tests runs on latest */
if (!docElement.getHTML) { return docElement.outerHTML; }
export function getOuterHTML(docElement, { shadowRootElements }) {
// chromium gives us declarative shadow DOM serialization API
let innerHTML = '';
/* istanbul ignore else if: Only triggered in chrome <= 128 and tests runs on latest */
if (docElement.getHTML) {
innerHTML = docElement.getHTML({ serializableShadowRoots: true });
// All major browsers in latest versions supports getHTML API to get serialized DOM
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getHTML
innerHTML = docElement.getHTML({ serializableShadowRoots: true, shadowRoots: shadowRootElements });
} else if (docElement.getInnerHTML) {
innerHTML = docElement.getInnerHTML({ includeShadowRoots: true });
} else {
// old firefox doesn't serialize shadow DOM, we're awaiting API's by firefox to become ready and are not polyfilling it.
// new firefox from 128 onwards serializes it using getHTML
return docElement.outerHTML;
}
docElement.textContent = '';
// Note: Here we are specifically passing replacer function to avoid any replacements due to
Expand Down
6 changes: 5 additions & 1 deletion packages/dom/src/serialize-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function doctype(dom) {

// Serializes and returns the cloned DOM as an HTML string
function serializeHTML(ctx) {
let html = getOuterHTML(ctx.clone.documentElement);
let html = getOuterHTML(ctx.clone.documentElement, { shadowRootElements: ctx.shadowRootElements });
// replace serialized data attributes with real attributes
html = html.replace(/ data-percy-serialized-attribute-(\w+?)=/ig, ' $1=');
// include the doctype with the html string
Expand All @@ -44,6 +44,9 @@ function serializeElements(ctx) {
let percyElementId = shadowHost.getAttribute('data-percy-element-id');
let cloneShadowHost = ctx.clone.querySelector(`[data-percy-element-id="${percyElementId}"]`);
if (shadowHost.shadowRoot && cloneShadowHost.shadowRoot) {
// getHTML requires shadowRoot to be passed explicitly
// to serialize the shadow elements properly
ctx.shadowRootElements.push(cloneShadowHost.shadowRoot);
serializeElements({
...ctx,
dom: shadowHost.shadowRoot,
Expand Down Expand Up @@ -89,6 +92,7 @@ export function serializeDOM(options) {
warnings: new Set(),
hints: new Set(),
cache: new Map(),
shadowRootElements: [],
enableJavaScript,
disableShadowDOM
};
Expand Down
47 changes: 47 additions & 0 deletions packages/dom/test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,50 @@ export function platformDOM(plat) {
export function assert(condition, message) {
if (!condition) throw new Error(message);
}

export function createAndAttachSlotTemplate(baseElement) {
baseElement.innerHTML = `
<custom-element>
<p slot="title">Hello from the title slot!</p>
<p>This content is distributed into the default slot.</p>
</custom-element>
`;
class CustomElement extends window.HTMLElement {
constructor() {
super();

// Attach shdow DOM
const shadowRoot = this.attachShadow({ mode: 'open' });

// Add template content to shadow DOM
shadowRoot.innerHTML = `
<style>
:host {
display: block;
background-color: #f9f9f9;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
padding: 15px;
font-family: Arial, sans-serif;
}
::slotted([slot="title"]) {
font-size: 1.5em;
font-weight: bold;
color: #333;
}
::slotted(*) {
margin: 5px 0;
}
</style>
<div>
<slot name="title"></slot>
<slot></slot>
</div>
`;
}
}

// Register the custom element

window.customElements.define('custom-element', CustomElement);
}
22 changes: 21 additions & 1 deletion packages/dom/test/serialize-dom.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { withExample, replaceDoctype, createShadowEl, getTestBrowser, chromeBrowser, parseDOM } from './helpers';
import { withExample, replaceDoctype, createShadowEl, getTestBrowser, chromeBrowser, parseDOM, createAndAttachSlotTemplate } from './helpers';
import serializeDOM, { waitForResize } from '@percy/dom';

describe('serializeDOM', () => {
Expand Down Expand Up @@ -275,6 +275,26 @@ describe('serializeDOM', () => {
const serialized = serializeDOM();
expect(serialized.warnings).toEqual(['data-percy-shadow-host does not have shadowRoot']);
});

it('renders slot template with shadowrootmode open', () => {
withExample('<div id="content"></div>', { withShadow: false });
const baseContent = document.querySelector('#content');
createAndAttachSlotTemplate(baseContent);

const html = serializeDOM().html;
expect(html).toMatch('<template shadowrootmode="open">');
expect(html).toMatch('<p slot="title">Hello from the title slot!</p>');
expect(html).toMatch('<p>This content is distributed into the default slot.</p>');

// match style pattern regex
const stylePattern = [
'<style data-percy-element-id=".*">',
':host\\s*{[^}]*}',
'::slotted\\(\\[slot="title"\\]\\)\\s*{[^}]*}',
'::slotted\\(\\*\\)\\s*{[^}]*}'
].join('\\s*');
expect(html).toMatch(new RegExp(stylePattern));
});
});

describe('with `domTransformation`', () => {
Expand Down

0 comments on commit 8d278a2

Please sign in to comment.