Line data Source code
1 : /*
2 : * Copyright (C) 2024 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 : /* This is the React component that renders all the iframes for the
7 : pages.
8 :
9 : We can't let React itself manipulate the iframe DOM elements,
10 : unfortunately, for these reasons:
11 :
12 : - We need to be super careful when setting the "src" attribute of
13 : an iframe element. Otherwise we get spurious browsing history
14 : entries that cause the Back button of browsers to behave
15 : erratically.
16 :
17 : - We need to adjust the window and document inside the iframe a bit.
18 :
19 : Thus, we use a giant useEffect hook to reimplement the incremental
20 : DOM updates that React would do for us.
21 : */
22 :
23 341 : import React, { useRef, useEffect } from 'react';
24 :
25 : import { ShellState, ShellFrame } from "./state";
26 : import { get_session_controller } from "./session";
27 :
28 338 : export const Frames = ({ state, hidden }: { state: ShellState, hidden: boolean }) => {
29 338 : const content_ref = useRef<HTMLDivElement | null>(null);
30 338 : const { frames, current_frame } = state;
31 :
32 338 : useEffect(() => {
33 338 : if (!content_ref.current)
34 338 : return;
35 :
36 338 : const content = content_ref.current;
37 :
38 6 : function iframe_remove(elt: HTMLIFrameElement) {
39 6 : elt.remove();
40 6 : }
41 :
42 338 : function iframe_new(name: string) {
43 338 : const elt = document.createElement("iframe");
44 338 : elt.setAttribute("name", name);
45 338 : elt.style.display = "none";
46 338 : content.appendChild(elt);
47 338 : return elt;
48 338 : }
49 :
50 338 : function setup_iframe(frame: ShellFrame, iframe: HTMLIFrameElement) {
51 338 : if (!iframe.contentWindow)
52 338 : return;
53 :
54 338 : get_session_controller().add_window(iframe.contentWindow);
55 66 : iframe.contentWindow.addEventListener("unload", () => teardown_iframe(frame), { once: true });
56 :
57 338 : if (iframe.contentDocument && iframe.contentDocument.documentElement) {
58 338 : iframe.contentDocument.documentElement.lang = state.config.language;
59 338 : if (state.config.language_direction)
60 338 : iframe.contentDocument.documentElement.dir = state.config.language_direction;
61 338 : }
62 :
63 338 : if (!frame.ready) {
64 338 : frame.ready = true;
65 338 : state.update();
66 338 : }
67 338 : }
68 :
69 66 : function teardown_iframe(frame: ShellFrame) {
70 66 : if (frame.ready) {
71 66 : frame.ready = false;
72 66 : state.update();
73 66 : }
74 66 : }
75 :
76 338 : const iframes_by_name: Record<string, HTMLIFrameElement> = {};
77 :
78 338 : for (let i = 0; i < content.children.length; i++) {
79 338 : const c = content.children[i];
80 338 : const name = c.getAttribute('name');
81 338 : if (c.nodeName == "IFRAME" && name) {
82 338 : iframes_by_name[name] = c as HTMLIFrameElement;
83 338 : }
84 338 : }
85 :
86 : // Remove obsolete iframes
87 338 : for (const name in iframes_by_name) {
88 338 : if (!frames[name] || frames[name].url == null)
89 68 : iframe_remove(iframes_by_name[name]);
90 338 : }
91 :
92 : // Add new and update existing iframes
93 338 : for (const name in frames) {
94 338 : const frame = frames[name];
95 338 : if (!frame.url)
96 338 : continue;
97 :
98 338 : let iframe = iframes_by_name[name];
99 :
100 338 : if (!iframe) {
101 338 : iframe = iframe_new(name);
102 338 : iframe.setAttribute("class", "container-frame");
103 338 : iframe.setAttribute("data-host", frame.host);
104 338 : iframe.addEventListener("load", () => setup_iframe(frame, iframe));
105 338 : }
106 :
107 338 : if (iframe.getAttribute("title") != frame.title)
108 338 : iframe.setAttribute("title", frame.title);
109 :
110 338 : if (frame.loaded && iframe.getAttribute("data-loaded") == null)
111 338 : iframe.setAttribute("data-loaded", "1");
112 338 : else if (!frame.loaded && iframe.getAttribute("data-loaded"))
113 63 : iframe.removeAttribute("data-loaded");
114 :
115 338 : const src = frame.url + "#" + frame.hash;
116 :
117 338 : if (iframe.getAttribute('src') != src) {
118 338 : if (iframe.contentWindow) {
119 : // This prevents the browser from creating a new
120 : // history entry. It would do that whenever the "src"
121 : // of a frame is changed and the window location is
122 : // not consistent with the new "src" value.
123 : //
124 : // This matters when a "jump" command changes both
125 : // the current frame and the hash of the newly
126 : // current frame.
127 338 : iframe.contentWindow.location.replace(src);
128 338 : }
129 338 : iframe.setAttribute('src', src);
130 338 : }
131 :
132 337 : iframe.style.display = (!hidden && frame == current_frame && frame.ready) ? "block" : "none";
133 338 : }
134 338 : });
135 :
136 338 : return <div ref={content_ref}
137 338 : id="content"
138 338 : className="area-ct-content"
139 338 : role="main"
140 338 : tabIndex={-1} />;
141 338 : };
|