11 Commits

Author SHA1 Message Date
UnlegitDqrk
c6b892c51d Updated to latest LuaScript 2026-02-28 19:32:44 +01:00
UnlegitDqrk
d749b9c84c Updated to new Protocol Build 2026-02-28 15:53:31 +01:00
UnlegitDqrk
ba44cd6f7d Updated to new Protocol Build 2026-02-28 15:35:57 +01:00
UnlegitDqrk
35cd895510 Implemented missing Lua features 2026-02-27 23:56:53 +01:00
UnlegitDqrk
d3662a0773 Implemented missing Lua features 2026-02-27 23:56:35 +01:00
UnlegitDqrk
ff2a76e7da Implemented missing Lua features 2026-02-27 23:56:20 +01:00
UnlegitDqrk
4c1cf09f34 Updated to latest Protocol Version 2026-02-27 21:05:07 +01:00
UnlegitDqrk
b3457ee133 Updated to latest Protocol Version 2026-02-27 20:56:16 +01:00
UnlegitDqrk
b6f7110d16 Updated to latest Protocol Version 2026-02-22 18:21:18 +01:00
UnlegitDqrk
4376fe6daa Updated to latest Protocol Version 2026-02-22 18:20:57 +01:00
UnlegitDqrk
69be55cac0 Usable Browser 2026-02-14 22:27:15 +01:00
14 changed files with 741 additions and 118 deletions

View File

@@ -1 +1,2 @@
Please read the license here: https://open-autonomous-connection.org/license.html Please read the license here: https://open-autonomous-connection.org/license.html
Download all third parties licenses here: https://open-autonomous-connection.org/assets/licenses.zip

View File

@@ -2,4 +2,17 @@
This is the Protocol for our Open Autonomous Connection project.<br /> This is the Protocol for our Open Autonomous Connection project.<br />
Feel free to join our Discord. Feel free to join our Discord.
<br /> <br />
## License Notice
This project (OAC) is licensed under
the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.org/license.html).
**Third-party components:**
<br />
Download all license here: https://open-autonomous-connection.org/assets/licenses.zip
- *UnlegitLibrary* is authored by the same copyright holder and is used here under a special agreement:
While [UnlegitLibrary](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/) is generally distributed under
the [GNU GPLv3](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master/LICENSE),
it is additionally licensed under OAPL **exclusively for the OAC project**.
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.

128
dependency-reduced-pom.xml Normal file
View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openautonomousconnection</groupId>
<artifactId>WebClient</artifactId>
<version>1.0.1-BETA.0.4</version>
<description>The default WebClient</description>
<url>https://open-autonomous-connection.org/</url>
<issueManagement>
<system>Issue Tracker</system>
<url>https://repo.open-autonomous-connection.org/open-autonomous-connection/WebClient/issues</url>
</issueManagement>
<developers>
<developer>
<name>UnlegitDqrk</name>
<url>https://unlegitdqrk.dev/</url>
<organization>Open Autonomous Connection</organization>
<organizationUrl>https://open-autonomous-connection.org/</organizationUrl>
<roles>
<role>Owner</role>
<role>Head Developer</role>
</roles>
</developer>
<developer>
<name>Maple</name>
<url>https://niumaple.carrd.co/</url>
<organization>Open Autonomous Connection</organization>
<organizationUrl>https://open-autonomous-connection.org/</organizationUrl>
<roles>
<role>Owner</role>
<role>Head Developer</role>
</roles>
</developer>
</developers>
<licenses>
<license>
<name>Open Autonomous Public License (OAPL)</name>
<url>https://open-autonomous-connection.org/license.html</url>
</license>
</licenses>
<organization>
<name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url>
</organization>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>org.openautonomousconnection.webclient.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports</arg>
<arg>java.base/sun.security.x509=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>java.base/sun.security.util=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<doclint>none</doclint>
<locale>en_US</locale>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<charset>UTF-8</charset>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<snapshots />
<id>oac</id>
<url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>25</maven.compiler.target>
<maven.compiler.source>25</maven.compiler.source>
</properties>
</project>

53
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>WebClient</artifactId> <artifactId>WebClient</artifactId>
<version>1.0.0-BETA.1.0</version> <version>1.0.1-BETA.0.4</version>
<organization> <organization>
<name>Open Autonomous Connection</name> <name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
@@ -15,8 +15,8 @@
<description>The default WebClient</description> <description>The default WebClient</description>
<properties> <properties>
<maven.compiler.source>23</maven.compiler.source> <maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target> <maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@@ -69,22 +69,22 @@
<dependency> <dependency>
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>OACSwing</artifactId> <artifactId>OACSwing</artifactId>
<version>1.0.0-BETA.1.1</version> <version>0.0.0-STABLE.1.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>LuaScript</artifactId> <artifactId>LuaScript</artifactId>
<version>1.0.0-BETA.1.1</version> <version>0.0.0-STABLE.1.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>InfoNameLib</artifactId> <artifactId>Protocol</artifactId>
<version>1.0.0-BETA.1.3</version> <version>1.0.1-BETA.0.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.38</version> <version>1.18.42</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -111,17 +111,46 @@
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version> <version>3.6.0</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <phase>package</phase>
<goals> <goals>
<goal>jar</goal> <goal>shade</goal>
</goals> </goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openautonomousconnection.webclient.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports</arg>
<arg>java.base/sun.security.x509=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>java.base/sun.security.util=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>

