Skip to content

Commit

Permalink
🐛 Restrict CSSOM serialization to modified stylesheets (#1056)
Browse files Browse the repository at this point in the history
  • Loading branch information
wwilsman authored Sep 7, 2022
1 parent 89e13e0 commit 079676b
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 9 deletions.
14 changes: 13 additions & 1 deletion packages/dom/src/serialize-cssom.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ function isCSSOM(styleSheet) {
return !styleSheet.href && styleSheet.cssRules && styleSheet.ownerNode;
}

// Returns false if any stylesheet rules do not match between two stylesheets
function styleSheetsMatch(sheetA, sheetB) {
for (let i = 0; i < sheetA.cssRules.length; i++) {
let ruleA = sheetA.cssRules[i].cssText;
let ruleB = sheetB.cssRules[i]?.cssText;
if (ruleA !== ruleB) return false;
}

return true;
}

// Outputs in-memory CSSOM into their respective DOM nodes.
export function serializeCSSOM(dom, clone) {
for (let styleSheet of dom.styleSheets) {
if (isCSSOM(styleSheet)) {
let style = clone.createElement('style');
let styleId = styleSheet.ownerNode.getAttribute('data-percy-element-id');
let cloneOwnerNode = clone.querySelector(`[data-percy-element-id="${styleId}"]`);
if (styleSheetsMatch(styleSheet, cloneOwnerNode.sheet)) continue;
let style = clone.createElement('style');

style.type = 'text/css';
style.setAttribute('data-percy-element-id', styleId);
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function withCSSOM(rules = [], prepare = () => {}) {
$test.prepend($style);

for (let rule of [].concat(rules)) {
document.styleSheets[0].insertRule(rule);
$style.sheet.insertRule(rule);
}
}

Expand Down
25 changes: 18 additions & 7 deletions packages/dom/test/serialize-css.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,31 @@ import serializeDOM from '@percy/dom';
describe('serializeCSSOM', () => {
beforeEach(() => {
let link = '<link rel="stylesheet" href="data:text/css,.box { margin: 10px; }"/>';
let style = '<style>.box { display: inline-block; }</style>';
let mod = '<style id="mod">.box { width: 500px; }</style>';
let style = '<style>.box { background: green; }</style>';

withExample(`<div class="box"></div>${link}${style}`);
withCSSOM('.box { height: 500px; width: 500px; background-color: green; }');
withExample(`<div class="box"></div>${link}${mod}${style}`);
withCSSOM('.box { height: 500px; }');

let modCSSRule = document.getElementById('mod').sheet.cssRules[0];
if (modCSSRule) modCSSRule.style.cssText = 'width: 1000px';

// give the linked style a few milliseconds to load
return new Promise(r => setTimeout(r, 100));
});

it('serializes CSSOM and does not mutate the orignal DOM', () => {
let $cssom = parseDOM(serializeDOM())('[data-percy-cssom-serialized]');

// linked stylesheet is not included
// linked and unmodified stylesheets are not included
expect($cssom).toHaveSize(2);
expect($cssom[0].innerHTML).toBe('.box { height: 500px; width: 500px; background-color: green; }');
expect($cssom[1].innerHTML).toBe('.box { display: inline-block; }');
expect(document.styleSheets[0]).toHaveProperty('ownerNode.innerText', '');
expect($cssom[0].innerHTML).toBe('.box { height: 500px; }');
expect($cssom[1].innerHTML).toBe('.box { width: 1000px; }');

expect(document.styleSheets[0].ownerNode.innerText).toBe('');
expect(document.styleSheets[1].ownerNode.innerText).toBe('');
expect(document.styleSheets[2].ownerNode.innerText).toBe('.box { width: 500px; }');
expect(document.styleSheets[3].ownerNode.innerText).toBe('.box { background: green; }');
expect(document.querySelectorAll('[data-percy-cssom-serialized]')).toHaveSize(0);
});

Expand Down

0 comments on commit 079676b

Please sign in to comment.