From ca1c98381490aa1fb451cfa704937688346ba40f Mon Sep 17 00:00:00 2001 From: Wil Wilsman Date: Tue, 6 Jul 2021 14:32:08 -0500 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20Serialize=20iframe=20documen?= =?UTF-8?q?t=20URLs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/dom/src/serialize-frames.js | 31 ++++++++++++++++++++++ packages/dom/test/serialize-frames.test.js | 21 +++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/packages/dom/src/serialize-frames.js b/packages/dom/src/serialize-frames.js index d6ef7c6bd..bfa1d7b0a 100644 --- a/packages/dom/src/serialize-frames.js +++ b/packages/dom/src/serialize-frames.js @@ -1,5 +1,35 @@ import serializeDOM from './serialize-dom'; +// List of attributes that accept URIs that should be transformed +const URI_ATTRS = ['href', 'src', 'srcset', 'poster', 'background']; +const URI_SELECTOR = URI_ATTRS.map(a => `[${a}]`).join(','); + +// A loose srcset image candidate regex split into capture groups +// https://html.spec.whatwg.org/multipage/images.html#srcset-attribute +const SRCSET_REGEX = /(\s*)([^,]\S*[^,])((?:\s+[^,]+)*\s*(?:,|$))/g; + +// Transforms URL attributes within a document to be fully qualified URLs. This is necessary when +// embedded documents are serialized and their contents become root-relative. +function transformRelativeUrls(dom) { + for (let el of dom.querySelectorAll(URI_SELECTOR)) { + for (let attr of URI_ATTRS) { + if (!(attr in el) || !el[attr]) continue; + let value = el[attr]; + + if (attr === 'srcset') { + // the srcset attribute needs to be parsed + value = value.replace(SRCSET_REGEX, (_, $1, uri, $3) => ( + `${$1}${new URL(uri, el.baseURI).href}${$3}` + )); + } else { + value = new URL(value, el.baseURI).href; + } + + el.setAttribute(attr, value); + } + } +} + // Recursively serializes iframe documents into srcdoc attributes. export default function serializeFrames(dom, clone, { enableJavaScript }) { for (let frame of dom.querySelectorAll('iframe')) { @@ -22,6 +52,7 @@ export default function serializeFrames(dom, clone, { enableJavaScript }) { // recersively serialize contents let serialized = serializeDOM({ + domTransformation: transformRelativeUrls, dom: frame.contentDocument, enableJavaScript }); diff --git a/packages/dom/test/serialize-frames.test.js b/packages/dom/test/serialize-frames.test.js index fc7fa1aa5..1a119263d 100644 --- a/packages/dom/test/serialize-frames.test.js +++ b/packages/dom/test/serialize-frames.test.js @@ -26,6 +26,14 @@ describe('serializeFrames', () => { + `); let $frameInput = await getFrame('frame-input'); @@ -82,6 +90,19 @@ describe('serializeFrames', () => { ].join(''))); }); + it('serializes iframes internal URLs', () => { + let url = uri => new URL(uri, document.URL).href; + + expect($('#frame-with-urls')[0].getAttribute('srcdoc')).toBe([ + ``, + ``, + `', + `` + // account for indentation in the fixture + ].join(' '.repeat(12)) + ''); + }); + it('does not serialize iframes with CORS', () => { expect($('#frame-external')[0].getAttribute('src')).toBe('https://example.com'); expect($('#frame-external-fail')[0].getAttribute('src')).toBe('https://google.com'); From 297e21434afdaa1b6bafc364ddce30f68eae3bd6 Mon Sep 17 00:00:00 2001 From: Wil Wilsman Date: Tue, 6 Jul 2021 14:32:53 -0500 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7=20Add=20fake=20karma=20proxy?= =?UTF-8?q?=20for=20fake=20assets=20to=20prevent=20404s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- karma.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/karma.config.js b/karma.config.js index e83bdd7b5..6981d737a 100644 --- a/karma.config.js +++ b/karma.config.js @@ -22,6 +22,11 @@ module.exports = config => config.set({ { pattern: 'test/**/*.test.js', watched: false } ], + proxies: { + // useful when the contents of a fake asset do not matter + '/_/': 'localhost/' + }, + // create dedicated bundles for src, test helpers, and each test suite preprocessors: { 'src/index.js': ['rollup'], From b89d4c7fcb54a7f356159af1d1a90f024b1afbd5 Mon Sep 17 00:00:00 2001 From: Wil Wilsman Date: Tue, 6 Jul 2021 17:05:12 -0500 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20Serialize=20iframe=20CSS=20U?= =?UTF-8?q?RLs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/dom/src/serialize-frames.js | 30 ++++++++++++++++++---- packages/dom/test/serialize-frames.test.js | 4 +++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/dom/src/serialize-frames.js b/packages/dom/src/serialize-frames.js index bfa1d7b0a..44d346cad 100644 --- a/packages/dom/src/serialize-frames.js +++ b/packages/dom/src/serialize-frames.js @@ -2,26 +2,46 @@ import serializeDOM from './serialize-dom'; // List of attributes that accept URIs that should be transformed const URI_ATTRS = ['href', 'src', 'srcset', 'poster', 'background']; -const URI_SELECTOR = URI_ATTRS.map(a => `[${a}]`).join(','); +const URI_SELECTOR = URI_ATTRS.map(a => `[${a}]`).join(',') + ( + ',[style*="url("]'); // include elements with url style attributes // A loose srcset image candidate regex split into capture groups // https://html.spec.whatwg.org/multipage/images.html#srcset-attribute const SRCSET_REGEX = /(\s*)([^,]\S*[^,])((?:\s+[^,]+)*\s*(?:,|$))/g; +// A loose CSS url() regex split into capture groups +const CSS_URL_REGEX = /(url\((["']?))((?:\\.|(?!\2).|[^)])+)(\2\))/g; + // Transforms URL attributes within a document to be fully qualified URLs. This is necessary when // embedded documents are serialized and their contents become root-relative. function transformRelativeUrls(dom) { + // transform style elements that might contain URLs + for (let style of dom.querySelectorAll('style')) { + style.innerHTML &&= style.innerHTML + .replace(CSS_URL_REGEX, (_, $1, $2, uri, $4) => ( + `${$1}${new URL(uri, style.baseURI).href}${$4}` + )); + } + + // transform element attributes that might contain URLs for (let el of dom.querySelectorAll(URI_SELECTOR)) { - for (let attr of URI_ATTRS) { - if (!(attr in el) || !el[attr]) continue; + for (let attr of URI_ATTRS.concat('style')) { + if (!(attr in el) || !el[attr] || !el.hasAttribute(attr)) continue; let value = el[attr]; - if (attr === 'srcset') { - // the srcset attribute needs to be parsed + if (attr === 'style') { + // transform inline style url() usage + value = el.getAttribute('style') + .replace(CSS_URL_REGEX, (_, $1, $2, uri, $4) => ( + `${$1}${new URL(uri, el.baseURI).href}${$4}` + )); + } else if (attr === 'srcset') { + // transform each srcset URL value = value.replace(SRCSET_REGEX, (_, $1, uri, $3) => ( `${$1}${new URL(uri, el.baseURI).href}${$3}` )); } else { + // resolve the URL with the node's base URI value = new URL(value, el.baseURI).href; } diff --git a/packages/dom/test/serialize-frames.test.js b/packages/dom/test/serialize-frames.test.js index 1a119263d..c99feee8f 100644 --- a/packages/dom/test/serialize-frames.test.js +++ b/packages/dom/test/serialize-frames.test.js @@ -29,6 +29,8 @@ describe('serializeFrames', () => {