From 4d4115f97d865c188b052754dae3a04f842d9ab3 Mon Sep 17 00:00:00 2001 From: Fynnian Brosius Date: Thu, 19 Mar 2026 22:10:37 +0100 Subject: [PATCH 1/3] Add protocol handler support for JabRef --- CHANGELOG.md | 1 + ...er-extension-communication-architecture.md | 97 +++++++++++++++++++ flatpak/org.jabref.jabref.desktop | 2 +- jabgui/buildres/linux/JabRef.desktop | 2 +- jabgui/buildres/macos/Info.plist | 11 +++ jabgui/buildres/windows/main.wxs | 16 +++ .../org/jabref/cli/ArgumentProcessor.java | 22 ++++- .../main/java/org/jabref/gui/JabRefGUI.java | 6 ++ .../jabref/gui/desktop/os/NativeDesktop.java | 10 ++ .../java/org/jabref/gui/desktop/os/OSX.java | 25 ++++- .../org/jabref/cli/ArgumentProcessorTest.java | 83 ++++++++++++++++ snap/gui/jabref.desktop | 2 +- 12 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 docs/decisions/0055-browser-extension-communication-architecture.md create mode 100644 jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1900ce767ab..b4448374518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a CLI option `--field-formatters` to the `convert` and `generate-bib-from-aux` commands to apply field formatters during export. [#11520](https://github.com/JabRef/jabref/issues/11520) - We added a preference to skip the import dialog for entries received from browser extensions, allowing direct import into the current library. The import dialog is shown by default; users can enable direct import in Preferences. - We added support for dragging entries from the "Citation relations" tab to other libraries. [#15135](https://github.com/JabRef/jabref/issues/15135) +- We added `jabref://` protocol handler registration so the browser extension can launch or foreground JabRef when the HTTP server is unreachable. [#15294](https://github.com/JabRef/jabref/pull/15294) - We added a fetcher selection dropdown to the citation count field in the General tab, allowing users to choose between Semantic Scholar, OpenAlex, OpenCitations, and scite.ai as the source. The selected fetcher is now persisted across restarts and can also be configured in the Entry Editor preferences. [#15134](https://github.com/JabRef/jabref/issues/15134) - We added support for citation properties in the CAYW endpoint. [#13821](https://github.com/JabRef/jabref/issues/13821) - We added an export format for [`academicpages`](https://academicpages.github.io/) format. [#12727](https://github.com/JabRef/jabref/issues/12727) diff --git a/docs/decisions/0055-browser-extension-communication-architecture.md b/docs/decisions/0055-browser-extension-communication-architecture.md new file mode 100644 index 00000000000..6ed74a5ffc4 --- /dev/null +++ b/docs/decisions/0055-browser-extension-communication-architecture.md @@ -0,0 +1,97 @@ +--- +nav_order: 55 +parent: Decision Records +status: proposed +date: 2026-03-06 +--- +# Use Hybrid Architecture (Protocol Handler + HTTP) for Browser Extension Communication + +## Context and Problem Statement + +JabRef's browser extension imports bibliographic data from web pages into the desktop application via HTTP requests to a local server (`jabsrv`). +If JabRef is not running, the request fails silently and the import is lost. +Additionally, the server sets `Access-Control-Allow-Origin: *` without authentication, allowing any website to send requests. + +How should the extension communicate with the desktop application to solve both problems while remaining cross-platform, cross-browser, and maintainable? + +## Decision Drivers + +* Must work on Windows, macOS, and Linux +* Must work in Chrome and Firefox under Manifest V3 +* Must handle the case when JabRef is not running +* Must authenticate the extension and protect against CSRF +* Changes must be maintainable by JabRef's open-source community + +## Considered Options + +* Native Messaging +* Local HTTP API (status quo) +* Protocol Handler only +* Hybrid — Protocol Handler + HTTP +* WebSocket +* Companion / Daemon + +## Decision Outcome + +Chosen option: "Hybrid — Protocol Handler + HTTP", because it comes out best (see below). + +The extension sends data via HTTP to `jabsrv` on localhost. +When JabRef is not running, a `jabref://` protocol handler starts the application; the extension then polls until the HTTP endpoint becomes reachable. + +### Consequences + +* Good, because HTTP is platform- and browser-independent using standard `fetch()` API +* Good, because the protocol handler starts JabRef when it is not running, without encoding data in the URL +* Good, because `jabsrv` already provides the HTTP infrastructure +* Good, because graceful degradation to pure HTTP when handler is not registered +* Bad, because a localhost listener requires CSRF mitigations (custom headers, origin checks) +* Bad, because the protocol handler must be registered on three operating systems +* Bad, because two communication channels increase implementation complexity + +## Pros and Cons of the Options + +### Native Messaging + +* Good, because no network listener exposed — best security properties +* Good, because the browser manages the host process lifecycle +* Bad, because six manifest variants and two host script languages required +* Bad, because high packaging overhead across multiple OS and package formats +* Bad, because JabRef already aims to remove this infrastructure due to maintenance burden (see PR #14884) + +### Local HTTP API (status quo) + +* Good, because platform- and browser-independent, easy to test +* Bad, because no mechanism to start JabRef when not running — import is lost + +### Protocol Handler only + +* Good, because can launch JabRef when not running +* Bad, because no authentication possible — any website can trigger the URL +* Bad, because limited URL length +* Bad, because unidirectional, no response channel + +### Hybrid (Protocol Handler + HTTP) + +* Good, because HTTP handles data transfer with full response channel +* Good, because protocol handler carries no data, avoiding the security issues of "Protocol Handler only" +* Good, because REST endpoints are extensible for future features +* Bad, because localhost listener requires CSRF mitigations +* Bad, because two communication channels increase complexity + +### WebSocket + +* Good, because persistent bidirectional connection with low latency +* Bad, because no mechanism to start JabRef when not running +* Bad, because WebSocket is not subject to Same-Origin Policy — any website can connect + +### Companion / Daemon + +* Good, because handles the offline-app case via local queue +* Bad, because three fundamentally different service managers per OS +* Bad, because effectively a second software project with own build system and CI/CD + +## More Information + +* [Issue #17: Architecture discussion](https://github.com/JabRef/JabRef-Browser-Extension-fresh/issues/17) +* [PR #18: Protocol Handler PoC](https://github.com/JabRef/JabRef-Browser-Extension-fresh/pull/18) +* [PR #14884: Remove Native Messaging infrastructure](https://github.com/JabRef/jabref/pull/14884) diff --git a/flatpak/org.jabref.jabref.desktop b/flatpak/org.jabref.jabref.desktop index 839978b0c88..c6081f1981a 100644 --- a/flatpak/org.jabref.jabref.desktop +++ b/flatpak/org.jabref.jabref.desktop @@ -9,4 +9,4 @@ Exec=JabRef %U Keywords=bibtex;biblatex;latex;bibliography Categories=Office; StartupWMClass=org-jabref-JabRefMain -MimeType=text/x-bibtex; +MimeType=text/x-bibtex;x-scheme-handler/jabref; diff --git a/jabgui/buildres/linux/JabRef.desktop b/jabgui/buildres/linux/JabRef.desktop index a5d3afddf18..618dcf14eff 100644 --- a/jabgui/buildres/linux/JabRef.desktop +++ b/jabgui/buildres/linux/JabRef.desktop @@ -7,7 +7,7 @@ Icon=APPLICATION_ICON Terminal=false Type=Application Categories=DEPLOY_BUNDLE_CATEGORY -DESKTOP_MIMES +MimeType=text/x-bibtex;x-scheme-handler/jabref; GenericName=BibTeX Editor Keywords=bibtex;biblatex;latex;bibliography diff --git a/jabgui/buildres/macos/Info.plist b/jabgui/buildres/macos/Info.plist index 2c733d1931f..b95c33238f4 100644 --- a/jabgui/buildres/macos/Info.plist +++ b/jabgui/buildres/macos/Info.plist @@ -31,6 +31,17 @@ DEPLOY_BUNDLE_CFBUNDLE_VERSION NSHumanReadableCopyright DEPLOY_BUNDLE_COPYRIGHTDEPLOY_FILE_ASSOCIATIONS + CFBundleURLTypes + + + CFBundleURLName + JabRef Protocol + CFBundleURLSchemes + + jabref + + + NSHighResolutionCapable true NSSupportsAutomaticGraphicsSwitching diff --git a/jabgui/buildres/windows/main.wxs b/jabgui/buildres/windows/main.wxs index 2bbd99d17f9..362379b87e0 100644 --- a/jabgui/buildres/windows/main.wxs +++ b/jabgui/buildres/windows/main.wxs @@ -160,9 +160,25 @@ + + + + + + + + + + + + + + + + diff --git a/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java index 7ace2484925..92a3e61294d 100644 --- a/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -1,6 +1,7 @@ package org.jabref.cli; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.prefs.BackingStoreException; @@ -18,6 +19,7 @@ public class ArgumentProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentProcessor.class); + private static final String JABREF_PROTOCOL_SCHEME = "jabref:"; public enum Mode { INITIAL_START, REMOTE_START } @@ -28,6 +30,7 @@ public enum Mode { INITIAL_START, REMOTE_START } private final List uiCommands = new ArrayList<>(); private boolean guiNeeded = true; + private final boolean protocolHandlerInvoked; public ArgumentProcessor(String[] args, Mode startupMode, @@ -36,8 +39,20 @@ public ArgumentProcessor(String[] args, this.preferences = preferences; this.guiCli = new GuiCommandLine(); + String[] filteredArgs = filterProtocolHandlerArgs(args); + this.protocolHandlerInvoked = filteredArgs.length < args.length; + cli = new CommandLine(this.guiCli); - cli.parseArgs(args); + cli.parseArgs(filteredArgs); + } + + /// Removes `jabref://` protocol handler URLs from the argument list. + /// These are passed by the OS when the `jabref://` URL scheme is triggered + /// and must not reach picocli, which would try to parse them as file paths. + private static String[] filterProtocolHandlerArgs(String[] args) { + return Arrays.stream(args) + .filter(arg -> !arg.startsWith(JABREF_PROTOCOL_SCHEME)) + .toArray(String[]::new); } public List processArguments() { @@ -94,6 +109,11 @@ public List processArguments() { if (guiCli.importBibtex != null) { uiCommands.add(new UiCommand.AppendBibTeXToCurrentLibrary(guiCli.importBibtex)); } + + if (protocolHandlerInvoked) { + uiCommands.add(new UiCommand.Focus()); + } + return uiCommands; } diff --git a/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java b/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java index 992bdaf9c7e..46e82b4342e 100644 --- a/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java @@ -19,6 +19,7 @@ import javafx.stage.WindowEvent; import org.jabref.gui.clipboard.ClipBoardManager; +import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.frame.JabRefFrame; import org.jabref.gui.help.VersionWorker; import org.jabref.gui.icon.IconTheme; @@ -467,6 +468,11 @@ public void startBackgroundTasks() { if (remotePreferences.enableLanguageServer()) { languageServerController.start(cliMessageHandler, remotePreferences.getLanguageServerPort()); } + + NativeDesktop.get().registerJabRefProtocolHandler(uri -> { + LOGGER.debug("Received URI via protocol handler: {}", uri); + Platform.runLater(() -> mainFrame.handleUiCommands(List.of(new UiCommand.Focus()))); + }); } private void setupHttpServerEnabledListener() { diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java index a3f86c090f0..dcfbe92401f 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java @@ -9,6 +9,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; import java.util.regex.Pattern; import org.jabref.architecture.AllowedToUseAwt; @@ -360,4 +361,13 @@ public void moveToTrash(Path path) { public boolean moveToTrashSupported() { return Desktop.getDesktop().isSupported(Desktop.Action.MOVE_TO_TRASH); } + + /// Registers a handler for the {@code jabref://} URL scheme so that the app is focused when a link is opened. + /// On macOS, URI invocations are delivered as Apple Events; this method registers the handler there. + /// On Windows and Linux the URL arrives as a CLI argument and is handled by [ArgumentProcessor], so this is a no-op. + /// + /// @param whenJabRefUriOpened callback invoked when a jabref:// URI is received (e.g. focus the main window; run on FX thread if needed) + public void registerJabRefProtocolHandler(Consumer whenJabRefUriOpened) { + // Default: no-op. Overridden in OSX. + } } diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java index 2d484620e22..f19474f0108 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java @@ -1,8 +1,11 @@ package org.jabref.gui.desktop.os; +import java.awt.Desktop; import java.io.IOException; +import java.net.URI; import java.nio.file.Path; import java.util.Optional; +import java.util.function.Consumer; import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.DialogService; @@ -10,12 +13,14 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.slf4j.LoggerFactory; + /// This class contains macOS (OSX) specific implementations for file directories and file/application open handling methods. /// /// We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link JabKit#initLogging}. /// The configuration of tinylog will become immutable as soon as the first log entry is issued. /// https://tinylog.org/v2/configuration/ -@AllowedToUseAwt("Requires AWT to open a file") +@AllowedToUseAwt("Requires AWT to open a file and to register the jabref:// protocol handler") public class OSX extends NativeDesktop { @Override @@ -52,4 +57,22 @@ public void openConsole(String absolutePath, DialogService dialogService) throws public Path getApplicationDirectory() { return Path.of("/Applications"); } + + @Override + public void registerJabRefProtocolHandler(Consumer whenJabRefUriOpened) { + if (!Desktop.isDesktopSupported()) { + return; + } + Desktop desktop = Desktop.getDesktop(); + if (!desktop.isSupported(Desktop.Action.APP_OPEN_URI)) { + return; + } + desktop.setOpenURIHandler(event -> { + URI uri = event.getURI(); + if ("jabref".equals(uri.getScheme())) { + whenJabRefUriOpened.accept(uri); + } + }); + LoggerFactory.getLogger(OSX.class).debug("Protocol handler for jabref:// registered"); + } } diff --git a/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java b/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java new file mode 100644 index 00000000000..0a042a73bfd --- /dev/null +++ b/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java @@ -0,0 +1,83 @@ +package org.jabref.cli; + +import java.util.List; + +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.logic.UiCommand; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +class ArgumentProcessorTest { + + private final GuiPreferences preferences = mock(GuiPreferences.class); + + @ParameterizedTest + @ValueSource(strings = {"jabref://", "jabref://open", "jabref:", "jabref://some/path"}) + void protocolHandlerUrlProducesFocusCommand(String url) { + ArgumentProcessor processor = new ArgumentProcessor( + new String[] {url}, + ArgumentProcessor.Mode.REMOTE_START, + preferences); + + List commands = processor.processArguments(); + + assertTrue(commands.stream().anyMatch(UiCommand.Focus.class::isInstance)); + } + + @Test + void normalArgumentsAreNotAffectedByProtocolFilter() { + ArgumentProcessor processor = new ArgumentProcessor( + new String[] {"--blank"}, + ArgumentProcessor.Mode.REMOTE_START, + preferences); + + List commands = processor.processArguments(); + + assertTrue(commands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance)); + assertFalse(commands.stream().anyMatch(UiCommand.Focus.class::isInstance)); + } + + @Test + void protocolHandlerUrlCombinedWithNormalArguments() { + ArgumentProcessor processor = new ArgumentProcessor( + new String[] {"jabref://", "--blank"}, + ArgumentProcessor.Mode.REMOTE_START, + preferences); + + List commands = processor.processArguments(); + + assertTrue(commands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance)); + } + + @Test + void emptyArgumentsProduceNoFocusCommand() { + ArgumentProcessor processor = new ArgumentProcessor( + new String[] {}, + ArgumentProcessor.Mode.REMOTE_START, + preferences); + + List commands = processor.processArguments(); + + assertFalse(commands.stream().anyMatch(UiCommand.Focus.class::isInstance)); + } + + @Test + void onlyProtocolHandlerUrlProducesOnlyFocusCommand() { + ArgumentProcessor processor = new ArgumentProcessor( + new String[] {"jabref://"}, + ArgumentProcessor.Mode.REMOTE_START, + preferences); + + List commands = processor.processArguments(); + + assertEquals(1, commands.size()); + assertTrue(commands.getFirst() instanceof UiCommand.Focus); + } +} diff --git a/snap/gui/jabref.desktop b/snap/gui/jabref.desktop index 3444b73fc8a..aa322a44add 100644 --- a/snap/gui/jabref.desktop +++ b/snap/gui/jabref.desktop @@ -9,4 +9,4 @@ Exec=jabref %U Keywords=bibtex;biblatex;latex;bibliography Categories=Office; StartupWMClass=org-jabref-JabRefMain -MimeType=text/x-bibtex; +MimeType=text/x-bibtex;x-scheme-handler/jabref; From 17c835a25d5fdd92614770dfbd83ea7cc3778dca Mon Sep 17 00:00:00 2001 From: Fynnian Brosius Date: Thu, 19 Mar 2026 22:16:48 +0100 Subject: [PATCH 2/3] Update Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4448374518..c98e9239957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,11 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added `jabref://` protocol handler registration so the browser extension can launch or foreground JabRef when the HTTP server is unreachable. [#15378](https://github.com/JabRef/jabref/pull/15378) - We added `--key-patterns` option to CLI parameters to allows users to set a citation key's pattern for a specific entry type. [#14707](https://github.com/JabRef/jabref/issues/14707) - We added a CLI option `--field-formatters` to the `convert` and `generate-bib-from-aux` commands to apply field formatters during export. [#11520](https://github.com/JabRef/jabref/issues/11520) - We added a preference to skip the import dialog for entries received from browser extensions, allowing direct import into the current library. The import dialog is shown by default; users can enable direct import in Preferences. - We added support for dragging entries from the "Citation relations" tab to other libraries. [#15135](https://github.com/JabRef/jabref/issues/15135) -- We added `jabref://` protocol handler registration so the browser extension can launch or foreground JabRef when the HTTP server is unreachable. [#15294](https://github.com/JabRef/jabref/pull/15294) - We added a fetcher selection dropdown to the citation count field in the General tab, allowing users to choose between Semantic Scholar, OpenAlex, OpenCitations, and scite.ai as the source. The selected fetcher is now persisted across restarts and can also be configured in the Entry Editor preferences. [#15134](https://github.com/JabRef/jabref/issues/15134) - We added support for citation properties in the CAYW endpoint. [#13821](https://github.com/JabRef/jabref/issues/13821) - We added an export format for [`academicpages`](https://academicpages.github.io/) format. [#12727](https://github.com/JabRef/jabref/issues/12727) From c0b8bba17d8446465388b1624c071bdc7c4db409 Mon Sep 17 00:00:00 2001 From: Fynnian Brosius Date: Fri, 20 Mar 2026 14:54:12 +0100 Subject: [PATCH 3/3] Match any case in url handler --- .../org/jabref/cli/ArgumentProcessor.java | 6 +++- .../org/jabref/cli/ArgumentProcessorTest.java | 32 +++++++------------ 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java index 92a3e61294d..9fbeb38b11b 100644 --- a/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -51,10 +51,14 @@ public ArgumentProcessor(String[] args, /// and must not reach picocli, which would try to parse them as file paths. private static String[] filterProtocolHandlerArgs(String[] args) { return Arrays.stream(args) - .filter(arg -> !arg.startsWith(JABREF_PROTOCOL_SCHEME)) + .filter(arg -> !isJabRefProtocolArgument(arg)) .toArray(String[]::new); } + private static boolean isJabRefProtocolArgument(String arg) { + return arg.regionMatches(true, 0, JABREF_PROTOCOL_SCHEME, 0, JABREF_PROTOCOL_SCHEME.length()); + } + public List processArguments() { uiCommands.clear(); guiNeeded = true; diff --git a/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java b/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java index 0a042a73bfd..220dac2e5cd 100644 --- a/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java +++ b/jabgui/src/test/java/org/jabref/cli/ArgumentProcessorTest.java @@ -10,8 +10,6 @@ import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; class ArgumentProcessorTest { @@ -19,7 +17,13 @@ class ArgumentProcessorTest { private final GuiPreferences preferences = mock(GuiPreferences.class); @ParameterizedTest - @ValueSource(strings = {"jabref://", "jabref://open", "jabref:", "jabref://some/path"}) + @ValueSource(strings = { + "jabref://", + "jabref://open", + "jabref:", + "jabref://some/path", + "JABREF://open", + "JabRef://open"}) void protocolHandlerUrlProducesFocusCommand(String url) { ArgumentProcessor processor = new ArgumentProcessor( new String[] {url}, @@ -28,7 +32,7 @@ void protocolHandlerUrlProducesFocusCommand(String url) { List commands = processor.processArguments(); - assertTrue(commands.stream().anyMatch(UiCommand.Focus.class::isInstance)); + assertEquals(List.of(new UiCommand.Focus()), commands); } @Test @@ -40,8 +44,7 @@ void normalArgumentsAreNotAffectedByProtocolFilter() { List commands = processor.processArguments(); - assertTrue(commands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance)); - assertFalse(commands.stream().anyMatch(UiCommand.Focus.class::isInstance)); + assertEquals(List.of(new UiCommand.BlankWorkspace()), commands); } @Test @@ -53,7 +56,7 @@ void protocolHandlerUrlCombinedWithNormalArguments() { List commands = processor.processArguments(); - assertTrue(commands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance)); + assertEquals(List.of(new UiCommand.BlankWorkspace()), commands); } @Test @@ -65,19 +68,6 @@ void emptyArgumentsProduceNoFocusCommand() { List commands = processor.processArguments(); - assertFalse(commands.stream().anyMatch(UiCommand.Focus.class::isInstance)); - } - - @Test - void onlyProtocolHandlerUrlProducesOnlyFocusCommand() { - ArgumentProcessor processor = new ArgumentProcessor( - new String[] {"jabref://"}, - ArgumentProcessor.Mode.REMOTE_START, - preferences); - - List commands = processor.processArguments(); - - assertEquals(1, commands.size()); - assertTrue(commands.getFirst() instanceof UiCommand.Focus); + assertEquals(List.of(), commands); } }