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

Fix[ISSUE_1819]: Support for serialising ShadowRoot Element for Chrome 131 onwards #1820

Merged
merged 2 commits into from
Dec 11, 2024
Merged
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
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; }
Copy link
Contributor Author

@this-is-shivamsingh this-is-shivamsingh Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Need to remove this, as this was blocking snapshot CLI flow, as our discovery is Chrome 123, and getHTML is only supported from Chrome 125 onwards
  • It's handled in else block now

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
Loading