Skip to content

Commit

Permalink
Fix Edge Browser Deadlock
Browse files Browse the repository at this point in the history
This contribution fixes Edge browser Deadlock issue on instantiating a
new Edge Browser object during a webview callback using async execution.

contributes to #669
  • Loading branch information
amartya4256 committed Aug 22, 2024
1 parent b4a5141 commit eab7b45
Showing 1 changed file with 60 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.nio.file.Path;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;

import org.eclipse.swt.*;
Expand Down Expand Up @@ -84,8 +85,8 @@ public WebViewEnvironment(ICoreWebView2Environment environment) {
static boolean inCallback;
boolean inNewWindow;
HashMap<Long, LocationEvent> navigations = new HashMap<>();

private String html;
private CompletableFuture<Boolean> initializationProcessFinished = new CompletableFuture<>();

static {
NativeClearSessions = () -> {
Expand Down Expand Up @@ -324,11 +325,15 @@ void checkDeadlock() {
// and JavaScript callbacks are serialized. An event handler waiting
// for a completion of another handler will deadlock. Detect this
// situation and throw an exception instead.
if (inCallback || inNewWindow) {
if (leadsToDeadlock()) {
SWT.error(SWT.ERROR_FAILED_EVALUATE, null, " [WebView2: deadlock detected]");
}
}

boolean leadsToDeadlock() {
return inCallback || inNewWindow;
}

WebViewEnvironment createEnvironment() {
Display display = Display.getCurrent();
WebViewEnvironment existingEnvironment = webViewEnvironments.get(display);
Expand Down Expand Up @@ -394,14 +399,24 @@ WebViewEnvironment createEnvironment() {

@Override
public void create(Composite parent, int style) {
checkDeadlock();
containingEnvironment = createEnvironment();

long[] ppv = new long[1];
int hr = containingEnvironment.environment().QueryInterface(COM.IID_ICoreWebView2Environment2, ppv);
if (hr == COM.S_OK) environment2 = new ICoreWebView2Environment2(ppv[0]);
// If leads to deadlock then execute asynchronously and move on.
// The webview calls are queued to be executed when it is done executing the current task.
browser.getDisplay().asyncExec(() -> setupBrowser());
}

void setupBrowser() {
int hr;
long[] ppv = new long[1];
hr = callAndWait(ppv, completion -> containingEnvironment.environment().CreateCoreWebView2Controller(browser.handle, completion));
if(browser.isDisposed()) {
browserDispose(new Event());
return;
}
switch (hr) {
case COM.S_OK:
break;
Expand Down Expand Up @@ -474,34 +489,39 @@ public void create(Composite parent, int style) {
browser.addListener(SWT.Move, this::browserMove);

containingEnvironment.instances().add(this);
initializationProcessFinished.complete(true);
}

void browserDispose(Event event) {
initializationProcessFinished.cancel(true);
containingEnvironment.instances.remove(this);

// Check for null before releasing
if (webView_2 != null) webView_2.Release();
if (environment2 != null) environment2.Release();
settings.Release();
webView.Release();
if (settings != null) settings.Release();
if (webView != null) webView.Release();
webView_2 = null;
environment2 = null;
settings = null;
webView = null;

// Bug in WebView2. Closing the controller from an event handler results
// in a crash. The fix is to delay the closure with asyncExec.
if (inCallback) {
ICoreWebView2Controller controller1 = controller;
controller.put_IsVisible(false);
browser.getDisplay().asyncExec(() -> {
controller1.Close();
controller1.Release();
});
} else {
controller.Close();
controller.Release();
if(controller != null) {
// Bug in WebView2. Closing the controller from an event handler results
// in a crash. The fix is to delay the closure with asyncExec.
if (inCallback) {
ICoreWebView2Controller controller1 = controller;
controller.put_IsVisible(false);
browser.getDisplay().asyncExec(() -> {
controller1.Close();
controller1.Release();
});
} else {
controller.Close();
controller.Release();
}
controller = null;
}
controller = null;
}

void browserFocusIn(Event event) {
Expand Down Expand Up @@ -955,6 +975,17 @@ public boolean setText(String html, boolean trusted) {
}

private boolean setWebpageData(String url, String postData, String[] headers, String html) {
// If the create() method hasn't finished executing, queue the call to wait for the browser to finish initializing
if (!initializationProcessFinished.isDone()) {
final String fUrl = url;
browser.getDisplay().asyncExec(() -> {
if (waitForInitialization()) {
setWebpageData(fUrl, postData, headers, html);
browserResize(new Event());
}
});
return true;
}
// Feature in WebView2. Partial URLs like "www.example.com" are not accepted.
// Prepend the protocol if it's missing.
if (!url.matches("[a-z][a-z0-9+.-]*:.*")) {
Expand Down Expand Up @@ -997,6 +1028,17 @@ private boolean setWebpageData(String url, String postData, String[] headers, St
return hr == COM.S_OK;
}

private boolean waitForInitialization() {
try {
initializationProcessFinished.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
SWT.error(SWT.ERROR_FAILED_EXEC, e);
} catch (CancellationException e) {
return false;
}
return true;
}

@Override
public boolean setUrl(String url, String postData, String[] headers) {
return setWebpageData(url, postData, headers, null);
Expand Down

0 comments on commit eab7b45

Please sign in to comment.