Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ Mohamed El Sayed <devblooming@tutanota.com>
Edwin Ludik <edwin.ludik@gmail.com>
Japnit Singh <truejswalia@gmail.com>
Dmitry Kandalov <dmitry.kandalov@gmail.com>
Kazuya Chikamatsu <kazu.chika.shima@gmail.com>
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
### Fixed

- Silent failure when opening Flutter projects without `.idea` directory in IntelliJ IDEA, by removing `FlutterProjectOpenProcessor` and migrating configuration logic to `FlutterInitializer`. (#8845)
- Gutter buttons not running tests with non-ASCII characters in their names. (#7985)
- Freeze from JX Browser close. (#8864)

## 90.0.0

Expand Down
33 changes: 17 additions & 16 deletions src/io/flutter/jxbrowser/EmbeddedBrowserEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
*/
package io.flutter.jxbrowser;

import com.intellij.openapi.application.ApplicationListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import io.flutter.utils.OpenApiUtils;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.engine.EngineOptions;
import com.teamdev.jxbrowser.engine.PasswordStore;
Expand All @@ -22,7 +23,7 @@
import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;
import static com.teamdev.jxbrowser.engine.RenderingMode.OFF_SCREEN;

public class EmbeddedBrowserEngine {
public class EmbeddedBrowserEngine implements Disposable {
private static final @NotNull Logger LOG = PluginLogger.createLogger(EmbeddedBrowserEngine.class);
private final Engine engine;

Expand Down Expand Up @@ -60,22 +61,22 @@ public EmbeddedBrowserEngine() {
}
engine = temp;

ApplicationManager.getApplication().addApplicationListener(new ApplicationListener() {
@Override
public boolean canExitApplication() {
try {
if (engine != null && !engine.isClosed()) {
engine.close();
}
}

@Override
public void dispose() {
OpenApiUtils.safeExecuteOnPooledThread(() -> {
try {
if (engine != null && !engine.isClosed()) {
engine.close();
}
catch (Exception ex) {
if (FlutterSettings.getInstance().isFilePathLoggingEnabled()) {
LOG.info(ex);
} else {
LOG.info("Exception when closing JX Browser engine: " + ex.getMessage());
}
}
catch (Exception ex) {
if (FlutterSettings.getInstance().isFilePathLoggingEnabled()) {
LOG.info(ex);
} else {
LOG.info("Exception when closing JX Browser engine: " + ex.getMessage());
}
Comment on lines +74 to 79
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This catch block logic is also present in EmbeddedJxBrowser.close(). According to the repository style guide (Rule #70, DRY), duplicated code should be avoided. Please consider extracting this conditional logging logic into a shared utility method to improve maintainability and reduce duplication.

return true;
}
});
}
Expand Down
14 changes: 13 additions & 1 deletion src/io/flutter/jxbrowser/EmbeddedJxBrowser.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.flutter.settings.FlutterSettings;
import io.flutter.utils.AsyncUtils;
import io.flutter.utils.JxBrowserUtils;
import io.flutter.utils.OpenApiUtils;
import io.flutter.utils.ZoomLevelSelector;
import io.flutter.view.EmbeddedBrowser;
import io.flutter.view.EmbeddedTab;
Expand Down Expand Up @@ -98,7 +99,18 @@ public void loadUrl(String url) {

@Override
public void close() {
this.browser.close();
OpenApiUtils.safeExecuteOnPooledThread(() -> {
try {
this.browser.close();
}
catch (Exception ex) {
if (FlutterSettings.getInstance().isFilePathLoggingEnabled()) {
LOG.info(ex);
} else {
LOG.info("Exception when closing JX Browser instance: " + ex.getMessage());
}
}
});
}

@Override
Expand Down
11 changes: 7 additions & 4 deletions src/io/flutter/run/common/CommonTestConfigUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,16 @@ public String findTestName(@Nullable PsiElement elt) {
final DartCallExpression call = findEnclosingTestCall(elt, getTestsFromOutline(elt.getContainingFile()));
if (call == null) return null;

return extractTestName(call);
}

@VisibleForTesting
@Nullable
public String extractTestName(@NotNull DartCallExpression call) {
final DartStringLiteralExpression lit = DartSyntax.getArgument(call, 0, DartStringLiteralExpression.class);
if (lit == null) return null;

final String name = DartSyntax.unquote(lit);
if (name == null) return null;

return StringUtil.escapeProperty(name, false);
return DartSyntax.unquote(lit);
}

/**
Expand Down
15 changes: 8 additions & 7 deletions src/io/flutter/view/EmbeddedBrowser.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ public void projectClosing(@NotNull Project project) {
final Map<String, BrowserTab> tabs = windows.get(window);
for (final String tabName : tabs.keySet()) {
final BrowserTab tab = tabs.get(tabName);
if (tab.embeddedTab != null) {
try {
tab.embeddedTab.close();
final EmbeddedTab embeddedTab = tab.embeddedTab;
if (embeddedTab != null) {
try {
embeddedTab.close();
}
catch (Exception ex) {
logger().info(ex);
}
}
catch (Exception ex) {
logger().info(ex);
}
}
}
tabs.clear();
}
Expand Down
7 changes: 6 additions & 1 deletion src/io/flutter/widgetpreview/WidgetPreviewPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,12 @@ public void dispose() {
// Dispose the browser tab
final EmbeddedTab tab = browserTabRef.getAndSet(null);
if (tab != null) {
tab.close();
try {
tab.close();
}
catch (Exception ex) {
LOG.info(ex);
}
}
}
}
33 changes: 33 additions & 0 deletions testSrc/unit/io/flutter/run/common/CommonTestConfigUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2026 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
package io.flutter.run.common;

import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.jetbrains.lang.dart.psi.DartCallExpression;
import io.flutter.AbstractDartElementTest;
import io.flutter.dart.DartSyntax;
import io.flutter.run.test.TestConfigUtils;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class CommonTestConfigUtilsTest extends AbstractDartElementTest {
@Test
public void extractTestNameShouldNotEscapeNonAscii() throws Exception {
run(() -> {
final PsiElement testKeyword = setUpDartElement(
"main() { test('テスト', () {}); }", "test", LeafPsiElement.class);
final DartCallExpression call =
DartSyntax.findEnclosingFunctionCall(testKeyword, "test");
assertNotNull(call);

final String name = TestConfigUtils.getInstance().extractTestName(call);
assertEquals("テスト", name);
});
}
}
22 changes: 21 additions & 1 deletion tool/kokoro/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ echo "kokoro build start"
echo "kokoro build finished"

echo "kokoro deploy start"
./bin/plugin deploy --channel=dev

KOKORO_TOKEN_FILE="${KOKORO_KEYSTORE_DIR}/${FLUTTER_KEYSTORE_ID}_${FLUTTER_KEYSTORE_NAME}"
if [ ! -f "$KOKORO_TOKEN_FILE" ]; then
echo "Error: Keystore token file not found at $KOKORO_TOKEN_FILE"
exit 1
fi
TOKEN=$(cat "$KOKORO_TOKEN_FILE")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The TOKEN=$(cat ...) command substitution can be fragile. If the token file contains leading/trailing whitespace or newlines, they will be included in the TOKEN variable, which can lead to authentication failures. Using xargs is a more robust way to read the content of a file into a variable as it handles whitespace trimming automatically.

Suggested change
TOKEN=$(cat "$KOKORO_TOKEN_FILE")
TOKEN=$(xargs < "$KOKORO_TOKEN_FILE")


ZIP_FILE="build/distributions/flutter-intellij-kokoro.zip"
if [ ! -f "$ZIP_FILE" ]; then
echo "Error: Zip file not found at $ZIP_FILE"
exit 1
fi

echo "Uploading $ZIP_FILE to JetBrains Marketplace..."
curl -if --fail \
--header "Authorization: Bearer $TOKEN" \
-F pluginId=9212 \
-F file=@"$ZIP_FILE" \
-F channel=dev \
https://plugins.jetbrains.com/plugin/uploadPlugin

echo "kokoro deploy finished"
Loading