diff --git a/lib/patch-dom-translate.js b/lib/patch-dom-translate.js new file mode 100644 index 000000000..8a4acf709 --- /dev/null +++ b/lib/patch-dom-translate.js @@ -0,0 +1,61 @@ +/* global Node */ + +let translateActive = false + +function detectDOMTranslate () { + const html = document.documentElement + if (!html) return false + + // classes used by DOM translators + if (html.classList.contains('translated-ltr')) return true + if (html.classList.contains('translated-rtl')) return true + if (html.classList.contains('translated')) return true + + return false +} + +/** + * workaround for DOM translators, facebook/react#11538 + * browser translation engines wrap text nodes in tags, + * which desyncs React's reconciler from the real DOM and throws + * `NotFoundError` on `removeChild` and `insertBefore` + * + * We only suppress these errors while a DOM translator is active + */ +export function patchDOMTranslations () { + if (typeof Node === 'undefined' || typeof window === 'undefined') return + if (Node.prototype.__patchedDOMTranslations) return + + Node.prototype.__patchedDOMTranslations = true + + const originalRemoveChild = Node.prototype.removeChild + const originalInsertBefore = Node.prototype.insertBefore + + Node.prototype.removeChild = function (child) { + if (child && child.parentNode !== this) { + if (translateActive) { + return child + } + } + return originalRemoveChild.call(this, child) + } + + Node.prototype.insertBefore = function (newNode, referenceNode) { + if (referenceNode && referenceNode.parentNode !== this) { + if (translateActive) { + // append so the node still ends up under the expected parent, React will correct on the next render + return this.appendChild(newNode) + } + } + return originalInsertBefore.call(this, newNode, referenceNode) + } + + const update = () => { translateActive = detectDOMTranslate() } + update() + + const observer = new window.MutationObserver(update) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class', 'lang'] + }) +} diff --git a/pages/_app.js b/pages/_app.js index 2e9af4eab..d73c56aa2 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -25,9 +25,12 @@ import { DomainProvider } from '@/components/territory-domains' import { WalletsProvider } from '@/wallets/client/hooks' import FaviconProvider from '@/components/favicon' import { CookiesProvider } from '@/components/use-cookie' +import { patchDOMTranslations } from '@/lib/patch-dom-translate' const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false }) +patchDOMTranslations() + NProgress.configure({ showSpinner: false })