Skip to content

Commit 551dac4

Browse files
committed
Use JCEF browser
1 parent 437cc15 commit 551dac4

File tree

11 files changed

+254
-68
lines changed

11 files changed

+254
-68
lines changed

flutter-idea/src/io/flutter/FlutterUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@
3636
import com.intellij.util.PlatformUtils;
3737
import com.jetbrains.lang.dart.DartFileType;
3838
import com.jetbrains.lang.dart.psi.DartFile;
39+
import io.flutter.jxbrowser.EmbeddedJxBrowser;
40+
import io.flutter.jxbrowser.JxBrowserStatus;
3941
import io.flutter.pub.PubRoot;
4042
import io.flutter.pub.PubRootCache;
4143
import io.flutter.settings.FlutterSettings;
4244
import io.flutter.utils.AndroidUtils;
4345
import io.flutter.utils.FlutterModuleUtils;
46+
import io.flutter.view.EmbeddedBrowser;
47+
import io.flutter.view.EmbeddedJcefBrowser;
4448
import org.jetbrains.annotations.NotNull;
4549
import org.jetbrains.annotations.Nullable;
4650
import org.jetbrains.annotations.SystemIndependent;
@@ -622,4 +626,18 @@ private static ModuleSourceOrderEntry findModuleSourceEntry(@NotNull Module modu
622626
}
623627
return null;
624628
}
629+
630+
@Nullable
631+
public static EmbeddedBrowser embeddedBrowser(Project project) {
632+
if (project == null || project.isDisposed()) {
633+
return null;
634+
}
635+
636+
return FlutterSettings.getInstance().isEnableJcefBrowser() ? EmbeddedJcefBrowser.getInstance(project) : EmbeddedJxBrowser.getInstance(project);
637+
}
638+
639+
public static boolean embeddedBrowserAvailable(JxBrowserStatus status) {
640+
return status.equals(JxBrowserStatus.INSTALLED) || status.equals(JxBrowserStatus.INSTALLATION_SKIPPED) && FlutterSettings.getInstance()
641+
.isEnableJcefBrowser();
642+
}
625643
}

flutter-idea/src/io/flutter/jxbrowser/EmbeddedBrowser.java renamed to flutter-idea/src/io/flutter/jxbrowser/EmbeddedJxBrowser.java

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.flutter.devtools.DevToolsUrl;
3434
import io.flutter.settings.FlutterSettings;
3535
import io.flutter.utils.AsyncUtils;
36+
import io.flutter.view.EmbeddedBrowser;
3637
import org.jetbrains.annotations.NotNull;
3738

3839
import java.awt.*;
@@ -42,18 +43,18 @@
4243
import java.util.concurrent.atomic.AtomicBoolean;
4344
import java.util.function.Function;
4445

