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-06-25 09:20:42

            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          339 : import React, { useRef, useEffect } from 'react';
      24              : 
      25              : import { ShellState, ShellFrame } from "./state";
      26              : import { get_session_controller } from "./session";
      27              : 
      28          336 : export const Frames = ({ state, hidden }: { state: ShellState, hidden: boolean }) => {
      29          336 :     const content_ref = useRef<HTMLDivElement | null>(null);
      30          336 :     const { frames, current_frame } = state;
      31              : 
      32          336 :     useEffect(() => {
      33          336 :         if (!content_ref.current)
      34          336 :             return;
      35              : 
      36          336 :         const content = content_ref.current;
      37              : 
      38            6 :         function iframe_remove(elt: HTMLIFrameElement) {
      39            6 :             elt.remove();
      40            6 :         }
      41              : 
      42          336 :         function iframe_new(name: string) {
      43          336 :             const elt = document.createElement("iframe");
      44          336 :             elt.setAttribute("name", name);
      45          336 :             elt.style.display = "none";
      46          336 :             content.appendChild(elt);
      47          336 :             return elt;
      48          336 :         }
      49              : 
      50          336 :         function setup_iframe(frame: ShellFrame, iframe: HTMLIFrameElement) {
      51          336 :             if (!iframe.contentWindow)
      52          336 :                 return;
      53              : 
      54          336 :             get_session_controller().add_window(iframe.contentWindow);
      55           65 :             iframe.contentWindow.addEventListener("unload", () => teardown_iframe(frame), { once: true });
      56              : 
      57          336 :             if (iframe.contentDocument && iframe.contentDocument.documentElement) {
      58          336 :                 iframe.contentDocument.documentElement.lang = state.config.language;
      59          336 :                 if (state.config.language_direction)
      60          336 :                     iframe.contentDocument.documentElement.dir = state.config.language_direction;
      61          336 :             }
      62              : 
      63          336 :             if (!frame.ready) {
      64          336 :                 frame.ready = true;
      65          336 :                 state.update();
      66          336 :             }
      67          336 :         }
      68              : 
      69           65 :         function teardown_iframe(frame: ShellFrame) {
      70           65 :             if (frame.ready) {
      71           65 :                 frame.ready = false;
      72           65 :                 state.update();
      73           65 :             }
      74           65 :         }
      75              : 
      76          336 :         const iframes_by_name: Record<string, HTMLIFrameElement> = {};
      77              : 
      78          336 :         for (let i = 0; i < content.children.length; i++) {
      79          336 :             const c = content.children[i];
      80          336 :             const name = c.getAttribute('name');
      81          336 :             if (c.nodeName == "IFRAME" && name) {
      82          336 :                 iframes_by_name[name] = c as HTMLIFrameElement;
      83          336 :             }
      84          336 :         }
      85              : 
      86              :         // Remove obsolete iframes
      87          336 :         for (const name in iframes_by_name) {
      88          336 :             if (!frames[name] || frames[name].url == null)
      89           67 :                 iframe_remove(iframes_by_name[name]);
      90          336 :         }
      91              : 
      92              :         // Add new and update existing iframes
      93          336 :         for (const name in frames) {
      94          336 :             const frame = frames[name];
      95          336 :             if (!frame.url)
      96          336 :                 continue;
      97              : 
      98          336 :             let iframe = iframes_by_name[name];
      99              : 
     100          336 :             if (!iframe) {
     101          336 :                 iframe = iframe_new(name);
     102          336 :                 iframe.setAttribute("class", "container-frame");
     103          336 :                 iframe.setAttribute("data-host", frame.host);
     104          336 :                 iframe.addEventListener("load", () => setup_iframe(frame, iframe));
     105          336 :             }
     106              : 
     107          336 :             if (iframe.getAttribute("title") != frame.title)
     108          336 :                 iframe.setAttribute("title", frame.title);
     109              : 
     110          336 :             if (frame.loaded && iframe.getAttribute("data-loaded") == null)
     111          336 :                 iframe.setAttribute("data-loaded", "1");
     112          336 :             else if (!frame.loaded && iframe.getAttribute("data-loaded"))
     113           62 :                 iframe.removeAttribute("data-loaded");
     114              : 
     115          336 :             const src = frame.url + "#" + frame.hash;
     116              : 
     117          336 :             if (iframe.getAttribute('src') != src) {
     118          336 :                 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          336 :                     iframe.contentWindow.location.replace(src);
     128          336 :                 }
     129          336 :                 iframe.setAttribute('src', src);
     130          336 :             }
     131              : 
     132          335 :             iframe.style.display = (!hidden && frame == current_frame && frame.ready) ? "block" : "none";
     133          336 :         }
     134          336 :     });
     135              : 
     136          336 :     return <div ref={content_ref}
     137          336 :                 id="content"
     138          336 :                 className="area-ct-content"
     139          336 :                 role="main"
     140          336 :                 tabIndex={-1} />;
     141          336 : };
        

Generated by: LCOV version 2.0-1