Skip to content

Commit

Permalink
feat: WIP attribute whitelist
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanPiercey committed Aug 20, 2019
1 parent 2568b2e commit c5ed821
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 82 deletions.
56 changes: 28 additions & 28 deletions src/__tests__/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,34 +44,34 @@ test("runs the first example", () => {
`
)
).toMatchInlineSnapshot(`
"<div style=\\"
transform: translateX(-100px);
width: 100px;
height: 200px;
background: red
\\">
<span style=\\"color: #333\\">
Hello!
</span>
<form>
<label>
Username:
<input/>
</label>
<label>
Password:
<input/>
</label>
<label>
Remember Me:
<input type=\\"checkbox\\"/>
</label>
<button>
Sign in
</button>
</form>
</div>"
`);
"<div style=\\"
transform: translateX(-100px);
width: 100px;
height: 200px;
background: red
\\">
<span style=\\"color: #333\\">
Hello!
</span>
<form>
<label>
Username:
<input/>
</label>
<label>
Password:
<input type=\\"password\\"/>
</label>
<label>
Remember Me:
<input type=\\"checkbox\\"/>
</label>
<button>
Sign in
</button>
</form>
</div>"
`);
});

test("works with diff snapshots", () => {
Expand Down
74 changes: 20 additions & 54 deletions src/attributes.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,34 @@
let FRAME: HTMLIFrameElement | null = null;
import { PROPERTIES } from "./properties";

/**
* Given an element, returns any attributes that have a cause a visual change.
* This works by copying the element to an iframe without any styles and
* testing the computed styles while toggling the attributes.
* This works by checking against a whitelist of known visual properties, and
* their related attribute name.
*/
export function getVisualAttributes(el: Element) {
let visualAttributes: Array<{ name: string; value: string }> | null = null;
const document = el.ownerDocument!;
FRAME = FRAME || document.createElement("iframe");

document.body.appendChild(FRAME);

const contentDocument = FRAME.contentDocument!;
const contentWindow = contentDocument.defaultView!;
const clone = contentDocument.importNode(el, false);
const { attributes } = clone;

contentDocument.body.appendChild(clone);

const defaultStyles = contentWindow.getComputedStyle(clone);

for (let i = attributes.length; i--; ) {
const attr = attributes[i];

if (attr.name === "style") {
const props = PROPERTIES["http://www.w3.org/1999/xhtml"];
const defaults = el.ownerDocument!.createElementNS(
el.namespaceURI || null,
el.localName
);

for (const prop in props) {
const { alias, tests } = props[prop as keyof typeof props];
const name = alias || prop;
const value = el[prop];

if (value === defaults[prop]) {
continue;
}

clone.removeAttributeNode(attr);

if (
!computedStylesEqual(defaultStyles, contentWindow.getComputedStyle(clone))
) {
(visualAttributes || (visualAttributes = [])).push({
name: attr.name,
value: attr.value
});
for (const test of tests) {
if (test(el)) {
(visualAttributes || (visualAttributes = [])).push({ name, value });
break;
}
}

clone.setAttributeNode(attr);
}

contentDocument.body.removeChild(clone);
document.body.removeChild(FRAME);

return visualAttributes;
}

/**
* Checks if two CSSStyleDeclarations have the same styles applied.
*/
function computedStylesEqual(a: CSSStyleDeclaration, b: CSSStyleDeclaration) {
if (a.length !== b.length) {
return false;
}

for (let i = a.length; i--; ) {
const name = a[i];

if (a[name] !== b[name]) {
return false;
}
}

return true;
}
Loading

0 comments on commit c5ed821

Please sign in to comment.