45-
public class EmbeddedBrowser {
46+
public class EmbeddedJxBrowser extends EmbeddedBrowser {
4647
private static final Logger LOG = Logger.getInstance(JxBrowserManager.class);
4748

4849
@NotNull
49-
public static EmbeddedBrowser getInstance(@NotNull Project project) {
50-
return Objects.requireNonNull(project.getService(EmbeddedBrowser.class));
50+
public static EmbeddedJxBrowser getInstance(@NotNull Project project) {
51+
return Objects.requireNonNull(project.getService(EmbeddedJxBrowser.class));
5152
}
5253

5354
private Browser browser;
5455
private CompletableFuture<DevToolsUrl> devToolsUrlFuture;
5556

56-
private EmbeddedBrowser(Project project) {
57+
private EmbeddedJxBrowser(Project project) {
5758
System.setProperty("jxbrowser.force.dpi.awareness", "1.0");
5859
System.setProperty("jxbrowser.logging.level", "DEBUG");
5960
System.setProperty("jxbrowser.logging.file", PathManager.getLogPath() + File.separatorChar + "jxbrowser.log");
@@ -99,6 +100,11 @@ public void projectClosing(@NotNull Project project) {
99100
});
100101
}
101102

103+
@Override
104+
public Logger logger() {
105+
return LOG;
106+
}
107+
102108
/**
103109
* This is to clear out a potentially old URL, i.e. a URL from an app that's no longer running.
104110
*/
@@ -180,54 +186,8 @@ public void openPanel(ContentManager contentManager, String tabName, DevToolsUrl
180186
});
181187
}
182188

183-
public void updatePanelToWidget(String widgetId) {
184-
updateUrlAndReload(devToolsUrl -> {
185-
devToolsUrl.widgetId = widgetId;
186-
return devToolsUrl;
187-
});
188-
}
189-
190-
public void updateColor(String newColor) {
191-
updateUrlAndReload(devToolsUrl -> {
192-
if (devToolsUrl.colorHexCode.equals(newColor)) {
193-
return null;
194-
}
195-
devToolsUrl.colorHexCode = newColor;
196-
return devToolsUrl;
197-
});
198-
}
199-
200-
public void updateFontSize(float newFontSize) {
201-
updateUrlAndReload(devToolsUrl -> {
202-
if (devToolsUrl.fontSize.equals(newFontSize)) {
203-
return null;
204-
}
205-
devToolsUrl.fontSize = newFontSize;
206-
return devToolsUrl;
207-
});
208-
}
209-
210-
private void updateUrlAndReload(Function<DevToolsUrl, DevToolsUrl> newDevToolsUrlFn) {
211-
final CompletableFuture<DevToolsUrl> updatedUrlFuture = devToolsUrlFuture.thenApply(devToolsUrl -> {
212-
if (devToolsUrl == null) {
213-
// This happens if URL has already been reset (e.g. new app has started). In this case [openPanel] should be called again instead of
214-
// modifying the URL.
215-
return null;
216-
}
217-
return newDevToolsUrlFn.apply(devToolsUrl);
218-
});
219-
220-
AsyncUtils.whenCompleteUiThread(updatedUrlFuture, (devToolsUrl, ex) -> {
221-
if (ex != null) {
222-
LOG.info(ex);
223-
FlutterInitializer.getAnalytics().sendExpectedException("jxbrowser-update", ex);
224-
return;
225-
}
226-
if (devToolsUrl == null) {
227-
// Reload is no longer needed - either URL has been reset or there has been no change.
228-
return;
229-
}
230-
browser.navigation().loadUrl(devToolsUrl.getUrlString());
231-
});
189+
@Override
190+
protected void navigateToUrl(String url) {
191+
browser.navigation().loadUrl(url);
232192
}
233193
}

flutter-idea/src/io/flutter/jxbrowser/JxBrowserManager.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,16 @@ public void listenForSettingChanges(@NotNull Project project) {
203203
}
204204

205205
public void setUp(@NotNull Project project) {
206+
if (jxBrowserUtils.skipInstallation()) {
207+
status.set(JxBrowserStatus.INSTALLATION_SKIPPED);
208+
return;
209+
}
210+
211+
if (!jxBrowserUtils.skipInstallation() && status.get().equals(JxBrowserStatus.INSTALLATION_SKIPPED)) {
212+
// This check returns status to NOT_INSTALLED so that JxBrowser can be downloaded and installed in cases where it is enabled after being disabled.
213+
status.compareAndSet(JxBrowserStatus.INSTALLATION_SKIPPED, JxBrowserStatus.NOT_INSTALLED);
214+
}
215+
206216
if (!status.compareAndSet(JxBrowserStatus.NOT_INSTALLED, JxBrowserStatus.INSTALLATION_IN_PROGRESS)) {
207217
// This check ensures that an IDE only downloads and installs JxBrowser once, even if multiple projects are open.
208218
// If already in progress, let calling point wait until success or failure (it may make sense to call setUp but proceed).

flutter-idea/src/io/flutter/jxbrowser/JxBrowserStatus.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ public enum JxBrowserStatus {
1010
INSTALLATION_IN_PROGRESS,
1111
INSTALLED,
1212
INSTALLATION_FAILED,
13+
14+
INSTALLATION_SKIPPED,
1315
}

flutter-idea/src/io/flutter/logging/FlutterConsoleLogManager.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,20 @@
3434
import com.intellij.ui.SimpleTextAttributes;
3535
import com.intellij.util.concurrency.QueueProcessor;
3636
import io.flutter.FlutterInitializer;
37+
import io.flutter.FlutterUtils;
3738
import io.flutter.devtools.DevToolsUtils;
3839
import io.flutter.inspector.DiagnosticLevel;
3940
import io.flutter.inspector.DiagnosticsNode;
4041
import io.flutter.inspector.DiagnosticsTreeStyle;
4142
import io.flutter.inspector.InspectorService;
42-
import io.flutter.jxbrowser.EmbeddedBrowser;
43+
import io.flutter.jxbrowser.EmbeddedJxBrowser;
4344
import io.flutter.jxbrowser.JxBrowserManager;
4445
import io.flutter.jxbrowser.JxBrowserStatus;
4546
import io.flutter.run.daemon.FlutterApp;
4647
import io.flutter.sdk.FlutterSdk;
4748
import io.flutter.settings.FlutterSettings;
4849
import io.flutter.utils.JsonUtils;
50+
import io.flutter.view.EmbeddedBrowser;
4951
import io.flutter.view.FlutterView;
5052
import io.flutter.vmService.VmServiceConsumers;
5153
import org.dartlang.vm.service.VmService;
@@ -275,7 +277,7 @@ private void processFlutterErrorEvent(@NotNull DiagnosticsNode diagnosticsNode)
275277
errorSummary = property.getDescription();
276278
} else if (StringUtil.equals("DevToolsDeepLinkProperty", property.getType()) &&
277279
FlutterSettings.getInstance().isEnableEmbeddedBrowsers() &&
278-
JxBrowserManager.getInstance().getStatus().equals(JxBrowserStatus.INSTALLED)) {
280+
FlutterUtils.embeddedBrowserAvailable(JxBrowserManager.getInstance().getStatus())) {
279281
showDeepLinkNotification(property, errorSummary);
280282
continue;
281283
}
@@ -439,7 +441,10 @@ public void actionPerformed(@NotNull AnActionEvent event) {
439441

440442
Project project = app.getProject();
441443
if (!project.isDisposed()) {
442-
EmbeddedBrowser.getInstance(project).updatePanelToWidget(widgetId);
444+
final EmbeddedBrowser browser = FlutterUtils.embeddedBrowser(project);
445+
if (browser != null) {
446+
browser.updatePanelToWidget(widgetId);
447+
}
443448
}
444449

445450
notification.expire();

flutter-idea/src/io/flutter/utils/JxBrowserUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.flutter.utils;
77

88
import com.intellij.openapi.util.SystemInfo;
9+
import io.flutter.settings.FlutterSettings;
910
import org.jetbrains.annotations.NotNull;
1011
// import com.intellij.util.system.CpuArch;
1112

@@ -84,4 +85,9 @@ public String getJxBrowserKey() throws FileNotFoundException {
8485
public boolean licenseIsSet() {
8586
return System.getProperty(JxBrowserUtils.LICENSE_PROPERTY_NAME) != null;
8687
}
88+
89+
90+
public boolean skipInstallation() {
91+
return FlutterSettings.getInstance().isEnableJcefBrowser();
92+
}
8793
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2022 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
package io.flutter.view;
7+
8+
import com.intellij.openapi.diagnostic.Logger;
9+
import com.intellij.ui.content.ContentManager;
10+
import io.flutter.FlutterInitializer;
11+
import io.flutter.devtools.DevToolsUrl;
12+
import io.flutter.jxbrowser.EmbeddedJxBrowser;
13+
import io.flutter.utils.AsyncUtils;
14+
15+
import java.util.concurrent.CompletableFuture;
16+
import java.util.function.Function;
17+
18+
public abstract class EmbeddedBrowser {
19+
protected CompletableFuture<DevToolsUrl> devToolsUrlFuture;
20+
21+
public abstract Logger logger();
22+
public abstract void resetUrl();
23+
24+
public abstract void openPanel(ContentManager contentManager, String tabName, DevToolsUrl devToolsUrl, Runnable onBrowserUnavailable);
25+
26+
public void updatePanelToWidget(String widgetId) {
27+
updateUrlAndReload(devToolsUrl -> {
28+
devToolsUrl.widgetId = widgetId;
29+
return devToolsUrl;
30+
});
31+
}
32+
33+
public void updateColor(String newColor) {
34+
updateUrlAndReload(devToolsUrl -> {
35+
if (devToolsUrl.colorHexCode.equals(newColor)) {
36+
return null;
37+
}
38+
devToolsUrl.colorHexCode = newColor;
39+
return devToolsUrl;
40+
});
41+
}
42+
43+
public void updateFontSize(float newFontSize) {
44+
updateUrlAndReload(devToolsUrl -> {
45+
if (devToolsUrl.fontSize.equals(newFontSize)) {
46+
return null;
47+
}
48+
devToolsUrl.fontSize = newFontSize;
49+
return devToolsUrl;
50+
});
51+
}
52+
53+
private void updateUrlAndReload(Function<DevToolsUrl, DevToolsUrl> newDevToolsUrlFn) {
54+
final CompletableFuture<DevToolsUrl> updatedUrlFuture = devToolsUrlFuture.thenApply(devToolsUrl -> {
55+
if (devToolsUrl == null) {
56+
// This happens if URL has already been reset (e.g. new app has started). In this case [openPanel] should be called again instead of
57+
// modifying the URL.
58+
return null;
59+
}
60+
return newDevToolsUrlFn.apply(devToolsUrl);
61+
});
62+
63+
AsyncUtils.whenCompleteUiThread(updatedUrlFuture, (devToolsUrl, ex) -> {
64+
if (ex != null) {
65+
logger().info(ex);
66+
FlutterInitializer.getAnalytics().sendExpectedException("browser-update", ex);
67+
return;
68+
}
69+
if (devToolsUrl == null) {
70+
// Reload is no longer needed - either URL has been reset or there has been no change.
71+
return;
72+
}
73+
navigateToUrl(devToolsUrl.getUrlString());
74+
});
75+
}
76+
77+
protected abstract void navigateToUrl(String url);
78+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2022 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
package io.flutter.view;
7+
8+
import com.intellij.openapi.components.ServiceManager;
9+
import com.intellij.openapi.diagnostic.Logger;
10+
import com.intellij.openapi.project.Project;
11+
import com.intellij.openapi.wm.ToolWindow;
12+
import com.intellij.ui.content.Content;
13+
import com.intellij.ui.content.ContentManager;
14+
import com.intellij.ui.jcef.JBCefBrowser;
15+
import icons.FlutterIcons;
16+
import io.flutter.FlutterInitializer;
17+
import io.flutter.devtools.DevToolsUrl;
18+
import io.flutter.jxbrowser.JxBrowserManager;
19+
import org.jetbrains.annotations.NotNull;
20+
21+
import java.awt.Dimension;
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.atomic.AtomicBoolean;
24+
25+
public class EmbeddedJcefBrowser extends EmbeddedBrowser {
26+
private static final Logger LOG = Logger.getInstance(JxBrowserManager.class);
27+
28+
private JBCefBrowser browser;
29+
30+
@NotNull
31+
public static EmbeddedJcefBrowser getInstance(Project project) {
32+
return ServiceManager.getService(project, EmbeddedJcefBrowser.class);
33+
}
34+
35+
private EmbeddedJcefBrowser(Project project) {
36+
browser = new JBCefBrowser();
37+
resetUrl();
38+
}
39+
40+
public Logger logger() {
41+
return LOG;
42+
}
43+
44+
public void resetUrl() {
45+
if (devToolsUrlFuture != null && !devToolsUrlFuture.isDone()) {
46+
devToolsUrlFuture.complete(null);
47+
}
48+
this.devToolsUrlFuture = new CompletableFuture<>();
49+
50+
}
51+
52+
public void openPanel(ContentManager contentManager, String tabName, DevToolsUrl devToolsUrl, Runnable onBrowserUnavailable) {
53+
// If the browser failed to start during setup, run unavailable callback.
54+
if (browser == null) {
55+
onBrowserUnavailable.run();
56+
return;
57+
}
58+
59+
60+
// Multiple LoadFinished events can occur, but we only need to add content the first time.
61+
final AtomicBoolean contentLoaded = new AtomicBoolean(false);
62+
63+
try {
64+
browser.loadURL(devToolsUrl.getUrlString());
65+
} catch (Exception ex) {
66+
devToolsUrlFuture.completeExceptionally(ex);
67+
onBrowserUnavailable.run();
68+
LOG.info(ex);
69+
FlutterInitializer.getAnalytics().sendExpectedException("jcef-load", ex);
70+
return;
71+
}
72+
73+
devToolsUrlFuture.complete(devToolsUrl);
74+
75+
if (contentManager.isDisposed()) {
76+
return;
77+
}
78+
79+
contentManager.removeAllContents(false);
80+
final Content content = contentManager.getFactory().createContent(null, tabName, false);
81+
browser.getComponent().setPreferredSize(new Dimension(contentManager.getComponent().getWidth(), contentManager.getComponent().getHeight()));
82+
content.setComponent(browser.getComponent());
83+
content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
84+
// TODO(helin24): Use differentiated icons for each tab and copy from devtools toolbar.
85+
content.setIcon(FlutterIcons.Phone);
86+
contentManager.addContent(content);
87+
}
88+
89+
public void navigateToUrl(String url) {
90+
browser.loadURL(url);
91+
}
92+
93+
}

0 commit comments

Comments
 (0)