* Copyright (C) 2024 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
pages.
We can't let React itself manipulate the iframe DOM elements,
unfortunately, for these reasons:
- We need to be super careful when setting the "src" attribute of
an iframe element. Otherwise we get spurious browsing history
entries that cause the Back button of browsers to behave
erratically.
- We need to adjust the window and document inside the iframe a bit.
Thus, we use a giant useEffect hook to reimplement the incremental
DOM updates that React would do for us.
*/
import React, { useRef, useEffect } from 'react';
import { ShellState, ShellFrame } from "./state";
import { get_session_controller } from "./session";
export const Frames = ({ state, hidden }: { state: ShellState, hidden: boolean }) => {
const content_ref = useRef<HTMLDivElement | null>(null);
const { frames, current_frame } = state;
useEffect(() => {
if (!content_ref.current)
return;
const content = content_ref.current;
function iframe_remove(elt: HTMLIFrameElement) {
elt.remove();
}
function iframe_new(name: string) {
const elt = document.createElement("iframe");
elt.setAttribute("name", name);
elt.style.display = "none";
content.appendChild(elt);
return elt;
}
function setup_iframe(frame: ShellFrame, iframe: HTMLIFrameElement) {
if (!iframe.contentWindow)
return;
get_session_controller().add_window(iframe.contentWindow);
iframe.contentWindow.addEventListener("unload", () => teardown_iframe(frame), { once: true });
if (iframe.contentDocument && iframe.contentDocument.documentElement) {
iframe.contentDocument.documentElement.lang = state.config.language;
if (state.config.language_direction)
iframe.contentDocument.documentElement.dir = state.config.language_direction;
}
if (!frame.ready) {
frame.ready = true;
state.update();
}
}
function teardown_iframe(frame: ShellFrame) {
if (frame.ready) {
frame.ready = false;
state.update();
}
}
const iframes_by_name: Record<string, HTMLIFrameElement> = {};
for (let i = 0; i < content.children.length; i++) {
const c = content.children[i];
const name = c.getAttribute('name');
if (c.nodeName === "IFRAME" && name) {
iframes_by_name[name] = c as HTMLIFrameElement;
}
}
for (const name in iframes_by_name) {
if (!frames[name] || frames[name].url == null)
iframe_remove(iframes_by_name[name]);
}
for (const name in frames) {
const frame = frames[name];
if (!frame.url)
continue;
let iframe = iframes_by_name[name];
if (!iframe) {
iframe = iframe_new(name);
iframe.setAttribute("class", "container-frame");
iframe.setAttribute("data-host", frame.host);
iframe.addEventListener("load", () => setup_iframe(frame, iframe));
}
if (iframe.getAttribute("title") !== frame.title)
iframe.setAttribute("title", frame.title);
if (frame.loaded && iframe.getAttribute("data-loaded") == null)
iframe.setAttribute("data-loaded", "1");
else if (!frame.loaded && iframe.getAttribute("data-loaded"))
iframe.removeAttribute("data-loaded");
const src = frame.url + "#" + frame.hash;
if (iframe.getAttribute('src') !== src) {
if (iframe.contentWindow) {
iframe.contentWindow.location.replace(src);
}
iframe.setAttribute('src', src);
}
iframe.style.display = (!hidden && frame === current_frame && frame.ready) ? "block" : "none";
}
});
return <div ref={content_ref}
id="content"
className="area-ct-content"
role="main"
tabIndex={-1} />;
};