diff --git a/LICENSE b/LICENSE index 3f64855..a7b8645 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +1,2 @@ -Please read the license here: https://open-autonomous-connection.org/license.html \ No newline at end of file +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 diff --git a/README.MD b/README.MD index 7e4dc3a..c6d71e7 100644 --- a/README.MD +++ b/README.MD @@ -2,4 +2,17 @@ This is the Protocol for our Open Autonomous Connection project.
Feel free to join our Discord. -
\ No newline at end of file +
+## License Notice + +This project (OAC) is licensed under +the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.org/license.html). + +**Third-party components:** +
+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. diff --git a/pom.xml b/pom.xml index 83eb462..586708a 100644 --- a/pom.xml +++ b/pom.xml @@ -15,8 +15,8 @@ The default WebClient - 23 - 23 + 25 + 25 UTF-8 @@ -69,7 +69,7 @@ org.openautonomousconnection OACSwing - 1.0.0-BETA.1.1 + 0.0.0-STABLE.1.3 org.openautonomousconnection @@ -78,13 +78,13 @@ org.openautonomousconnection - InfoNameLib - 1.0.0-BETA.1.3 + Protocol + 1.0.1-BETA.0.3 org.projectlombok lombok - 1.18.38 + 1.18.42 provided @@ -135,6 +135,14 @@ maven-compiler-plugin 3.13.0 + + + org.projectlombok + lombok + 1.18.42 + + + --add-exports java.base/sun.security.x509=ALL-UNNAMED diff --git a/src/main/java/org/openautonomousconnection/webclient/ClientImpl.java b/src/main/java/org/openautonomousconnection/webclient/ClientImpl.java index dba0639..036c33b 100644 --- a/src/main/java/org/openautonomousconnection/webclient/ClientImpl.java +++ b/src/main/java/org/openautonomousconnection/webclient/ClientImpl.java @@ -1,90 +1,98 @@ package org.openautonomousconnection.webclient; import dev.unlegitdqrk.unlegitlibrary.event.Listener; -import org.openautonomousconnection.infonamelib.LibClientImpl; -import org.openautonomousconnection.infonamelib.OacWebUrlInstaller; +import lombok.Getter; import org.openautonomousconnection.oacswing.component.OACOptionPane; import org.openautonomousconnection.protocol.side.client.ProtocolClient; import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent; +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.Component; +import java.io.ByteArrayOutputStream; +import java.net.URL; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; /** - * Protocol client implementation for the WebClient. + * WebClient Protocol implementation (v1.0.1). + * + *

Implements full stream assembly with strict correlation via: + * requestId + tabId + pageId + frameId.

*/ -public class ClientImpl extends ProtocolClient { +public final class ClientImpl extends ProtocolClient { + 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 AtomicBoolean connectedInitialized = new AtomicBoolean(false); + + private final AtomicBoolean serverConnectionInitialized = new AtomicBoolean(false); private final Component dialogParent; private final Runnable onServerReady; public ClientImpl(Component dialogParent, Runnable onServerReady) { this.dialogParent = dialogParent; - this.onServerReady = Objects.requireNonNull(onServerReady, "onServerReady"); + this.onServerReady = Objects.requireNonNull(onServerReady); } @Override public boolean trustINS(String caFingerprint) { Object[] options = {"Continue", "Cancel"}; - int result = OACOptionPane.showOptionDialog( + return OACOptionPane.showOptionDialog( dialogParent, - "You never connected to this INS before!\n" + - "Fingerprint: " + caFingerprint + "\nDo you want to connect?", + "Fingerprint: " + caFingerprint + "\nContinue?", "INS Connection", OACOptionPane.YES_NO_OPTION, OACOptionPane.INFORMATION_MESSAGE, null, options, options[0] - ); - - return result == 0; + ) == 0; } @Override public boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint) { Object[] options = {"Continue", "Cancel"}; - - int result = OACOptionPane.showOptionDialog( + return OACOptionPane.showOptionDialog( dialogParent, - "The fingerprint does not match with the saved fingerprint!\n" + - "Saved Fingerprint: " + oldCAFingerprint + "\n" + - "New Fingerprint: " + newCAFingerprint + "\n" + - "Do you want to connect?", + "Saved: " + oldCAFingerprint + "\nNew: " + newCAFingerprint + "\nContinue?", "INS Connection", OACOptionPane.YES_NO_OPTION, OACOptionPane.INFORMATION_MESSAGE, null, options, options[0] - ); - - return result == 0; + ) == 0; } @Listener public void onConnected(ConnectedToProtocolINSServerEvent event) { try { - buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl); - OacWebUrlInstaller.installOnce(getProtocolBridge().getProtocolValues().eventManager, this, libImpl); - if (connectedInitialized.compareAndSet(false, true)) { + if (serverConnectionInitialized.compareAndSet(false, true)) { + buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl); onServerReady.run(); } } catch (Exception e) { - getProtocolBridge().getLogger().exception("Failed to build Server connection", e); - OACOptionPane.showMessageDialog( - dialogParent, - "Failed to to build Server connection:\n" + e.getMessage(), - "Server Connection", - OACOptionPane.ERROR_MESSAGE - ); + serverConnectionInitialized.set(false); + throw new RuntimeException(e); } } - private class LibImpl extends LibClientImpl { + 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 streams = new ConcurrentHashMap<>(); + + private BrowserTab currentTab; + @Override public void serverConnectionFailed(Exception exception) { getProtocolBridge().getLogger().exception("Failed to connect to server", exception); @@ -95,5 +103,141 @@ public class ClientImpl extends ProtocolClient { 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 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[] ignored) { + + 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 + ); + } + } } -} + + 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 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 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; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/openautonomousconnection/webclient/Main.java b/src/main/java/org/openautonomousconnection/webclient/Main.java index 07cc55e..70602a1 100644 --- a/src/main/java/org/openautonomousconnection/webclient/Main.java +++ b/src/main/java/org/openautonomousconnection/webclient/Main.java @@ -1,6 +1,8 @@ package org.openautonomousconnection.webclient; 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.DesignManager; import org.openautonomousconnection.webclient.settings.AppSettings; @@ -8,6 +10,7 @@ import org.openautonomousconnection.webclient.settings.SettingsManager; import org.openautonomousconnection.webclient.ui.BrowserUI; import javax.swing.*; +import java.io.File; import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; @@ -23,6 +26,8 @@ public class Main { private static AppSettings settings; private static AddonLoader addonLoader; + private static Logger logger; + private static EventManager eventManager; public static BrowserUI getUi() { return ui; @@ -32,6 +37,14 @@ public class Main { return settings; } + public static AddonLoader getAddonLoader() { + return addonLoader; + } + + public static Logger getLogger() { + return logger; + } + private static void installDefaultCookieManager() { if (CookieHandler.getDefault() != null) return; @@ -40,7 +53,10 @@ public class Main { CookieHandler.setDefault(cm); } - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { + eventManager = new EventManager(); + logger = new Logger(new File("logs", "client"), false, true); + addonLoader = new AddonLoader(eventManager, logger); settings = SettingsManager.load(); FxBootstrap.ensureInitialized(); diff --git a/src/main/java/org/openautonomousconnection/webclient/ui/BrowserTab.java b/src/main/java/org/openautonomousconnection/webclient/ui/BrowserTab.java index 71692c3..60af345 100644 --- a/src/main/java/org/openautonomousconnection/webclient/ui/BrowserTab.java +++ b/src/main/java/org/openautonomousconnection/webclient/ui/BrowserTab.java @@ -26,7 +26,16 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.awt.*; +import java.io.File; +import java.io.IOException; 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.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; @@ -64,8 +73,14 @@ public final class BrowserTab extends OACPanel { * @param onLocationChange callback invoked on URL changes * @param luaEnabled whether Lua is enabled for this tab * @param luaPolicy execution policy for Lua + * @param protocolClient protocol client used for OAC network requests */ - public BrowserTab(String key, String initialUrl, Consumer onLocationChange, boolean luaEnabled, LuaExecutionPolicy luaPolicy, ClientImpl protocolClient) { + public BrowserTab(String key, + String initialUrl, + Consumer onLocationChange, + boolean luaEnabled, + LuaExecutionPolicy luaPolicy, + ClientImpl protocolClient) { super(); this.key = Objects.requireNonNull(key, "key"); this.onLocationChanged = Objects.requireNonNull(onLocationChange, "onLocationChange"); @@ -98,6 +113,11 @@ public final class BrowserTab extends OACPanel { return key; } + /** + * Returns the protocol client used by this tab. + * + * @return protocol client + */ public ClientImpl getProtocolClient() { return protocolClient; } @@ -270,6 +290,78 @@ public final class BrowserTab extends OACPanel { } } + /** + * Receives a fully assembled stream payload from the protocol layer and renders or saves it. + * + *

Rules: + *

    + *
  • Renderable: HTML/text/images/pdf are rendered directly (no wrapper pages for non-html).
  • + *
  • Not renderable: opens "Save As" dialog.
  • + *
  • If user cancels "Save As": content is shown raw in the browser via data: URL.
  • + *
+ * + * @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 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. */ @@ -377,4 +469,179 @@ public final class BrowserTab extends OACPanel { // Best-effort shutdown. } } -} + + // -------------------- Stream render/save helpers -------------------- + + 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); + }); + } + + 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 headers) { + if (headers == null || headers.isEmpty()) return null; + + String cd = null; + for (Map.Entry 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"; + } + + public void bindProtocolClient() { + protocolClient.getLibImpl().bindTab(this); + } +} \ No newline at end of file diff --git a/src/main/java/org/openautonomousconnection/webclient/ui/BrowserUI.java b/src/main/java/org/openautonomousconnection/webclient/ui/BrowserUI.java index 01c46be..d4686b8 100644 --- a/src/main/java/org/openautonomousconnection/webclient/ui/BrowserUI.java +++ b/src/main/java/org/openautonomousconnection/webclient/ui/BrowserUI.java @@ -3,13 +3,13 @@ package org.openautonomousconnection.webclient.ui; import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader; import dev.unlegitdqrk.unlegitlibrary.event.EventManager; import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler; -import dev.unlegitdqrk.unlegitlibrary.utils.Logger; import org.openautonomousconnection.oacswing.component.*; import org.openautonomousconnection.oacswing.component.design.DesignManager; import org.openautonomousconnection.protocol.ProtocolBridge; import org.openautonomousconnection.protocol.ProtocolValues; import org.openautonomousconnection.protocol.versions.ProtocolVersion; import org.openautonomousconnection.webclient.ClientImpl; +import org.openautonomousconnection.webclient.Main; import org.openautonomousconnection.webclient.settings.AppSettings; import org.openautonomousconnection.webclient.settings.HistoryStore; import org.openautonomousconnection.webclient.settings.InsEndpoint; @@ -247,6 +247,9 @@ public class BrowserUI extends OACFrame { client ); + // Bind stream callbacks to this tab (v1.0.1 stream assembly -> tab render). + tab.bindProtocolClient(); + tabRef.set(tab); tab.setOpenInNewTabCallback(() -> openNewTab( tab.getEngineLocation() == null ? settings.getStartPageUrl() : tab.getEngineLocation() @@ -285,14 +288,15 @@ public class BrowserUI extends OACFrame { values.packetHandler = new PacketHandler(); values.eventManager = new EventManager(); values.ssl = settings.isSslEnabled(); - AddonLoader addonLoader = new AddonLoader(values.eventManager, - new Logger(new File(logsFolder, "addons"), false, true)); + AddonLoader addonLoader = Main.getAddonLoader(); ProtocolBridge bridge = new ProtocolBridge( client, values, - ProtocolVersion.PV_1_0_0_BETA, - new File(logsFolder, "client") + ProtocolVersion.PV_1_0_1_BETA, + Main.getLogger(), + Main.getAddonLoader(), + client.getLibImpl() ); protocolByKey.put(key, bridge); @@ -351,12 +355,6 @@ public class BrowserUI extends OACFrame { } } - private void closeCurrentTab() { - String key = getSelectedTabKey(); - if (key == null) return; - closeTabByKey(key); - } - private void closeTabByKey(String key) { int idx = findTabIndexByKey(key); if (idx < 0) return; @@ -417,12 +415,9 @@ public class BrowserUI extends OACFrame { if (idx < 0) return; if (plusTabSupport.isPlusTab(idx)) { - if (suppressPlusAutoOpen) { - return; - } - if (tabsByKey.isEmpty()) { - return; - } + if (suppressPlusAutoOpen) return; + if (tabsByKey.isEmpty()) return; + handlingTabSwitch = true; try { int fallback = lastSelectedRealTab >= 0 @@ -452,12 +447,22 @@ public class BrowserUI extends OACFrame { } } + /** + * Returns the currently active tab (excluding the plus-tab). + * + * @return current tab or null + */ public BrowserTab getCurrentTab() { String key = getSelectedTabKey(); if (key == null) return null; return tabsByKey.get(key); } + /** + * Returns the addon loader associated with the current tab. + * + * @return addon loader or null + */ public AddonLoader getCurrentAddonLoader() { String key = getSelectedTabKey(); if (key == null) return null; @@ -470,4 +475,4 @@ public class BrowserUI extends OACFrame { if (plusTabSupport.isPlusTab(idx)) return null; return getTitleBar().getTabs().getTitleAt(idx); } -} +} \ No newline at end of file