View File

@@ -1,93 +1,172 @@
package org.openautonomousconnection.webclient; package org.openautonomousconnection.webclient;
import dev.unlegitdqrk.unlegitlibrary.event.Listener; import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import org.openautonomousconnection.infonamelib.LibClientImpl; import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import org.openautonomousconnection.infonamelib.OacWebUrlInstaller; import lombok.Getter;
import org.openautonomousconnection.oacswing.component.OACOptionPane; import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.WebPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.navigate.WebNavigateAckPacket;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient; import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.client.ProtocolWebClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent; import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.LibClientImpl_v1_0_1_B;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebFlagInspector;
import org.openautonomousconnection.protocol.urlhandler.v1_0_1.beta.web.WebRequestContextProvider;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webclient.ui.BrowserTab;
import java.awt.*; import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Protocol client implementation for the WebClient. * WebClient Protocol implementation (v1.0.1).
*
* <p>Implements full stream assembly with strict correlation via:
* requestId + tabId + pageId + frameId.</p>
*/ */
public class ClientImpl extends ProtocolClient { public final class ClientImpl extends ProtocolWebClient {
private static final long MAX_STREAM_BYTES = 64L * 1024L * 1024L; // 64MB safety cap
private static final int MAX_CONCURRENT_STREAMS = 256;
@Getter
private final LibImpl libImpl = new LibImpl(); private final LibImpl libImpl = new LibImpl();
private final AtomicBoolean connectedInitialized = new AtomicBoolean(false);
private final AtomicBoolean serverConnectionInitialized = new AtomicBoolean(false);
private final Component dialogParent; private final Component dialogParent;
private final Runnable onServerReady; private final Runnable onServerReady;
public ClientImpl(Component dialogParent, Runnable onServerReady) { public ClientImpl(Component dialogParent, Runnable onServerReady) {
this.dialogParent = dialogParent; this.dialogParent = dialogParent;
this.onServerReady = Objects.requireNonNull(onServerReady, "onServerReady"); this.onServerReady = Objects.requireNonNull(onServerReady);
} }
@Override @Override
public boolean trustINS(String caFingerprint) { public boolean trustINS(String caFingerprint) {
Object[] options = {"Continue", "Cancel"}; Object[] options = {"Continue", "Cancel"};
int result = OACOptionPane.showOptionDialog( return OACOptionPane.showOptionDialog(
dialogParent, dialogParent,
"You never connected to this INS before!\n" + "Fingerprint: " + caFingerprint + "\nContinue?",
"Fingerprint: " + caFingerprint + "\nDo you want to connect?",
"INS Connection", "INS Connection",
OACOptionPane.YES_NO_OPTION, OACOptionPane.YES_NO_OPTION,
OACOptionPane.INFORMATION_MESSAGE, OACOptionPane.INFORMATION_MESSAGE,
null, null,
options, options,
options[0] options[0]
); ) == 0;
return result == 0;
} }
@Override @Override
public boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint) { public boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint) {
Object[] options = {"Continue", "Cancel"}; Object[] options = {"Continue", "Cancel"};
return OACOptionPane.showOptionDialog(
int result = OACOptionPane.showOptionDialog(
dialogParent, dialogParent,
"The fingerprint does not match with the saved fingerprint!\n" + "Saved: " + oldCAFingerprint + "\nNew: " + newCAFingerprint + "\nContinue?",
"Saved Fingerprint: " + oldCAFingerprint + "\n" +
"New Fingerprint: " + newCAFingerprint + "\n" +
"Do you want to connect?",
"INS Connection", "INS Connection",
OACOptionPane.YES_NO_OPTION, OACOptionPane.YES_NO_OPTION,
OACOptionPane.INFORMATION_MESSAGE, OACOptionPane.INFORMATION_MESSAGE,
null, null,
options, options,
options[0] options[0]
); ) == 0;
return result == 0;
} }
@Listener @Listener
public void onConnected(ConnectedToProtocolINSServerEvent event) { public void onConnected(ConnectedToProtocolINSServerEvent event) {
try { try {
buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl); if (serverConnectionInitialized.compareAndSet(false, true)) {
OacWebUrlInstaller.installOnce(getProtocolBridge().getProtocolValues().eventManager, this, libImpl); buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl);
if (connectedInitialized.compareAndSet(false, true)) {
onServerReady.run(); onServerReady.run();
} }
} catch (Exception e) { } catch (Exception e) {
getProtocolBridge().getLogger().exception("Failed to build Server connection", e); serverConnectionInitialized.set(false);
OACOptionPane.showMessageDialog( throw new RuntimeException(e);
dialogParent,
"Failed to to build Server connection:\n" + e.getMessage(),
"Server Connection",
OACOptionPane.ERROR_MESSAGE
);
} }
} }
private class LibImpl extends LibClientImpl { private record StreamKey(long requestId, long tabId, long pageId, long frameId) {
StreamKey(WebPacketHeader h) {
this(h.getRequestId(), h.getTabId(), h.getPageId(), h.getFrameId());
}
}
private static final class StreamState {
private final int statusCode;
private final String contentType;
private final Map<String, String> headers;
private final long declaredLength;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private int expectedSeq = 0;
private long written = 0;
private boolean ended = false;
private boolean ok = true;
StreamState(int statusCode,
String contentType,
Map<String, String> headers,
long declaredLength) {
this.statusCode = statusCode;
this.contentType = contentType == null ? "application/octet-stream" : contentType;
this.headers = headers == null ? Map.of() : Map.copyOf(headers);
this.declaredLength = declaredLength;
}
void append(int seq, byte[] data) {
if (ended) throw new IllegalStateException("Chunk after end");
if (seq != expectedSeq) throw new IllegalStateException("Out-of-order chunk");
expectedSeq++;
if (data == null || data.length == 0) return;
written += data.length;
if (written > MAX_STREAM_BYTES)
throw new IllegalStateException("Stream exceeds limit");
buffer.writeBytes(data);
}
void markEnd(boolean ok, String error) {
this.ended = true;
this.ok = ok;
}
byte[] finish() {
if (!ok) return new byte[0];
byte[] data = buffer.toByteArray();
if (declaredLength > 0 && data.length != declaredLength) {
// tolerated but can log if needed
}
return data;
}
}
public final class LibImpl extends LibClientImpl_v1_0_1_B {
private final WebRequestContextProvider provider = new WebRequestContextProvider.Default();
private final WebFlagInspector inspector = new WebFlagInspector.Default();
private final ConcurrentHashMap<StreamKey, StreamState> streams = new ConcurrentHashMap<>();
private BrowserTab currentTab;
@Override @Override
public void serverConnectionFailed(Exception exception) { public void serverConnectionFailed(Exception exception) {
getProtocolBridge().getLogger().exception("Failed to connect to server", exception); getProtocolBridge().getProtocolValues().logger.exception("Failed to connect to server", exception);
OACOptionPane.showMessageDialog( OACOptionPane.showMessageDialog(
dialogParent, dialogParent,
"Failed to connect to Server:\n" + exception.getMessage(), "Failed to connect to Server:\n" + exception.getMessage(),
@@ -95,5 +174,74 @@ public class ClientImpl extends ProtocolClient {
OACOptionPane.ERROR_MESSAGE OACOptionPane.ERROR_MESSAGE
); );
} }
public void bindTab(BrowserTab tab) {
this.currentTab = tab;
}
@Override
public boolean isStream(WebPacketHeader header) {
return inspector.isStream(header);
}
@Override
public WebRequestContext contextFor(URL url) {
return provider.contextFor(url);
}
@Override
public void streamStart(WebPacketHeader header,
int statusCode,
String contentType,
Map<String, String> headers,
long totalLength) {
if (streams.size() >= MAX_CONCURRENT_STREAMS) {
throw new IllegalStateException("Too many concurrent streams");
}
StreamKey key = new StreamKey(header);
streams.put(key, new StreamState(statusCode, contentType, headers, totalLength));
}
@Override
public void streamChunk(WebPacketHeader header, int seq, byte[] data) {
StreamState state = streams.get(new StreamKey(header));
if (state == null) {
throw new IllegalStateException("Chunk without streamStart");
}
state.append(seq, data);
}
@Override
public void streamEnd(WebPacketHeader header, boolean ok, String error) {
StreamState state = streams.get(new StreamKey(header));
if (state != null) {
state.markEnd(ok, error);
}
}
@Override
public void streamFinish(WebPacketHeader header, byte[] data) {
StreamKey key = new StreamKey(header);
StreamState state = streams.remove(key);
if (state == null) return;
byte[] content = state.finish();
if (currentTab != null) {
currentTab.handleStreamFinished(
header.getRequestId(),
header.getTabId(),
header.getPageId(),
header.getFrameId(),
state.statusCode,
state.contentType,
state.headers,
content
);
}
}
} }
} }

