fix(iframe-loader): add fallback for iframe loading

Some browsers (mostly cypress's chrome) doesn't execute
the onLoad callback automatically on the iframe.
The fallback is a timer that triggers this after a timeout of 500ms.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-09-28 18:11:59 +02:00
parent 62abd9cbe0
commit 5a1edea00a
2 changed files with 64 additions and 2 deletions

View file

@ -7,7 +7,9 @@ import { ORIGIN, useBaseUrl } from '../../../../hooks/common/use-base-url'
import { Logger } from '../../../../utils/logger' import { Logger } from '../../../../utils/logger'
import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider' import { useEditorToRendererCommunicator } from '../../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
import type { RefObject } from 'react' import type { RefObject } from 'react'
import { useCallback, useMemo, useRef } from 'react' import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useTimeoutFn } from '../../../../hooks/common/use-timeout-fn'
import { isTestMode } from '../../../../utils/test-modes'
const log = new Logger('IframeLoader') const log = new Logger('IframeLoader')
@ -31,7 +33,9 @@ export const useForceRenderPageUrlOnIframeLoadCallback = (
}, [iframeCommunicator, rendererBaseUrl]) }, [iframeCommunicator, rendererBaseUrl])
const redirectionInProgress = useRef<boolean>(false) const redirectionInProgress = useRef<boolean>(false)
return useCallback(() => { const loadedAtLeastOnce = useRef(false)
const onIframeLoad = useCallback(() => {
const frame = iFrameReference.current const frame = iFrameReference.current
if (!frame) { if (!frame) {
@ -45,9 +49,31 @@ export const useForceRenderPageUrlOnIframeLoadCallback = (
} else { } else {
const oldUrl = frame.src === '' ? '(none)' : frame.src const oldUrl = frame.src === '' ? '(none)' : frame.src
log.warn(`Navigated away from unknown URL. Was ${oldUrl}. Forcing back to ${forcedUrl}`) log.warn(`Navigated away from unknown URL. Was ${oldUrl}. Forcing back to ${forcedUrl}`)
loadedAtLeastOnce.current = true
onNavigateAway?.() onNavigateAway?.()
redirectionInProgress.current = true redirectionInProgress.current = true
frame.src = forcedUrl frame.src = forcedUrl
} }
}, [iFrameReference, onNavigateAway, forcedUrl]) }, [iFrameReference, onNavigateAway, forcedUrl])
const [startForceTimer, stopForceTimer] = useTimeoutFn(
500,
useCallback(() => {
if (loadedAtLeastOnce.current) {
return
}
log.debug('Forced load of iframe')
onIframeLoad()
}, [onIframeLoad])
)
useEffect(() => {
if (!isTestMode) {
return
}
startForceTimer()
return () => stopForceTimer()
}, [startForceTimer, stopForceTimer])
return onIframeLoad
} }

View file

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useCallback, useRef } from 'react'
/**
* Creates a timer with the given timeout and callback.
* The timer is not started automatically.
*
* @param timeout The timeout in milliseconds
* @param callback The callback to execute when the time is up
* @return [startTimer, stopTimer] Functions to start and stop the timeout
*/
export const useTimeoutFn = (timeout: number, callback: () => void) => {
const timerRef = useRef<NodeJS.Timeout | null>(null)
const stopTimer = useCallback(() => {
if (timerRef.current === null) {
return
}
clearTimeout(timerRef.current)
timerRef.current = null
}, [])
const startTimer = useCallback(() => {
if (timerRef.current !== null) {
return
}
timerRef.current = setTimeout(callback, timeout)
}, [callback, timeout])
return [startTimer, stopTimer]
}