diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java index 7539dfde2..f33be9df7 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java @@ -62,6 +62,11 @@ public void completed(final ProgressEvent event) { }); webViewAssetProvider.setContent(browser); + + // Block F5/Refresh - the webview uses setText() with no backing URL, + // so browser refresh navigates to about:blank or file:/// causing a blank screen. + browser.addLocationListener(new RefreshBlockingLocationListener()); + browser.addKeyListener(new RefreshBlockingKeyListener()); } super.setupView(parent); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingKeyListener.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingKeyListener.java new file mode 100644 index 000000000..ef0c45add --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingKeyListener.java @@ -0,0 +1,31 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.views; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; + +/** + * Blocks F5 keypress to prevent browser refresh in the chat webview. + * The webview uses setText() with no backing URL, so F5 would navigate + * to about:blank, wiping the UI. + */ +final class RefreshBlockingKeyListener extends KeyAdapter { + + @Override + public void keyPressed(final KeyEvent e) { + if (shouldBlockKey(e.keyCode)) { + e.doit = false; + } + } + + /** + * Determines whether the given key code should be blocked. + * Package-private for testability. + */ + static boolean shouldBlockKey(final int keyCode) { + return keyCode == SWT.F5; + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingLocationListener.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingLocationListener.java new file mode 100644 index 000000000..e52185070 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingLocationListener.java @@ -0,0 +1,38 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.views; + +import org.eclipse.swt.browser.LocationAdapter; +import org.eclipse.swt.browser.LocationEvent; + +/** + * Blocks browser navigation to prevent the chat webview from going blank on + * refresh. The webview uses setText() with no backing URL, so any refresh or + * navigation attempt will leave the rendered content. On different platforms, + * refresh may navigate to about:blank (Windows/Linux) or file:/// (macOS WebKit). + * This listener blocks all navigation since the webview should never navigate away. + */ +final class RefreshBlockingLocationListener extends LocationAdapter { + + @Override + public void changing(final LocationEvent event) { + if (shouldBlockNavigation(event.location)) { + event.doit = false; + } + } + + /** + * Determines whether navigation to the given location should be blocked. + * Blocks about:blank and file:// URLs which are triggered by refresh on + * different platforms. The webview content is set via setText() and should + * never navigate to any URL. + * Package-private for testability. + */ + static boolean shouldBlockNavigation(final String location) { + if (location == null || location.isEmpty()) { + return false; + } + return "about:blank".equals(location) || location.startsWith("file:"); + } +} diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingKeyListenerTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingKeyListenerTest.java new file mode 100644 index 000000000..69ca6fd8c --- /dev/null +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingKeyListenerTest.java @@ -0,0 +1,49 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.views; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.swt.SWT; +import org.junit.jupiter.api.Test; + +public final class RefreshBlockingKeyListenerTest { + + @Test + void testBlocksF5KeyPress() { + assertTrue(RefreshBlockingKeyListener.shouldBlockKey(SWT.F5), + "F5 keypress should be blocked"); + } + + @Test + void testAllowsF4KeyPress() { + assertFalse(RefreshBlockingKeyListener.shouldBlockKey(SWT.F4), + "F4 keypress should not be blocked"); + } + + @Test + void testAllowsF6KeyPress() { + assertFalse(RefreshBlockingKeyListener.shouldBlockKey(SWT.F6), + "F6 keypress should not be blocked"); + } + + @Test + void testAllowsEnterKey() { + assertFalse(RefreshBlockingKeyListener.shouldBlockKey(SWT.CR), + "Enter key should not be blocked"); + } + + @Test + void testAllowsRegularCharacterKey() { + assertFalse(RefreshBlockingKeyListener.shouldBlockKey('a'), + "Regular character keys should not be blocked"); + } + + @Test + void testAllowsEscapeKey() { + assertFalse(RefreshBlockingKeyListener.shouldBlockKey(SWT.ESC), + "Escape key should not be blocked"); + } +} diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingLocationListenerTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingLocationListenerTest.java new file mode 100644 index 000000000..33be28eaa --- /dev/null +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/views/RefreshBlockingLocationListenerTest.java @@ -0,0 +1,48 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.views; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public final class RefreshBlockingLocationListenerTest { + + @Test + void testBlocksNavigationToAboutBlank() { + assertTrue(RefreshBlockingLocationListener.shouldBlockNavigation("about:blank"), + "Navigation to about:blank should be blocked"); + } + + @Test + void testBlocksNavigationToFileProtocol() { + assertTrue(RefreshBlockingLocationListener.shouldBlockNavigation("file:///"), + "Navigation to file:/// should be blocked (macOS WebKit reload)"); + } + + @Test + void testBlocksNavigationToFileProtocolWithPath() { + assertTrue(RefreshBlockingLocationListener.shouldBlockNavigation("file:///tmp/test.html"), + "Navigation to file:// URLs should be blocked"); + } + + @Test + void testAllowsNavigationToHttpsUrl() { + assertFalse(RefreshBlockingLocationListener.shouldBlockNavigation("https://example.com"), + "Navigation to HTTPS URLs should be allowed"); + } + + @Test + void testAllowsNavigationWhenLocationIsNull() { + assertFalse(RefreshBlockingLocationListener.shouldBlockNavigation(null), + "Navigation should be allowed when location is null"); + } + + @Test + void testAllowsNavigationToEmptyString() { + assertFalse(RefreshBlockingLocationListener.shouldBlockNavigation(""), + "Navigation should be allowed when location is empty"); + } +}