Skip to content

Commit a5df325

Browse files
authored
Fix for srcdoc iframes (#113)
* Fix for srcdoc * Lime survey test html * Fix for blobs
1 parent 12acc5b commit a5df325

4 files changed

Lines changed: 660 additions & 20 deletions

File tree

src/cypress/e2e/limeSurvey.cy.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/// <reference types="cypress" />
2+
3+
import { ELEMENT_FILTER } from '../../constants'
4+
import { isBlobIframe } from '../../elements'
5+
import { searchReplace } from '../../searchreplace'
6+
7+
const SEARCHTERM = 'Phone Type:'
8+
const REPLACETERM = 'The replacement'
9+
const BASEURL = 'http://localhost:9000'
10+
describe('Search Replace LimeSurvey', { baseUrl: BASEURL, responseTimeout: 120e3 }, () => {
11+
beforeEach(() => {
12+
cy.visit('http://localhost:9000/tests/limeSurvey/test.html')
13+
})
14+
15+
it('counts the correct number of occurrences', () => {
16+
cy.window().then((window) => {
17+
console.log(window.location.host)
18+
const iframes = Array.from(
19+
<NodeListOf<HTMLIFrameElement>>window.document.querySelectorAll('iframe')
20+
).filter((iframe) => !isBlobIframe(iframe))
21+
cy.wrap(
22+
searchReplace(
23+
'count',
24+
window,
25+
SEARCHTERM,
26+
REPLACETERM,
27+
false,
28+
false,
29+
true,
30+
false,
31+
false,
32+
false,
33+
true,
34+
false,
35+
iframes,
36+
ELEMENT_FILTER
37+
).then((result) => {
38+
console.log(`result`, result)
39+
expect(result.searchReplaceResult.count.original).to.equal(2)
40+
})
41+
).then(() => {
42+
console.log('done')
43+
})
44+
})
45+
})
46+
it('replaces the search term and then replaces it again', () => {
47+
cy.window().then((window) => {
48+
console.log(window.location.host)
49+
const iframes = Array.from(
50+
<NodeListOf<HTMLIFrameElement>>window.document.querySelectorAll('iframe')
51+
).filter((iframe) => !isBlobIframe(iframe))
52+
cy.wrap(
53+
searchReplace(
54+
'searchReplace',
55+
window,
56+
SEARCHTERM,
57+
REPLACETERM,
58+
false,
59+
false,
60+
true,
61+
false,
62+
false,
63+
false,
64+
true,
65+
false,
66+
iframes,
67+
ELEMENT_FILTER
68+
).then((result1) => {
69+
expect(result1.searchReplaceResult.count.replaced).to.equal(2)
70+
searchReplace(
71+
'searchReplace',
72+
window,
73+
REPLACETERM,
74+
SEARCHTERM,
75+
false,
76+
false,
77+
true,
78+
false,
79+
false,
80+
false,
81+
true,
82+
false,
83+
iframes,
84+
ELEMENT_FILTER
85+
).then((result2) => {
86+
expect(result2.searchReplaceResult.count.replaced).to.equal(2)
87+
})
88+
})
89+
).then(() => {
90+
console.log('done')
91+
})
92+
})
93+
})
94+
})

src/elements.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ export function containsPartialClass(element: Element, partialClass: string) {
4040
}
4141

4242
export function getLocalIframes(window: Window, document: Document): HTMLIFrameElement[] {
43+
console.log('iframes', document.querySelectorAll('iframe'))
4344
return Array.from(<NodeListOf<HTMLIFrameElement>>document.querySelectorAll('iframe')).filter((iframe) => {
44-
return iframe.src === '' || iframe.src === 'about:blank' || iframe.src === window.location.href
45+
return iframe.src === '' || iframe.src === 'about:blank' || iframe.srcdoc || iframe.src === window.location.href
4546
})
4647
}
4748

@@ -72,6 +73,8 @@ export const waitForIframeLoad = async (iframe: HTMLIFrameElement): Promise<HTML
7273
} else {
7374
resolve(iframe)
7475
}
76+
} else if (iframe.srcdoc) {
77+
resolve(iframe)
7578
} else {
7679
resolve(null)
7780
}
@@ -200,6 +203,10 @@ export function elementIsVisible(element: HTMLElement, ancestorCheck = true, clo
200203

201204
export function getInitialIframeElement(iframe: HTMLIFrameElement): HTMLElement | null {
202205
let element: HTMLElement | null = null
206+
console.log('iframe', iframe)
207+
if (iframe.srcdoc) {
208+
return iframe
209+
}
203210
if (iframe.contentDocument) {
204211
element = iframe.contentDocument?.body || iframe.contentDocument?.querySelector('div')
205212
}

src/searchreplace.ts

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,15 @@ function replaceInInputShadow(
103103

104104
function getValue(node: Element | Node, config: SearchReplaceConfig): string {
105105
const nodeElement = getElementFromNode(node)
106-
console.log('nodeElement', nodeElement)
107106
// if it's an input or a textarea, take the value
108107
if (nodeElement && (nodeElement.nodeName === 'INPUT' || nodeElement.nodeName === 'TEXTAREA')) {
109108
return nodeElement['value']
110109
}
110+
// if it's an iframe with srcdoc, take the srcdoc
111+
if (nodeElement && nodeElement.nodeName === 'IFRAME' && nodeElement.hasAttribute('srcdoc')) {
112+
return nodeElement['srcdoc']
113+
}
114+
111115
// if it's a contenteditable div, take the outerHTML if we're replacing HTML, otherwise take the innerHTML
112116
if (nodeElement && nodeElement.nodeName.match(/DIV|BODY/g) && nodeElement.hasAttribute('contenteditable')) {
113117
console.log('returning outer / innerHTML')
@@ -189,12 +193,10 @@ function containsAncestor(element: Element, results: Map<Element, SearchReplaceR
189193
function countOccurrences(el: HTMLElement, config: SearchReplaceConfig): number {
190194
let target = getValue(el, config)
191195

192-
if (config.hiddenContent && config.searchTarget === 'innerText') {
196+
if (config.hiddenContent && config.searchTarget === 'innerText' && el.tagName !== 'IFRAME') {
193197
// textContent contains text of visible and hidden elements
194-
console.log('using textContent')
195198
target = (el as HTMLElement).textContent || ''
196199
}
197-
console.log('counting in', target)
198200
const matches = target.match(config.globalSearchPattern) || []
199201
return matches.length
200202
}
@@ -233,6 +235,7 @@ function replaceInNodeOrElement(
233235
replaced = true
234236
}
235237
if (replaced) {
238+
console.log('adding', config.replaceAll ? occurrences.length : 1, 'to replaced count')
236239
replacementCount = config.replaceAll ? occurrences.length : 1 // adds one to replaced count if a replacement was made, adds occurrences if a global replace is made
237240
}
238241
nodeElement?.dispatchEvent(new Event('input', { bubbles: true }))
@@ -392,7 +395,7 @@ function replaceInner(
392395
}
393396

394397
const occurrences = countOccurrences(element, config)
395-
console.log('occurrences', occurrences, element)
398+
console.log('occurrences', occurrences)
396399
elementsChecked = updateResults(elementsChecked, element, false, occurrences, 0)
397400

398401
const ancestorChecked = containsAncestor(element, elementsChecked)
@@ -527,6 +530,24 @@ function equivalentInIgnoredElements(ignoredElements: Set<Element>, element: Ele
527530
return false
528531
}
529532

533+
function replaceInSrcDocIframe(
534+
config: SearchReplaceConfig,
535+
iframe: HTMLIFrameElement,
536+
searchReplaceResult: SearchReplaceResult,
537+
elementsChecked: Map<Element, SearchReplaceResult>
538+
): ReplaceFunctionReturnType {
539+
const occurrences = countOccurrences(iframe, config)
540+
console.log("occurrences in iframe's srcdoc", occurrences)
541+
elementsChecked = updateResults(elementsChecked, iframe, false, occurrences, 0)
542+
searchReplaceResult.count.original = searchReplaceResult.count.original + occurrences
543+
if (config.replace && occurrences) {
544+
iframe.srcdoc = iframe.srcdoc.replace(config.searchPattern, config.replaceTerm)
545+
console.log('adding', config.replaceAll ? occurrences : 1, 'to replaced count')
546+
searchReplaceResult.count.replaced += config.replaceAll ? occurrences : 1
547+
}
548+
return { searchReplaceResult, elementsChecked }
549+
}
550+
530551
function replaceInHTML(
531552
config: SearchReplaceConfig,
532553
document: Document,
@@ -535,7 +556,6 @@ function replaceInHTML(
535556
elementsChecked: Map<Element, SearchReplaceResult>
536557
): ReplaceFunctionReturnType {
537558
for (const [originalIndex, originalElement] of originalElements.entries()) {
538-
console.log('replacing in element', originalElement)
539559
let clonedElement = originalElement.cloneNode(true) as HTMLElement
540560

541561
const { clonedElementRemoved, removedSet } = copyElementAndRemoveSelectedElements(
@@ -544,9 +564,11 @@ function replaceInHTML(
544564
// - match the element filter, but are not blob iframes, nor are WYSIWYG iframes.
545565
// Removes SCRIPT, STYLE, IFRAME, etc.
546566
// - match the input filter, as these are handled later
567+
// - are already in the elementsChecked map
547568
(el: HTMLElement) =>
548569
(!!el.nodeName.match(config.elementFilter) && !isBlobIframe(el) && !isWYSIWYGEditorIframe(el)) ||
549-
isInputElement(el),
570+
isInputElement(el) ||
571+
elementsChecked.has(el),
550572
false
551573
)
552574
clonedElement = clonedElementRemoved as HTMLElement
@@ -671,7 +693,7 @@ export async function searchReplace(
671693
shadowRoots,
672694
}
673695
// we check other places if text was not replaced in a text editor
674-
let result: ReplaceFunctionReturnType
696+
let result: ReplaceFunctionReturnType = { searchReplaceResult, elementsChecked }
675697
if (inputFieldsOnly) {
676698
result = replaceInputFields(config, document, searchReplaceResult, elementsChecked)
677699
if (config.replaceNext && result.searchReplaceResult.replaced) {
@@ -680,17 +702,27 @@ export async function searchReplace(
680702
} else {
681703
const startingElement = document.body || document.querySelector('div')
682704

683-
const searchableIframes = (await getSearchableIframes(window, document))
684-
.map(getInitialIframeElement)
685-
.filter(notEmpty)
686-
console.log('searchable iframes', searchableIframes)
687-
result = replaceInHTML(
688-
config,
689-
document,
690-
[startingElement, ...searchableIframes],
691-
searchReplaceResult,
692-
elementsChecked
693-
)
705+
const searchableIframesInitial = await getSearchableIframes(window, document)
706+
const srcDocIframes = searchableIframesInitial.filter((iframe) => iframe.hasAttribute('srcdoc'))
707+
srcDocIframes.map((iframe) => {
708+
const srcDocResult = replaceInSrcDocIframe(
709+
config,
710+
iframe,
711+
result.searchReplaceResult,
712+
result.elementsChecked
713+
)
714+
result.searchReplaceResult = srcDocResult.searchReplaceResult
715+
result.elementsChecked = srcDocResult.elementsChecked
716+
})
717+
console.log(JSON.stringify(result.searchReplaceResult))
718+
console.log('searchableIframesInitial', searchableIframesInitial)
719+
const searchableIframes = searchableIframesInitial.filter((iframe: HTMLIFrameElement) => {
720+
return iframe.srcdoc === '' || iframe.srcdoc === undefined
721+
})
722+
const searchable = searchableIframes.map(getInitialIframeElement).filter(notEmpty)
723+
console.log('searchableIframes', searchableIframes)
724+
console.log('searchable', searchable)
725+
result = replaceInHTML(config, document, [startingElement, ...searchable], searchReplaceResult, elementsChecked)
694726
}
695727

696728
return result

tests/limeSurvey/test.html

Lines changed: 507 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)