From a5df3253372a0aea4e8e552b04f35834d82847aa Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 31 Jan 2024 20:54:23 +0100 Subject: [PATCH] Fix for srcdoc iframes (#113) * Fix for srcdoc * Lime survey test html * Fix for blobs --- src/cypress/e2e/limeSurvey.cy.ts | 94 ++++++ src/elements.ts | 9 +- src/searchreplace.ts | 70 +++-- tests/limeSurvey/test.html | 507 +++++++++++++++++++++++++++++++ 4 files changed, 660 insertions(+), 20 deletions(-) create mode 100644 src/cypress/e2e/limeSurvey.cy.ts create mode 100644 tests/limeSurvey/test.html diff --git a/src/cypress/e2e/limeSurvey.cy.ts b/src/cypress/e2e/limeSurvey.cy.ts new file mode 100644 index 0000000..e604457 --- /dev/null +++ b/src/cypress/e2e/limeSurvey.cy.ts @@ -0,0 +1,94 @@ +/// + +import { ELEMENT_FILTER } from '../../constants' +import { isBlobIframe } from '../../elements' +import { searchReplace } from '../../searchreplace' + +const SEARCHTERM = 'Phone Type:' +const REPLACETERM = 'The replacement' +const BASEURL = 'http://localhost:9000' +describe('Search Replace LimeSurvey', { baseUrl: BASEURL, responseTimeout: 120e3 }, () => { + beforeEach(() => { + cy.visit('http://localhost:9000/tests/limeSurvey/test.html') + }) + + it('counts the correct number of occurrences', () => { + cy.window().then((window) => { + console.log(window.location.host) + const iframes = Array.from( + >window.document.querySelectorAll('iframe') + ).filter((iframe) => !isBlobIframe(iframe)) + cy.wrap( + searchReplace( + 'count', + window, + SEARCHTERM, + REPLACETERM, + false, + false, + true, + false, + false, + false, + true, + false, + iframes, + ELEMENT_FILTER + ).then((result) => { + console.log(`result`, result) + expect(result.searchReplaceResult.count.original).to.equal(2) + }) + ).then(() => { + console.log('done') + }) + }) + }) + it('replaces the search term and then replaces it again', () => { + cy.window().then((window) => { + console.log(window.location.host) + const iframes = Array.from( + >window.document.querySelectorAll('iframe') + ).filter((iframe) => !isBlobIframe(iframe)) + cy.wrap( + searchReplace( + 'searchReplace', + window, + SEARCHTERM, + REPLACETERM, + false, + false, + true, + false, + false, + false, + true, + false, + iframes, + ELEMENT_FILTER + ).then((result1) => { + expect(result1.searchReplaceResult.count.replaced).to.equal(2) + searchReplace( + 'searchReplace', + window, + REPLACETERM, + SEARCHTERM, + false, + false, + true, + false, + false, + false, + true, + false, + iframes, + ELEMENT_FILTER + ).then((result2) => { + expect(result2.searchReplaceResult.count.replaced).to.equal(2) + }) + }) + ).then(() => { + console.log('done') + }) + }) + }) +}) diff --git a/src/elements.ts b/src/elements.ts index e621f65..b948066 100644 --- a/src/elements.ts +++ b/src/elements.ts @@ -40,8 +40,9 @@ export function containsPartialClass(element: Element, partialClass: string) { } export function getLocalIframes(window: Window, document: Document): HTMLIFrameElement[] { + console.log('iframes', document.querySelectorAll('iframe')) return Array.from(>document.querySelectorAll('iframe')).filter((iframe) => { - return iframe.src === '' || iframe.src === 'about:blank' || iframe.src === window.location.href + return iframe.src === '' || iframe.src === 'about:blank' || iframe.srcdoc || iframe.src === window.location.href }) } @@ -72,6 +73,8 @@ export const waitForIframeLoad = async (iframe: HTMLIFrameElement): Promise, element: Ele return false } +function replaceInSrcDocIframe( + config: SearchReplaceConfig, + iframe: HTMLIFrameElement, + searchReplaceResult: SearchReplaceResult, + elementsChecked: Map +): ReplaceFunctionReturnType { + const occurrences = countOccurrences(iframe, config) + console.log("occurrences in iframe's srcdoc", occurrences) + elementsChecked = updateResults(elementsChecked, iframe, false, occurrences, 0) + searchReplaceResult.count.original = searchReplaceResult.count.original + occurrences + if (config.replace && occurrences) { + iframe.srcdoc = iframe.srcdoc.replace(config.searchPattern, config.replaceTerm) + console.log('adding', config.replaceAll ? occurrences : 1, 'to replaced count') + searchReplaceResult.count.replaced += config.replaceAll ? occurrences : 1 + } + return { searchReplaceResult, elementsChecked } +} + function replaceInHTML( config: SearchReplaceConfig, document: Document, @@ -535,7 +556,6 @@ function replaceInHTML( elementsChecked: Map ): ReplaceFunctionReturnType { for (const [originalIndex, originalElement] of originalElements.entries()) { - console.log('replacing in element', originalElement) let clonedElement = originalElement.cloneNode(true) as HTMLElement const { clonedElementRemoved, removedSet } = copyElementAndRemoveSelectedElements( @@ -544,9 +564,11 @@ function replaceInHTML( // - match the element filter, but are not blob iframes, nor are WYSIWYG iframes. // Removes SCRIPT, STYLE, IFRAME, etc. // - match the input filter, as these are handled later + // - are already in the elementsChecked map (el: HTMLElement) => (!!el.nodeName.match(config.elementFilter) && !isBlobIframe(el) && !isWYSIWYGEditorIframe(el)) || - isInputElement(el), + isInputElement(el) || + elementsChecked.has(el), false ) clonedElement = clonedElementRemoved as HTMLElement @@ -671,7 +693,7 @@ export async function searchReplace( shadowRoots, } // we check other places if text was not replaced in a text editor - let result: ReplaceFunctionReturnType + let result: ReplaceFunctionReturnType = { searchReplaceResult, elementsChecked } if (inputFieldsOnly) { result = replaceInputFields(config, document, searchReplaceResult, elementsChecked) if (config.replaceNext && result.searchReplaceResult.replaced) { @@ -680,17 +702,27 @@ export async function searchReplace( } else { const startingElement = document.body || document.querySelector('div') - const searchableIframes = (await getSearchableIframes(window, document)) - .map(getInitialIframeElement) - .filter(notEmpty) - console.log('searchable iframes', searchableIframes) - result = replaceInHTML( - config, - document, - [startingElement, ...searchableIframes], - searchReplaceResult, - elementsChecked - ) + const searchableIframesInitial = await getSearchableIframes(window, document) + const srcDocIframes = searchableIframesInitial.filter((iframe) => iframe.hasAttribute('srcdoc')) + srcDocIframes.map((iframe) => { + const srcDocResult = replaceInSrcDocIframe( + config, + iframe, + result.searchReplaceResult, + result.elementsChecked + ) + result.searchReplaceResult = srcDocResult.searchReplaceResult + result.elementsChecked = srcDocResult.elementsChecked + }) + console.log(JSON.stringify(result.searchReplaceResult)) + console.log('searchableIframesInitial', searchableIframesInitial) + const searchableIframes = searchableIframesInitial.filter((iframe: HTMLIFrameElement) => { + return iframe.srcdoc === '' || iframe.srcdoc === undefined + }) + const searchable = searchableIframes.map(getInitialIframeElement).filter(notEmpty) + console.log('searchableIframes', searchableIframes) + console.log('searchable', searchable) + result = replaceInHTML(config, document, [startingElement, ...searchable], searchReplaceResult, elementsChecked) } return result diff --git a/tests/limeSurvey/test.html b/tests/limeSurvey/test.html new file mode 100644 index 0000000..d52198e --- /dev/null +++ b/tests/limeSurvey/test.html @@ -0,0 +1,507 @@ + + + + + + + + + + +Training + + +
+ +
+ + + +
+
+
+
+
+
+ + + + + + + +
+ + +

+ Edit question: pPhoneType (ID:1698525)

+
+ +
+ + +
+ + +
+ +
+ +
+ +
+ Required +
+
+ + + + +
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+ + + +
+ + +
+
+
+
+ + +
+ + +
+
+
+ +
+ +
+
+
+ +
+
On Off
+
+
+ +
+ +
On Off
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+
+ +
+
+
+ + +

+ + +

+
+
+
+ + + + + + \ No newline at end of file