LCOV - code coverage report
Current view: top level - pkg/shell - frames.tsx Coverage Total Hit
Test: cockpit Lines: 100.0 % 84 84
Test Date: 2026-07-03 07:31:16

            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 : };
        

Generated by: LCOV version 2.0-1