View File

@@ -1,6 +1,8 @@
package org.openautonomousconnection.webclient; package org.openautonomousconnection.webclient;
import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader; import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import org.openautonomousconnection.oacswing.component.design.Design; import org.openautonomousconnection.oacswing.component.design.Design;
import org.openautonomousconnection.oacswing.component.design.DesignManager; import org.openautonomousconnection.oacswing.component.design.DesignManager;
import org.openautonomousconnection.webclient.settings.AppSettings; import org.openautonomousconnection.webclient.settings.AppSettings;
@@ -8,6 +10,7 @@ import org.openautonomousconnection.webclient.settings.SettingsManager;
import org.openautonomousconnection.webclient.ui.BrowserUI; import org.openautonomousconnection.webclient.ui.BrowserUI;
import javax.swing.*; import javax.swing.*;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
@@ -23,6 +26,8 @@ public class Main {
private static AppSettings settings; private static AppSettings settings;
private static AddonLoader addonLoader; private static AddonLoader addonLoader;
private static Logger logger;
private static EventManager eventManager;
public static BrowserUI getUi() { public static BrowserUI getUi() {
return ui; return ui;
@@ -32,6 +37,14 @@ public class Main {
return settings; return settings;
} }
public static AddonLoader getAddonLoader() {
return addonLoader;
}
public static Logger getLogger() {
return logger;
}
private static void installDefaultCookieManager() { private static void installDefaultCookieManager() {
if (CookieHandler.getDefault() != null) return; if (CookieHandler.getDefault() != null) return;
@@ -40,7 +53,13 @@ public class Main {
CookieHandler.setDefault(cm); CookieHandler.setDefault(cm);
} }
public static void main(String[] args) throws IOException { static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
File logsFolder = new File("logs");
if (!logsFolder.exists()) logsFolder.mkdir();
eventManager = new EventManager();
logger = new Logger(logsFolder, false, true);
addonLoader = new AddonLoader(eventManager, logger);
settings = SettingsManager.load(); settings = SettingsManager.load();
FxBootstrap.ensureInitialized(); FxBootstrap.ensureInitialized();
@@ -50,6 +69,7 @@ public class Main {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
ui = new BrowserUI(settings); ui = new BrowserUI(settings);
ui.setSize(1200, 800); ui.setSize(1200, 800);
ui.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
ui.setLocationRelativeTo(null); ui.setLocationRelativeTo(null);
ui.setVisible(true); ui.setVisible(true);
ui.openNewTab(settings.getStartPageUrl()); ui.openNewTab(settings.getStartPageUrl());

View File

@@ -15,26 +15,26 @@ public class WebLogger {
} }
public void log(String string) { public void log(String string) {
client.getProtocolBridge().getLogger().log(host + ": " + string); client.getProtocolBridge().getProtocolValues().logger.log(host + ": " + string);
} }
public void info(String info) { public void info(String info) {
client.getProtocolBridge().getLogger().info(host + ": " + info); client.getProtocolBridge().getProtocolValues().logger.info(host + ": " + info);
} }
public void warn(String warn) { public void warn(String warn) {
client.getProtocolBridge().getLogger().warn(host + ": " + warn); client.getProtocolBridge().getProtocolValues().logger.warn(host + ": " + warn);
} }
public void error(String error) { public void error(String error) {
client.getProtocolBridge().getLogger().error(host + ": " + error); client.getProtocolBridge().getProtocolValues().logger.error(host + ": " + error);
} }
public void exception(String infoLine, Exception exception) { public void exception(String infoLine, Exception exception) {
client.getProtocolBridge().getLogger().exception(host + ": " + infoLine, exception); client.getProtocolBridge().getProtocolValues().logger.exception(host + ": " + infoLine, exception);
} }
public void debug(String debug) { public void debug(String debug) {
client.getProtocolBridge().getLogger().debug(host + ": " + debug); client.getProtocolBridge().getProtocolValues().logger.debug(host + ": " + debug);
} }
} }

View File

@@ -5,8 +5,11 @@ import javafx.scene.web.WebView;
import org.openautonomousconnection.luascript.fx.FxDomHost; import org.openautonomousconnection.luascript.fx.FxDomHost;
import org.openautonomousconnection.luascript.fx.FxThreadBridge; import org.openautonomousconnection.luascript.fx.FxThreadBridge;
import org.openautonomousconnection.luascript.hosts.UiHost; import org.openautonomousconnection.luascript.hosts.UiHost;
import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.webclient.Main;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import javax.swing.*;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -66,23 +69,17 @@ public final class UiHostImpl implements UiHost {
@Override @Override
public void alert(String message) { public void alert(String message) {
// No JS: use simple JavaFX dialog-less fallback (log-style). You can replace with real Dialogs later. OACOptionPane.showMessageDialog(Main.getUi(), message, "Alert", JOptionPane.INFORMATION_MESSAGE);
// Keeping it deterministic and non-blocking for now.
System.out.println("[ui.alert] " + (message == null ? "" : message));
} }
@Override @Override
public boolean confirm(String message) { public boolean confirm(String message) {
// No JS: deterministic default (false). Replace with JavaFX dialogs if you want UI interaction. return OACOptionPane.showConfirmDialog(Main.getUi(), message, "Alert", OACOptionPane.YES_NO_OPTION) == OACOptionPane.YES_OPTION;
System.out.println("[ui.confirm] " + (message == null ? "" : message));
return false;
} }
@Override @Override
public String prompt(String message, String defaultValue) { public String prompt(String message, String defaultValue) {
// No JS: deterministic default. return (String) OACOptionPane.showInputDialog(Main.getUi(), message, "Prompt", JOptionPane.QUESTION_MESSAGE, null, null, defaultValue);
System.out.println("[ui.prompt] " + (message == null ? "" : message));
return defaultValue;
} }
@Override @Override

View File

@@ -2,7 +2,6 @@ package org.openautonomousconnection.webclient.settings;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy; import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -187,6 +186,6 @@ public final class AppSettings {
* Resets the Lua policy back to ui default. * Resets the Lua policy back to ui default.
*/ */
public void resetLuaPolicyToUiDefault() { public void resetLuaPolicyToUiDefault() {
this.luaPolicy = new LuaExecutionPolicy(Duration.ofMillis(50L), 200_000L, 5_000); this.luaPolicy = LuaExecutionPolicy.uiDefault();
} }
} }

View File

@@ -4,13 +4,14 @@ import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine; import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView; import javafx.scene.web.WebView;
import org.luaj.vm2.Globals; import org.luaj.vm2.Globals;
import org.openautonomousconnection.luascript.fx.FxDomHost; import org.luaj.vm2.LuaError;
import org.openautonomousconnection.luascript.fx.FxEventHost; import org.openautonomousconnection.luascript.fx.*;
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.runtime.LuaRuntime; import org.openautonomousconnection.luascript.runtime.LuaRuntime;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy; import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory; import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.webclient.Main;
import org.openautonomousconnection.webclient.lua.WebLogger; import org.openautonomousconnection.webclient.lua.WebLogger;
import org.openautonomousconnection.webclient.lua.hosts.ConsoleHostImpl; import org.openautonomousconnection.webclient.lua.hosts.ConsoleHostImpl;
import org.openautonomousconnection.webclient.lua.hosts.UiHostImpl; import org.openautonomousconnection.webclient.lua.hosts.UiHostImpl;
@@ -92,20 +93,37 @@ public final class FxEngine implements AutoCloseable {
.sandbox(true) .sandbox(true)
); );
ConsoleHostImpl console = new ConsoleHostImpl(logger); FxEventHost eventHost = new FxEventHost(dom);
UiHostImpl uiHost = new UiHostImpl(engine, webView, dom); HostServices services = new HostServices.Default(
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine); new FxUiHost(engine, dom),
dom,
eventHost,
new FxWebViewResourceHost(engine),
new ConsoleHostImpl(logger),
new FxAudioHost(),
new FxImageHost(engine, dom),
new FxVideoHost(engine, dom),
new FxSchedulerHost(),
new FxSelectorHost(engine, dom),
new FxGeometryHost(engine, dom),
new FxCssHost(engine, dom),
new FxStorageHost(engine, dom),
new FxUtilHost(engine, dom),
new FxClipboardHost(),
new FxObserverHost(engine, dom)
);
LuaRuntime rt = new LuaRuntime(globals, new HostServices.Default(uiHost, dom, null, resourceHost, console), policy); LuaRuntime rt = new LuaRuntime(globals, services, policy);
eventHost.setRouter(rt.eventRouter());
FxEventHost eventHost = new FxEventHost(dom, rt.eventRouter());
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console);
rt.close();
rt = new LuaRuntime(globals, services, policy);
rt.installStdTables(true); rt.installStdTables(true);
rt.bootstrapFromDom();
try {
rt.bootstrapFromDom();
} catch (LuaError error) {
Main.getLogger().exception("Failed to start script", error);
OACOptionPane.showMessageDialog(Main.getUi(), error.getMessage(), "Script", OACOptionPane.ERROR_MESSAGE);
}
this.runtime = rt; this.runtime = rt;
} }
@@ -135,4 +153,4 @@ public final class FxEngine implements AutoCloseable {
} }
} }
} }
} }

