Skip to content
Closed
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Made dev release daily instead of weekly
- Set the device selector component to opaque during its creation to avoid an unexpected background color (#8471)
- Refactored `DeviceSelectorAction` and add rich icons to different platform devices (#8475)
- Fix EDT freezes when opening embedded DevTools and on theme change (#8477)

## 87.1.0

Expand Down
98 changes: 50 additions & 48 deletions src/io/flutter/FlutterInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

Expand All @@ -78,6 +78,10 @@ public class FlutterInitializer extends FlutterProjectActivity {

private @NotNull AtomicLong lastScheduledThemeChangeTime = new AtomicLong();

// Shared scheduler to avoid creating/closing executors on EDT
@NotNull
private final ScheduledExecutorService scheduler = AppExecutorUtil.getAppScheduledExecutorService();

@Override
public void executeProjectStartup(@NotNull Project project) {
log().info("Executing Flutter plugin startup for project: " + project.getName());
Expand Down Expand Up @@ -253,57 +257,55 @@ private void sendThemeChangedEvent(@NotNull Project project) {
lastScheduledThemeChangeTime.set(requestTime);

// Schedule event to be sent in a second if nothing more recent has come in.
try (var executor = Executors.newSingleThreadScheduledExecutor()) {
executor.schedule(() -> {
if (lastScheduledThemeChangeTime.get() != requestTime) {
// A more recent request has been set, so drop this request.
scheduler.schedule(() -> {
if (lastScheduledThemeChangeTime.get() != requestTime) {
// A more recent request has been set, so drop this request.
return;
}

final JsonObject params = new JsonObject();
params.addProperty("eventKind", "themeChanged");
params.addProperty("streamId", "Editor");

final JsonObject themeData = new JsonObject();
final DevToolsUtils utils = new DevToolsUtils();
themeData.addProperty("isDarkMode", Boolean.FALSE.equals(utils.getIsBackgroundBright()));
themeData.addProperty("backgroundColor", utils.getColorHexCode());
themeData.addProperty("fontSize", utils.getFontSize().intValue());

final JsonObject eventData = new JsonObject();
eventData.add("theme", themeData);
params.add("eventData", eventData);

try {
final DtdUtils dtdUtils = new DtdUtils();
final DartToolingDaemonService dtdService = dtdUtils.readyDtdService(project).get();
if (dtdService == null) {
log().error("Unable to send theme changed event because DTD service is null");
return;
}

final JsonObject params = new JsonObject();
params.addProperty("eventKind", "themeChanged");
params.addProperty("streamId", "Editor");

final JsonObject themeData = new JsonObject();
final DevToolsUtils utils = new DevToolsUtils();
themeData.addProperty("isDarkMode", Boolean.FALSE.equals(utils.getIsBackgroundBright()));
themeData.addProperty("backgroundColor", utils.getColorHexCode());
themeData.addProperty("fontSize", utils.getFontSize().intValue());

final JsonObject eventData = new JsonObject();
eventData.add("theme", themeData);
params.add("eventData", eventData);

try {
final DtdUtils dtdUtils = new DtdUtils();
final DartToolingDaemonService dtdService = dtdUtils.readyDtdService(project).get();
if (dtdService == null) {
log().error("Unable to send theme changed event because DTD service is null");
return;
}

dtdService.sendRequest("postEvent", params, false, object -> {
JsonObject result = object.getAsJsonObject("result");
if (result == null) {
log().error("Theme changed event returned null result");
return;
}
JsonPrimitive type = result.getAsJsonPrimitive("type");
if (type == null) {
log().error("Theme changed event result type is null");
return;
}
if (!"Success".equals(type.getAsString())) {
log().error("Theme changed event result: " + type.getAsString());
}
dtdService.sendRequest("postEvent", params, false, object -> {
JsonObject result = object.getAsJsonObject("result");
if (result == null) {
log().error("Theme changed event returned null result");
return;
}
);
}
catch (WebSocketException | InterruptedException | ExecutionException e) {
log().error("Unable to send theme changed event", e);
}
}, 1, TimeUnit.SECONDS);
}
JsonPrimitive type = result.getAsJsonPrimitive("type");
if (type == null) {
log().error("Theme changed event result type is null");
return;
}
if (!"Success".equals(type.getAsString())) {
log().error("Theme changed event result: " + type.getAsString());
}
}
);
}
catch (WebSocketException | InterruptedException | ExecutionException e) {
log().error("Unable to send theme changed event", e);
}
}, 1, TimeUnit.SECONDS);
}

private void checkSdkVersionNotification(@NotNull Project project) {
Expand Down
13 changes: 5 additions & 8 deletions src/io/flutter/jxbrowser/EmbeddedJxBrowser.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public class EmbeddedJxBrowser extends EmbeddedBrowser {
"Waiting for JxBrowser installation timed out. Restart your IDE to try again.";
private static final String INSTALLATION_WAIT_FAILED = "The JxBrowser installation failed unexpectedly. Restart your IDE to try again.";
private static final int INSTALLATION_WAIT_LIMIT_SECONDS = 30;

@NotNull
private final AtomicReference<Engine> engineRef = new AtomicReference<>(null);

private final Project project;
Expand Down Expand Up @@ -173,19 +175,14 @@ private EmbeddedJxBrowser(@NotNull Project project) {
}

@Override
public @Nullable EmbeddedTab openEmbeddedTab(ContentManager contentManager) {
public @Nullable EmbeddedTab openEmbeddedTab(@NotNull ContentManager contentManager) {
manageJxBrowserDownload(contentManager);
if (engineRef.get() == null) {
engineRef.compareAndSet(null, EmbeddedBrowserEngine.getInstance().getEngine());
}
final Engine engine = engineRef.get();
if (engine == null) {
showMessageWithUrlLink(jxBrowserErrorMessage(), contentManager);
handleJxBrowserInstallationInProgress(contentManager);
return null;
}
else {
return new EmbeddedJxBrowserTab(engine);
}
return new EmbeddedJxBrowserTab(engine);
}

private @NotNull String jxBrowserErrorMessage() {
Expand Down
57 changes: 57 additions & 0 deletions testSrc/unit/io/flutter/FlutterInitializerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2016 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;

import org.junit.Test;

import java.lang.reflect.Field;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicLong;

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

/**
* Tests for {@link FlutterInitializer}.
*/
public class FlutterInitializerTest {

@Test
public void testInitializerCanBeCreated() {
// Test that we can create FlutterInitializer without issues
// This validates that the shared scheduler field is properly initialized
FlutterInitializer initializer = new FlutterInitializer();
assertNotNull("FlutterInitializer should be created successfully", initializer);
}

@Test
public void testSchedulerFieldExists() throws Exception {
// Test that the scheduler field exists and is properly initialized
FlutterInitializer initializer = new FlutterInitializer();

Field schedulerField = FlutterInitializer.class.getDeclaredField("scheduler");
schedulerField.setAccessible(true);

Object scheduler = schedulerField.get(initializer);
assertNotNull("Scheduler field should be initialized", scheduler);
assertTrue("Scheduler should be a ScheduledExecutorService",
scheduler instanceof ScheduledExecutorService);
}

@Test
public void testDebounceFieldExists() throws Exception {
// Test that the debounce field exists and is properly initialized
FlutterInitializer initializer = new FlutterInitializer();

Field debounceField = FlutterInitializer.class.getDeclaredField("lastScheduledThemeChangeTime");
debounceField.setAccessible(true);

Object debounceTimer = debounceField.get(initializer);
assertNotNull("Debounce timer field should be initialized", debounceTimer);
assertTrue("Debounce timer should be an AtomicLong",
debounceTimer instanceof AtomicLong);
}
}