Skip to content
Open
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
10 changes: 5 additions & 5 deletions src/clone-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ async function cloneChildren<T extends HTMLElement>(

if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
children = toArray<T>(nativeNode.assignedNodes())
} else if (
isInstanceOfElement(nativeNode, HTMLIFrameElement) &&
nativeNode.contentDocument?.body
) {
children = toArray<T>(nativeNode.contentDocument.body.childNodes)
} else if (isInstanceOfElement(nativeNode, HTMLIFrameElement)) {
// cloneIFrameElement (called by cloneSingleNode) already recursively clones
// the iframe's contentDocument.body and all its descendants.
// Appending children here would duplicate the iframe content.
return clonedNode
} else {
children = toArray<T>((nativeNode.shadowRoot ?? nativeNode).childNodes)
}
Expand Down
8 changes: 8 additions & 0 deletions test/resources/iframe-content/node.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div id="iframe-wrapper" style="width:200px;height:80px;">
<iframe
id="test-iframe"
title="Test iframe content"
style="width:100%;height:100%;border:none;"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>iframe test</title></head><body><p class='iframe-para' data-testid='iframe-para'>IFRAME_CONTENT</p></body></html>"
Copy link

Choose a reason for hiding this comment

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

WCAG 2.4.2: Document is missing a <title> element.

Documents must have a <title> element to provide users with an overview of content.

Details

Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').


Best Practice: Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.

Page must have a mechanism to bypass repeated blocks of content.

Details

Missing: no landmarks (<main>, <nav>, <header>, <footer>), no skip link, no headings

Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.


Best Practice: Page does not contain a level-one heading.

Page should contain a level-one heading.

Details

A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.


Best Practice: Page has no main landmark.

Page should have exactly one main landmark.

Details

The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.


WCAG 3.1.1: <html> element missing lang attribute.

The <html> element must have a lang attribute.

Details

Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish).

></iframe>
</div>
38 changes: 38 additions & 0 deletions test/spec/special.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import '../spec/setup'
import { toPng } from '../../src'
import { cloneNode } from '../../src/clone-node'
import { delay } from '../../src/util'
import { assertTextRendered, bootstrap, renderAndCheck } from '../spec/helper'

Expand Down Expand Up @@ -59,4 +60,41 @@ describe('special cases', () => {
.then(done)
.catch(done)
})

it('should not duplicate iframe content when cloning', (done) => {
// Regression test for: cloneChildren() re-appended iframe body childNodes
// after cloneSingleNode() → cloneIFrameElement() had already recursively
// cloned the full iframe body, causing every child to appear twice.
bootstrap('iframe-content/node.html')
.then((node) => {
const iframe = node.querySelector('iframe') as HTMLIFrameElement
// Poll until srcdoc iframe body is accessible (async load)
return new Promise<HTMLDivElement>((resolve, reject) => {
let attempts = 0
const poll = () => {
attempts++
const ready =
iframe.contentDocument?.body?.querySelector('.iframe-para')
if (ready) {
resolve(node)
} else if (attempts > 30) {
reject(new Error('iframe did not load in time'))
} else {
setTimeout(poll, 100)
}
}
poll()
})
})
.then((node) => cloneNode(node, {}, true))
.then((clonedNode) => {
expect(clonedNode).not.toBeNull()
// With the bug: two .iframe-para elements appear (duplicate children).
// With the fix: exactly one .iframe-para appears.
const paras = clonedNode!.querySelectorAll('.iframe-para')
expect(paras.length).toBe(1)
})
.then(done)
.catch(done)
})
})
Loading