View File

@@ -48,8 +48,8 @@ public record InsEndpoint(String host, int port) {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (!(o instanceof InsEndpoint other)) return false; if (!(o instanceof InsEndpoint(String host1, int port1))) return false;
return host.equalsIgnoreCase(other.host) && port == other.port; return host.equalsIgnoreCase(host1) && port == port1;
} }
@Override @Override

View File

@@ -87,9 +87,10 @@ public final class SettingsManager {
} }
// Lua policy // Lua policy
long timeoutMs = parseLong(p.getProperty("lua.timeoutMs"), 50L); LuaExecutionPolicy defaultPolicy = LuaExecutionPolicy.uiDefault();
long instr = parseLong(p.getProperty("lua.instructionLimit"), 200_000L); long timeoutMs = parseLong(p.getProperty("lua.timeoutMs"), defaultPolicy.timeout().toMillis());
int hook = parseInt(p.getProperty("lua.hookStep"), 5_000); long instr = parseLong(p.getProperty("lua.instructionLimit"), defaultPolicy.instructionLimit());
int hook = parseInt(p.getProperty("lua.hookStep"), defaultPolicy.hookStep());
try { try {
s.setLuaPolicy(new LuaExecutionPolicy(Duration.ofMillis(timeoutMs), instr, hook)); s.setLuaPolicy(new LuaExecutionPolicy(Duration.ofMillis(timeoutMs), instr, hook));
} catch (Exception ignored) { } catch (Exception ignored) {
@@ -187,4 +188,4 @@ public final class SettingsManager {
return def; return def;
} }
} }
} }

