diff --git a/src/rebuild.ts b/src/rebuild.ts index f6be61ca..bcf1f681 100644 --- a/src/rebuild.ts +++ b/src/rebuild.ts @@ -1,4 +1,15 @@ -import { serializedNodeWithId, NodeType } from './types'; +import { serializedNodeWithId, NodeType, tagMap, elementNode } from './types'; + +const tagMap: tagMap = { + script: 'noscript', +}; +function getTagName(n: elementNode): string { + let tagName = tagMap[n.tagName] ? tagMap[n.tagName] : n.tagName; + if (tagName === 'link' && n.attributes._cssText) { + tagName = 'style'; + } + return tagName; +} function buildNode(n: serializedNodeWithId): Node | null { switch (n.type) { @@ -11,14 +22,15 @@ function buildNode(n: serializedNodeWithId): Node | null { n.systemId, ); case NodeType.Element: - const tagName = n.tagName === 'script' ? 'noscript' : n.tagName; + const tagName = getTagName(n); const node = document.createElement(tagName); for (const name in n.attributes) { if (n.attributes.hasOwnProperty(name)) { let value = n.attributes[name]; value = typeof value === 'boolean' ? '' : value; - // textarea hack - if (n.tagName === 'textarea' && name === 'value') { + const isTextarea = tagName === 'textarea' && name === 'value'; + const isRemoteCss = tagName === 'style' && name === '_cssText'; + if (isTextarea || isRemoteCss) { const child = document.createTextNode(value); node.appendChild(child); continue; diff --git a/src/snapshot.ts b/src/snapshot.ts index c6f4eb60..c5af9f58 100644 --- a/src/snapshot.ts +++ b/src/snapshot.ts @@ -15,7 +15,18 @@ function resetId() { _id = 1; } -function serializeNode(n: Node): serializedNode | false { +function getCssRulesString(s: CSSStyleSheet): string | null { + try { + const rules = s.rules || s.cssRules; + return rules + ? Array.from(rules).reduce((prev, cur) => (prev += cur.cssText), '') + : null; + } catch (error) { + return null; + } +} + +function serializeNode(n: Node, doc: Document): serializedNode | false { switch (n.nodeType) { case n.DOCUMENT_NODE: return { @@ -31,10 +42,23 @@ function serializeNode(n: Node): serializedNode | false { }; case n.ELEMENT_NODE: const tagName = (n as HTMLElement).tagName.toLowerCase(); - const attributes: attributes = {}; + let attributes: attributes = {}; for (const { name, value } of Array.from((n as HTMLElement).attributes)) { attributes[name] = value; } + // remote css + if (tagName === 'link' && attributes.hasOwnProperty('href')) { + const stylesheet = Array.from(doc.styleSheets).find( + s => s.href === attributes.href, + ); + const cssText = getCssRulesString(stylesheet as CSSStyleSheet); + if (cssText) { + attributes = { + _cssText: cssText, + }; + } + } + // form fields if ( tagName === 'input' || tagName === 'textarea' || @@ -91,8 +115,8 @@ function serializeNode(n: Node): serializedNode | false { } } -function _snapshot(n: Node): serializedNodeWithId | null { - const _serializedNode = serializeNode(n); +function _snapshot(n: Node, doc: Document): serializedNodeWithId | null { + const _serializedNode = serializeNode(n, doc); if (!_serializedNode) { // TODO: dev only console.warn(n, 'not serialized'); @@ -106,15 +130,15 @@ function _snapshot(n: Node): serializedNodeWithId | null { serializedNode.type === NodeType.Element ) { for (const childN of Array.from(n.childNodes)) { - serializedNode.childNodes.push(_snapshot(childN)); + serializedNode.childNodes.push(_snapshot(childN, doc)); } } return serializedNode; } -function snapshot(n: Node): serializedNodeWithId | null { +function snapshot(n: Document): serializedNodeWithId | null { resetId(); - return _snapshot(n); + return _snapshot(n, n); } export default snapshot; diff --git a/src/types.ts b/src/types.ts index 17fc0e42..ea19a198 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,3 +53,7 @@ export type serializedNode = | commentNode; export type serializedNodeWithId = serializedNode & { id: number }; + +export type tagMap = { + [key: string]: string; +}; diff --git a/test/html/cors-style-sheet.html b/test/html/cors-style-sheet.html new file mode 100644 index 00000000..06dddaa2 --- /dev/null +++ b/test/html/cors-style-sheet.html @@ -0,0 +1,16 @@ + + + +
+ + + +