feat(editor): F4.6d the web browser and web server on a computer's desktop

The oracle BrowserWindowRun uses Java's browser and web server windows as a
person does; the room keeps a browser window per computer and sends a
program window's view, the start names the person's language. The editor
shows the page in a sandboxed frame, links and forms go to the room, the web
server has its button, virtual hosts and lines. Better than Java: relative
images and links count from the page's folder, a mark stays in the page, the
error page from Java's template, the virtual hosts' file with complete rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 16:09:05 +00:00
co-authored by Claude Opus 5.5
parent f0e7c73ec7
commit aa54e5400e
31 changed files with 2773 additions and 55 deletions
+338
View File
@@ -0,0 +1,338 @@
// Uses FILIUS' own web browser and web server windows
// (GUIApplicationWebBrowserWindow, GUIApplicationWebServerWindow on their
// GUIDesktopPanels) on web-lan's network as a person does: an address typed
// into the browser's address line and Enter pressed, a link clicked (the
// event Swing's HTMLEditorKit makes of it: the href against the document's
// base), a form's button pressed (the real button Swing draws for it), and
// on the server's window the start button, the virtual hosts' check box and
// table. After each step it writes what the windows show: the browser's
// address line and title, the page its program handed the window; the
// server's button, check box, table and log, and the requests it got. The
// reference for the editor's browser and web server windows
// (packages/sim/test/browserWindow.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes BrowserWindowRun <scenario.json> <output json>
import filius.gui.anwendungssicht.GUIApplicationWebBrowserWindow;
import filius.gui.anwendungssicht.GUIApplicationWebServerWindow;
import filius.gui.anwendungssicht.GUIApplicationWindow;
import filius.gui.anwendungssicht.GUIDesktopPanel;
import filius.software.system.Betriebssystem;
import filius.software.system.Datei;
import filius.software.system.Dateisystem;
import filius.software.www.HTTPNachricht;
import filius.software.www.WebBrowser;
import filius.software.www.WebServer;
import java.awt.Component;
import java.awt.Container;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLDocument;
public class BrowserWindowRun {
static final String BROWSER = "00000000-0000-0000-0000-000000000201";
static final String SERVER = "00000000-0000-0000-0000-000000000203";
static final List<Object> steps = new ArrayList<>();
/** What the browser's program handed its window since the last step. */
static final List<String> pages = new ArrayList<>();
/** The requests the server got since the last step (its ">>" lines). */
static final List<String> requests = new ArrayList<>();
static Object field(Class<?> owner, Object object, String name) throws Exception {
Field field = owner.getDeclaredField(name);
field.setAccessible(true);
return field.get(object);
}
static String title(GUIApplicationWindow window) throws Exception {
return ((JLabel) field(GUIApplicationWindow.class, window, "titleLabel")).getText();
}
static void browserStep(GUIApplicationWebBrowserWindow window, String step) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("address", ((JTextField) field(GUIApplicationWebBrowserWindow.class, window, "urlFeld")).getText());
result.put("title", title(window));
synchronized (pages) {
result.put("pages", new ArrayList<>(pages));
pages.clear();
}
synchronized (requests) {
result.put("requests", new ArrayList<>(requests));
requests.clear();
}
steps.add(result);
}
static void serverStep(GUIApplicationWebServerWindow window, WebServer server, Dateisystem files, String step) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("title", title(window));
result.put("button", ((JButton) field(GUIApplicationWebServerWindow.class, window, "buttonStart")).getText());
result.put("virtualHosts", ((JCheckBox) field(GUIApplicationWebServerWindow.class, window, "showVHosts")).isSelected());
result.put("serverVirtualHosts", server.isUseVHost());
List<Object> rows = new ArrayList<>();
for (String[] row : server.getVHostArray()) rows.add(Arrays.asList(row[0], row[1]));
result.put("table", rows);
Datei vhosts = files.holeDatei("/www.conf/vhosts");
result.put("vhostsFile", vhosts == null ? null : vhosts.getDateiInhalt());
result.put("log", ((JTextArea) field(GUIApplicationWebServerWindow.class, window, "logArea")).getText());
steps.add(result);
}
static void type(GUIApplicationWebBrowserWindow window, String typed) throws Exception {
JTextField address = (JTextField) field(GUIApplicationWebBrowserWindow.class, window, "urlFeld");
SwingUtilities.invokeAndWait(() -> {
address.setText(typed);
KeyEvent event = new KeyEvent(address, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_ENTER, KeyEvent.CHAR_UNDEFINED);
for (KeyListener listener : address.getKeyListeners()) listener.keyPressed(event);
});
settle();
}
/** Waits until the browser's program handed its window a page and its images came (or 25 seconds passed: nothing loads). */
static void settle() throws Exception {
long until = System.currentTimeMillis() + 25000;
int known = -1;
long quiet = 0;
while (System.currentTimeMillis() < until) {
Thread.sleep(100);
int count;
boolean page;
synchronized (pages) {
count = pages.size();
page = pages.stream().anyMatch(each -> !each.equals("update"));
}
synchronized (requests) {
count += requests.size();
}
if (count != known) {
known = count;
quiet = System.currentTimeMillis();
} else if (page && System.currentTimeMillis() - quiet > 2500) {
return;
}
}
}
/** A click on a link: HTMLEditorKit.LinkController's event, the href against the document's base. */
static void follow(GUIApplicationWebBrowserWindow window, String href) throws Exception {
JEditorPane pane = (JEditorPane) field(GUIApplicationWebBrowserWindow.class, window, "anzeigeFeld");
SwingUtilities.invokeAndWait(() -> {
try {
URL base = ((HTMLDocument) pane.getDocument()).getBase();
HyperlinkEvent event = new HyperlinkEvent(pane, HyperlinkEvent.EventType.ACTIVATED, new URL(base, href), href);
for (HyperlinkListener listener : pane.getHyperlinkListeners()) listener.hyperlinkUpdate(event);
} catch (Exception error) {
throw new RuntimeException(error);
}
});
settle();
}
static void reload(GUIApplicationWebBrowserWindow window) throws Exception {
type(window, "www.filius.de/links.html");
synchronized (pages) {
pages.clear();
}
synchronized (requests) {
requests.clear();
}
}
static AbstractButton find(Container container, String text) {
for (Component component : container.getComponents()) {
if (component instanceof AbstractButton && text.equals(((AbstractButton) component).getText())) return (AbstractButton) component;
if (component instanceof Container) {
AbstractButton found = find((Container) component, text);
if (found != null) return found;
}
}
return null;
}
/** A press on the button Swing drew for a form's submit input. */
static void press(GUIApplicationWebBrowserWindow window, String label) throws Exception {
JEditorPane pane = (JEditorPane) field(GUIApplicationWebBrowserWindow.class, window, "anzeigeFeld");
SwingUtilities.invokeAndWait(() -> {
pane.revalidate();
pane.doLayout();
});
Thread.sleep(500);
AbstractButton[] button = new AbstractButton[1];
SwingUtilities.invokeAndWait(() -> button[0] = find(pane, label));
if (button[0] == null) throw new IllegalStateException("no button " + label);
SwingUtilities.invokeAndWait(() -> button[0].doClick());
settle();
}
static void file(Dateisystem files, String directory, String name, String type, String content) {
files.speicherDatei(directory, new Datei(name, type, content));
}
public static void main(String[] args) throws Exception {
try {
run(args);
} catch (Throwable error) {
error.printStackTrace();
System.exit(1);
}
System.exit(0);
}
static void run(String[] args) throws Exception {
ScenarioNetwork network = ScenarioNetwork.read(args[0]);
Betriebssystem serverSystem = (Betriebssystem) network.nodes.get(SERVER).getSystemSoftware();
Betriebssystem browserSystem = (Betriebssystem) network.nodes.get(BROWSER).getSystemSoftware();
serverSystem.installApp("filius.software.www.WebServer", false);
browserSystem.installApp("filius.software.www.WebBrowser", false);
WebServer server = (WebServer) serverSystem.holeSoftware("filius.software.www.WebServer");
WebBrowser browser = (WebBrowser) browserSystem.holeSoftware("filius.software.www.WebBrowser");
Dateisystem files = serverSystem.getDateisystem();
String image = files.holeDatei("/webserver/splashscreen-mini.png").getDateiInhalt();
file(files, "/webserver", "links.html", "html", "<html><head><title>Links &amp; mehr</title></head><body>\n"
+ "<a href=\"ziel.html\">rel</a> <a href=\"/text.txt\">abs</a> <a href=\"http://192.168.0.30/\">host</a>\n"
+ "<a href=\"ordner/drei.html\">folder</a> <a href=\"ziel.html?a=1&amp;b=2\">query</a> <a href=\"#oben\">anchor</a>\n"
+ "<form action=\"ziel.html\" method=\"get\"><input name=\"q\" value=\"a b\"><input type=\"submit\" name=\"los\" value=\"Los\"></form>\n"
+ "<form action=\"ziel.html\" method=\"post\"><input name=\"name\" value=\"Fil ius\"><input type=\"checkbox\" name=\"c\" value=\"1\" checked>"
+ "<input type=\"checkbox\" name=\"d\" value=\"1\"><select name=\"s\"><option>x</option><option selected value=\"y\">Y</option></select>"
+ "<textarea name=\"t\">&uuml;&amp;=</textarea><input type=\"submit\" name=\"ok\" value=\"Gut\"></form>\n"
+ "</body></html>");
file(files, "/webserver", "ziel.html", "html", "<html><head><title>Ziel</title></head><body>Ziel</body></html>");
file(files, "/webserver", "ohne.html", "html", "<html><body>Ohne Titel</body></html>");
file(files, "/webserver", "text.txt", "txt", "Nur Text\nzwei Zeilen");
file(files, "/webserver", "bild.png", "png", image);
files.erstelleVerzeichnis("/webserver", "ordner");
file(files, "/webserver/ordner", "drei.html", "html", "<html><head><title>Drei</title></head><body><a href=\"vier.html\">vier</a><img src=\"bild.png\"></body></html>");
file(files, "/webserver/ordner", "vier.html", "html", "<html><body>Vier im Ordner</body></html>");
file(files, "/webserver", "vier.html", "html", "<html><body>Vier oben</body></html>");
server.hinzuBeobachter((observable, data) -> {
String text = String.valueOf(data);
if (text.startsWith(">>")) synchronized (requests) { requests.add(text.substring(2)); }
});
browser.hinzuBeobachter((observable, data) -> {
synchronized (pages) {
if (data instanceof HTTPNachricht) {
HTTPNachricht page = (HTTPNachricht) data;
pages.add(page.getStatusCode() + " " + page.getContentType() + "\n" + page.getDaten());
} else {
pages.add("update");
}
}
});
network.start();
Thread.sleep(500);
JFrame frame = new JFrame();
GUIDesktopPanel serverDesktop = new GUIDesktopPanel(serverSystem);
GUIDesktopPanel browserDesktop = new GUIDesktopPanel(browserSystem);
frame.getContentPane().setLayout(new java.awt.FlowLayout());
frame.getContentPane().add(serverDesktop);
frame.getContentPane().add(browserDesktop);
GUIApplicationWebServerWindow serverWindow = (GUIApplicationWebServerWindow) serverDesktop.starteAnwendung("filius.software.www.WebServer");
GUIApplicationWebBrowserWindow window = (GUIApplicationWebBrowserWindow) browserDesktop.starteAnwendung("filius.software.www.WebBrowser");
SwingUtilities.invokeAndWait(() -> {
frame.pack();
frame.setVisible(true);
});
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:open");
browserStep(window, "open");
type(window, "192.168.0.30");
browserStep(window, "type:192.168.0.30 (server off)");
// The links page loaded anew before each link and button, without a step of its own.
SwingUtilities.invokeAndWait(() -> {
try {
((JButton) field(GUIApplicationWebServerWindow.class, serverWindow, "buttonStart")).doClick();
} catch (Exception error) {
throw new RuntimeException(error);
}
});
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:start");
for (String typed : new String[] { "192.168.0.30", "www.filius.de/ohne.html", "kein host!", "http://www.filius.de/nichts.html",
"leer.filius.de", "www.filius.de/text.txt", "www.filius.de/links.html?x=1", "www.filius.de/links.html" }) {
type(window, typed);
browserStep(window, "type:" + typed);
}
serverStep(serverWindow, server, files, "server:after pages");
for (String href : new String[] { "ziel.html", "/text.txt", "http://192.168.0.30/", "ordner/drei.html", "vier.html" }) {
follow(window, href);
browserStep(window, "follow:" + href);
reload(window);
}
for (String href : new String[] { "ziel.html?a=1&b=2", "#oben" }) {
follow(window, href);
browserStep(window, "follow:" + href);
reload(window);
}
press(window, "Los");
browserStep(window, "press:Los");
reload(window);
press(window, "Gut");
browserStep(window, "press:Gut");
// The virtual hosts: switched on, rows written as the table's editor does (changeVHostTable), a stop and a start.
JCheckBox box = (JCheckBox) field(GUIApplicationWebServerWindow.class, serverWindow, "showVHosts");
SwingUtilities.invokeAndWait(box::doClick);
Thread.sleep(300);
serverStep(serverWindow, server, files, "server:virtual hosts on");
String[][] cells = { { "0", "0", "seite.filius.de" }, { "0", "1", "vhost1" }, { "1", "0", "nurhost.filius.de" }, { "2", "1", "nurordner" },
{ "3", "0", "andere.filius.de" }, { "3", "1", "vhost2" } };
for (String[] cell : cells) server.changeVHostTable(Integer.parseInt(cell[0]), Integer.parseInt(cell[1]), cell[2]);
serverStep(serverWindow, server, files, "server:rows written");
serverSystem.beenden();
serverSystem.starten();
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:restart");
SwingUtilities.invokeAndWait(box::doClick);
Thread.sleep(300);
serverStep(serverWindow, server, files, "server:virtual hosts off");
serverSystem.beenden();
serverSystem.starten();
Thread.sleep(500);
serverStep(serverWindow, server, files, "server:restart after off");
SwingUtilities.invokeAndWait(() -> {
try {
((JButton) field(GUIApplicationWebServerWindow.class, serverWindow, "buttonStart")).doClick();
} catch (Exception error) {
throw new RuntimeException(error);
}
});
Thread.sleep(1000);
serverStep(serverWindow, server, files, "server:stop");
Map<String, Object> output = new LinkedHashMap<>();
output.put("steps", steps);
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
network.stop();
System.out.println("browser window steps: " + steps.size());
}
}