View File

@@ -26,7 +26,16 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource; import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
import java.awt.*; import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.Instant;
import java.util.Base64;
import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -64,8 +73,14 @@ public final class BrowserTab extends OACPanel {
* @param onLocationChange callback invoked on URL changes * @param onLocationChange callback invoked on URL changes
* @param luaEnabled whether Lua is enabled for this tab * @param luaEnabled whether Lua is enabled for this tab
* @param luaPolicy execution policy for Lua * @param luaPolicy execution policy for Lua
* @param protocolClient protocol client used for OAC network requests
*/ */
public BrowserTab(String key, String initialUrl, Consumer<String> onLocationChange, boolean luaEnabled, LuaExecutionPolicy luaPolicy, ClientImpl protocolClient) { public BrowserTab(String key,
String initialUrl,
Consumer<String> onLocationChange,
boolean luaEnabled,
LuaExecutionPolicy luaPolicy,
ClientImpl protocolClient) {
super(); super();
this.key = Objects.requireNonNull(key, "key"); this.key = Objects.requireNonNull(key, "key");
this.onLocationChanged = Objects.requireNonNull(onLocationChange, "onLocationChange"); this.onLocationChanged = Objects.requireNonNull(onLocationChange, "onLocationChange");
@@ -89,6 +104,108 @@ public final class BrowserTab extends OACPanel {
return sw.toString(); return sw.toString();
} }
private static String normalizeMime(String contentType) {
String ct = (contentType == null || contentType.isBlank()) ? "application/octet-stream" : contentType.trim();
int semi = ct.indexOf(';');
String base = (semi >= 0 ? ct.substring(0, semi) : ct).trim();
return base.isEmpty() ? "application/octet-stream" : base;
}
private static boolean isHtml(String contentType) {
String ct = normalizeMime(contentType).toLowerCase(Locale.ROOT);
return ct.equals("text/html") || ct.equals("application/xhtml+xml");
}
private static boolean isText(String contentType) {
String ct = normalizeMime(contentType).toLowerCase(Locale.ROOT);
return ct.startsWith("text/") || ct.equals("application/json") || ct.equals("application/xml") || ct.endsWith("+json") || ct.endsWith("+xml");
}
private static boolean isImage(String contentType) {
String ct = normalizeMime(contentType).toLowerCase(Locale.ROOT);
return ct.startsWith("image/");
}
private static boolean isPdf(String contentType) {
String ct = normalizeMime(contentType).toLowerCase(Locale.ROOT);
return ct.equals("application/pdf");
}
private static Charset charsetFromContentType(String contentType, Charset def) {
if (contentType == null) return def;
String[] parts = contentType.split(";");
for (String p : parts) {
String s = p.trim();
if (s.toLowerCase(Locale.ROOT).startsWith("charset=")) {
String name = s.substring("charset=".length()).trim();
try {
return Charset.forName(name);
} catch (Exception ignored) {
return def;
}
}
}
return def;
}
private static String extractFilenameFromContentDisposition(Map<String, String> headers) {
if (headers == null || headers.isEmpty()) return null;
String cd = null;
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase("content-disposition")) {
cd = e.getValue();
break;
}
}
if (cd == null || cd.isBlank()) return null;
String lower = cd.toLowerCase(Locale.ROOT);
int fn = lower.indexOf("filename=");
if (fn < 0) return null;
String v = cd.substring(fn + "filename=".length()).trim();
if (v.startsWith("\"")) {
int end = v.indexOf('"', 1);
if (end > 1) return v.substring(1, end);
return null;
}
int semi = v.indexOf(';');
if (semi >= 0) v = v.substring(0, semi).trim();
return v.isBlank() ? null : v;
}
private static String sanitizeFilename(String name) {
String s = name.replace('\\', '_').replace('/', '_');
s = s.replace("..", "_");
s = s.replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_')
.replace('<', '_').replace('>', '_').replace('|', '_');
return s.isBlank() ? "download.bin" : s;
}
private static String extensionFromContentType(String contentType) {
String ct = normalizeMime(contentType).toLowerCase(Locale.ROOT);
if (ct.equals("application/pdf")) return ".pdf";
if (ct.equals("application/zip")) return ".zip";
if (ct.equals("application/x-7z-compressed")) return ".7z";
if (ct.equals("application/x-rar-compressed")) return ".rar";
if (ct.equals("application/gzip")) return ".gz";
if (ct.equals("application/json")) return ".json";
if (ct.equals("application/xml") || ct.endsWith("+xml")) return ".xml";
if (ct.startsWith("image/")) {
int slash = ct.indexOf('/');
if (slash > 0 && slash < ct.length() - 1) {
String ext = ct.substring(slash + 1).trim();
if (!ext.isEmpty()) return "." + ext;
}
}
if (ct.startsWith("text/")) return ".txt";
return ".bin";
}
/** /**
* Returns the stable tab key. * Returns the stable tab key.
* *
@@ -98,6 +215,11 @@ public final class BrowserTab extends OACPanel {
return key; return key;
} }
/**
* Returns the protocol client used by this tab.
*
* @return protocol client
*/
public ClientImpl getProtocolClient() { public ClientImpl getProtocolClient() {
return protocolClient; return protocolClient;
} }
@@ -214,6 +336,8 @@ public final class BrowserTab extends OACPanel {
return getEngineLocation(); return getEngineLocation();
} }
// -------------------- Stream render/save helpers --------------------
/** /**
* Returns current engine location. * Returns current engine location.
* *
@@ -270,6 +394,78 @@ public final class BrowserTab extends OACPanel {
} }
} }
/**
* Receives a fully assembled stream payload from the protocol layer and renders or saves it.
*
* <p>Rules:
* <ul>
* <li>Renderable: HTML/text/images/pdf are rendered directly (no wrapper pages for non-html).</li>
* <li>Not renderable: opens "Save As" dialog.</li>
* <li>If user cancels "Save As": content is shown raw in the browser via data: URL.</li>
* </ul>
*
* @param requestId request correlation id
* @param tabId protocol tab id
* @param pageId protocol page id
* @param frameId protocol frame id
* @param statusCode http-like status code
* @param contentType mime type
* @param headers response headers
* @param content full payload bytes
*/
public void handleStreamFinished(long requestId,
long tabId,
long pageId,
long frameId,
int statusCode,
String contentType,
Map<String, String> headers,
byte[] content) {
String ct = (contentType == null || contentType.isBlank())
? "application/octet-stream"
: contentType.trim();
byte[] data = (content == null) ? new byte[0] : content;
// ---- Renderable types -> render without extra wrapper pages ----
if (isHtml(ct)) {
Charset cs = charsetFromContentType(ct, StandardCharsets.UTF_8);
String html = new String(data, cs);
Platform.runLater(() -> {
WebEngine e = engine;
if (e != null) e.loadContent(html, "text/html");
});
return;
}
if (isText(ct)) {
Charset cs = charsetFromContentType(ct, StandardCharsets.UTF_8);
String text = new String(data, cs);
Platform.runLater(() -> {
WebEngine e = engine;
if (e != null) e.loadContent(text, "text/plain");
});
return;
}
if (isImage(ct) || isPdf(ct)) {
renderRawDataUrl(ct, data);
return;
}
// ---- Not renderable -> Save As; if cancelled -> raw data: URL ----
String suggested = extractFilenameFromContentDisposition(headers);
if (suggested == null || suggested.isBlank()) {
String ext = extensionFromContentType(ct);
suggested = "download_" + requestId + "_" + Instant.now().toEpochMilli() + ext;
} else {
suggested = sanitizeFilename(suggested);
}
showSaveAsDialogAndWriteBytes(suggested, ct, data);
}
/** /**
* Releases resources. * Releases resources.
*/ */
@@ -377,4 +573,75 @@ public final class BrowserTab extends OACPanel {
// Best-effort shutdown. // Best-effort shutdown.
} }
} }
}
private void showSaveAsDialogAndWriteBytes(String suggestedFilename, String contentType, byte[] data) {
SwingUtilities.invokeLater(() -> {
Window parent = SwingUtilities.getWindowAncestor(this);
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("Save As");
chooser.setSelectedFile(new File(suggestedFilename));
int result = chooser.showSaveDialog(parent);
if (result != JFileChooser.APPROVE_OPTION) {
// Cancel -> show raw in browser
renderRawDataUrl(contentType, data);
return;
}
File file = chooser.getSelectedFile();
if (file == null) {
renderRawDataUrl(contentType, data);
return;
}
if (file.isDirectory()) {
JOptionPane.showMessageDialog(parent, "Please choose a file, not a directory.", "Save As", JOptionPane.WARNING_MESSAGE);
renderRawDataUrl(contentType, data);
return;
}
if (file.exists()) {
int overwrite = JOptionPane.showConfirmDialog(
parent,
"File already exists. Overwrite?\n" + file.getAbsolutePath(),
"Confirm Overwrite",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE
);
if (overwrite != JOptionPane.YES_OPTION) {
renderRawDataUrl(contentType, data);
return;
}
}
try {
Files.write(file.toPath(), data);
} catch (IOException ex) {
JOptionPane.showMessageDialog(
parent,
"Failed to save file:\n" + ex.getMessage(),
"Save As",
JOptionPane.ERROR_MESSAGE
);
// On failure, still show raw so user can at least see bytes
renderRawDataUrl(contentType, data);
}
});
}
private void renderRawDataUrl(String contentType, byte[] data) {
String mime = normalizeMime(contentType);
String b64 = Base64.getEncoder().encodeToString(data == null ? new byte[0] : data);
String dataUrl = "data:" + mime + ";base64," + b64;
Platform.runLater(() -> {
WebEngine e = engine;
if (e != null) e.load(dataUrl);
});
}
public void bindProtocolClient() {
protocolClient.getLibImpl().bindTab(this);
}
}

View File

@@ -3,13 +3,13 @@ package org.openautonomousconnection.webclient.ui;
import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader; import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager; import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import org.openautonomousconnection.oacswing.component.*; import org.openautonomousconnection.oacswing.component.*;
import org.openautonomousconnection.oacswing.component.design.DesignManager; import org.openautonomousconnection.oacswing.component.design.DesignManager;
import org.openautonomousconnection.protocol.ProtocolBridge; import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolValues; import org.openautonomousconnection.protocol.ProtocolValues;
import org.openautonomousconnection.protocol.versions.ProtocolVersion; import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webclient.ClientImpl; import org.openautonomousconnection.webclient.ClientImpl;
import org.openautonomousconnection.webclient.Main;
import org.openautonomousconnection.webclient.settings.AppSettings; import org.openautonomousconnection.webclient.settings.AppSettings;
import org.openautonomousconnection.webclient.settings.HistoryStore; import org.openautonomousconnection.webclient.settings.HistoryStore;
import org.openautonomousconnection.webclient.settings.InsEndpoint; import org.openautonomousconnection.webclient.settings.InsEndpoint;
@@ -231,6 +231,8 @@ public class BrowserUI extends OACFrame {
readyTab.loadUrl(normalized); readyTab.loadUrl(normalized);
})); }));
connectTabClient(key, client);
BrowserTab tab = new BrowserTab( BrowserTab tab = new BrowserTab(
key, key,
normalized, normalized,
@@ -247,6 +249,9 @@ public class BrowserUI extends OACFrame {
client client
); );
// Bind stream callbacks to this tab (v1.0.1 stream assembly -> tab render).
tab.bindProtocolClient();
tabRef.set(tab); tabRef.set(tab);
tab.setOpenInNewTabCallback(() -> openNewTab( tab.setOpenInNewTabCallback(() -> openNewTab(
tab.getEngineLocation() == null ? settings.getStartPageUrl() : tab.getEngineLocation() tab.getEngineLocation() == null ? settings.getStartPageUrl() : tab.getEngineLocation()
@@ -272,41 +277,37 @@ public class BrowserUI extends OACFrame {
cardLayout.show(pageHost, key); cardLayout.show(pageHost, key);
browser.addressField().setText(normalized); browser.addressField().setText(normalized);
connectTabClient(key, client);
} }
private void connectTabClient(String key, ClientImpl client) { private void connectTabClient(String key, ClientImpl client) {
try { try {
File logsFolder = new File("logs");
if (!logsFolder.exists()) logsFolder.mkdir();
ProtocolValues values = new ProtocolValues(); ProtocolValues values = new ProtocolValues();
values.packetHandler = new PacketHandler(); values.packetHandler = new PacketHandler();
values.eventManager = new EventManager(); values.eventManager = new EventManager();
values.ssl = settings.isSslEnabled(); values.ssl = settings.isSslEnabled();
AddonLoader addonLoader = new AddonLoader(values.eventManager, values.logger = Main.getLogger();
new Logger(new File(logsFolder, "addons"), false, true)); values.addonLoader = Main.getAddonLoader();
values.protocolVersion = ProtocolVersion.PV_1_0_1_BETA;
AddonLoader addonLoader = values.addonLoader;
ProtocolBridge bridge = new ProtocolBridge( ProtocolBridge bridge = new ProtocolBridge(
client, client,
values, client.getLibImpl(),
ProtocolVersion.PV_1_0_0_BETA, values
new File(logsFolder, "client")
); );
protocolByKey.put(key, bridge); protocolByKey.put(key, bridge);
addonLoaderByKey.put(key, addonLoader); addonLoaderByKey.put(key, addonLoader);
File addonsFolder = new File("addons");
if (!addonsFolder.exists()) addonsFolder.mkdir();
addonLoader.loadAddonsFromDirectory(addonsFolder);
client.buildINSConnection(); client.buildINSConnection();
bridge.getProtocolValues().eventManager.registerListener(client); bridge.getProtocolValues().eventManager.registerListener(client);
InsEndpoint ep = Objects.requireNonNull(settings.getSelectedIns(), "selectedIns"); InsEndpoint ep = Objects.requireNonNull(settings.getSelectedIns(), "selectedIns");
client.getClientINSConnection().connect(ep.host(), ep.port()); client.getClientINSConnection().connect(ep.host(), ep.port());
File addonsFolder = new File("addons");
if (!addonsFolder.exists()) addonsFolder.mkdir();
addonLoader.loadAddonsFromDirectory(addonsFolder);
} catch (Exception e) { } catch (Exception e) {
OACOptionPane.showMessageDialog( OACOptionPane.showMessageDialog(
this, this,
@@ -351,12 +352,6 @@ public class BrowserUI extends OACFrame {
} }
} }
private void closeCurrentTab() {
String key = getSelectedTabKey();
if (key == null) return;
closeTabByKey(key);
}
private void closeTabByKey(String key) { private void closeTabByKey(String key) {
int idx = findTabIndexByKey(key); int idx = findTabIndexByKey(key);
if (idx < 0) return; if (idx < 0) return;
@@ -417,12 +412,9 @@ public class BrowserUI extends OACFrame {
if (idx < 0) return; if (idx < 0) return;
if (plusTabSupport.isPlusTab(idx)) { if (plusTabSupport.isPlusTab(idx)) {
if (suppressPlusAutoOpen) { if (suppressPlusAutoOpen) return;
return; if (tabsByKey.isEmpty()) return;
}
if (tabsByKey.isEmpty()) {
return;
}
handlingTabSwitch = true; handlingTabSwitch = true;
try { try {
int fallback = lastSelectedRealTab >= 0 int fallback = lastSelectedRealTab >= 0
@@ -452,12 +444,22 @@ public class BrowserUI extends OACFrame {
} }
} }
/**
* Returns the currently active tab (excluding the plus-tab).
*
* @return current tab or null
*/
public BrowserTab getCurrentTab() { public BrowserTab getCurrentTab() {
String key = getSelectedTabKey(); String key = getSelectedTabKey();
if (key == null) return null; if (key == null) return null;
return tabsByKey.get(key); return tabsByKey.get(key);
} }
/**
* Returns the addon loader associated with the current tab.
*
* @return addon loader or null
*/
public AddonLoader getCurrentAddonLoader() { public AddonLoader getCurrentAddonLoader() {
String key = getSelectedTabKey(); String key = getSelectedTabKey();
if (key == null) return null; if (key == null) return null;
@@ -470,4 +472,4 @@ public class BrowserUI extends OACFrame {
if (plusTabSupport.isPlusTab(idx)) return null; if (plusTabSupport.isPlusTab(idx)) return null;
return getTitleBar().getTabs().getTitleAt(idx); return getTitleBar().getTabs().getTitleAt(idx);
} }
} }