Skip to content

Commit

Permalink
Added async impl
Browse files Browse the repository at this point in the history
  • Loading branch information
amartya4256 committed Aug 29, 2024
1 parent eab7b45 commit 69bbd42
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ public WebViewEnvironment(ICoreWebView2Environment environment) {

private static Map<Display, WebViewEnvironment> webViewEnvironments = new HashMap<>();

ICoreWebView2 webView;
ICoreWebView2_2 webView_2;
CompletableFuture<ICoreWebView2> lastWebViewTask = new CompletableFuture<>();
CompletableFuture<ICoreWebView2_2> lastWebView_2Task = new CompletableFuture<>();
ICoreWebView2Controller controller;
ICoreWebView2Settings settings;
ICoreWebView2Environment2 environment2;
Expand Down Expand Up @@ -310,12 +310,13 @@ static ICoreWebView2CookieManager getCookieManager() {
SWT.error(SWT.ERROR_NOT_IMPLEMENTED, null, " [WebView2: cookie access requires a Browser instance]");
}
Edge instance = environmentWrapper.instances().get(0);
if (instance.webView_2 == null) {
if (instance.lastWebView_2Task == null) {
SWT.error(SWT.ERROR_NOT_IMPLEMENTED, null, " [WebView2 version 88+ is required to access cookies]");
}

long[] ppv = new long[1];
int hr = instance.webView_2.get_CookieManager(ppv);
instance.waitForWebviewInitialization();
int hr = instance.lastWebView_2Task.join().get_CookieManager(ppv);
if (hr != COM.S_OK) error(SWT.ERROR_NO_HANDLES, hr);
return new ICoreWebView2CookieManager(ppv[0]);
}
Expand All @@ -325,15 +326,11 @@ 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 (leadsToDeadlock()) {
if (inCallback || inNewWindow) {
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 @@ -401,18 +398,26 @@ WebViewEnvironment createEnvironment() {
public void create(Composite parent, int style) {
containingEnvironment = createEnvironment();

final CompletableFuture<ICoreWebView2> firstWebViewTask = lastWebViewTask;
final CompletableFuture<ICoreWebView2_2> firstWebView_2Task = lastWebView_2Task;
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());
containingEnvironment.environment().CreateCoreWebView2Controller(browser.handle,
newCallback((result, pv) -> {
if ((int)result == COM.S_OK) {
ppv[0] = pv;
new IUnknown(pv).AddRef();
}
setupBrowser((int)result, ppv, firstWebViewTask, firstWebView_2Task);
return COM.S_OK;
}));

addProgressListener(ProgressListener.completedAdapter(__ -> writeToDefaultPathDOM()));
}

void setupBrowser() {
int hr;
long[] ppv = new long[1];
hr = callAndWait(ppv, completion -> containingEnvironment.environment().CreateCoreWebView2Controller(browser.handle, completion));
void setupBrowser(int hr, long[] ppv, CompletableFuture<ICoreWebView2> firstWebViewTask, CompletableFuture<ICoreWebView2_2> firstWebView_2Task) {
if(browser.isDisposed()) {
browserDispose(new Event());
return;
Expand All @@ -429,37 +434,38 @@ void setupBrowser() {
controller = new ICoreWebView2Controller(ppv[0]);

controller.get_CoreWebView2(ppv);
webView = new ICoreWebView2(ppv[0]);
webView.get_Settings(ppv);
firstWebViewTask.complete(new ICoreWebView2(ppv[0]));
final ICoreWebView2 wv = firstWebViewTask.join();
wv.get_Settings(ppv);
settings = new ICoreWebView2Settings(ppv[0]);
hr = webView.QueryInterface(COM.IID_ICoreWebView2_2, ppv);
if (hr == COM.S_OK) webView_2 = new ICoreWebView2_2(ppv[0]);
hr = wv.QueryInterface(COM.IID_ICoreWebView2_2, ppv);
if (hr == COM.S_OK) firstWebView_2Task.complete(new ICoreWebView2_2(ppv[0]));

long[] token = new long[1];
IUnknown handler;
handler = newCallback(this::handleCloseRequested);
webView.add_WindowCloseRequested(handler, token);
wv.add_WindowCloseRequested(handler, token);
handler.Release();
handler = newCallback(this::handleNavigationStarting);
webView.add_NavigationStarting(handler, token);
wv.add_NavigationStarting(handler, token);
handler.Release();
handler = newCallback(this::handleFrameNavigationStarting);
webView.add_FrameNavigationStarting(handler, token);
wv.add_FrameNavigationStarting(handler, token);
handler.Release();
handler = newCallback(this::handleNavigationCompleted);
webView.add_NavigationCompleted(handler, token);
wv.add_NavigationCompleted(handler, token);
handler.Release();
handler = newCallback(this::handleFrameNavigationCompleted);
webView.add_FrameNavigationCompleted(handler, token);
wv.add_FrameNavigationCompleted(handler, token);
handler.Release();
handler = newCallback(this::handleDocumentTitleChanged);
webView.add_DocumentTitleChanged(handler, token);
wv.add_DocumentTitleChanged(handler, token);
handler.Release();
handler = newCallback(this::handleNewWindowRequested);
webView.add_NewWindowRequested(handler, token);
wv.add_NewWindowRequested(handler, token);
handler.Release();
handler = newCallback(this::handleSourceChanged);
webView.add_SourceChanged(handler, token);
wv.add_SourceChanged(handler, token);
handler.Release();
handler = newCallback(this::handleMoveFocusRequested);
controller.add_MoveFocusRequested(handler, token);
Expand All @@ -470,17 +476,15 @@ void setupBrowser() {
handler = newCallback(this::handleAcceleratorKeyPressed);
controller.add_AcceleratorKeyPressed(handler, token);
handler.Release();
if (webView_2 != null) {
if (firstWebView_2Task.isDone()) {
handler = newCallback(this::handleDOMContentLoaded);
webView_2.add_DOMContentLoaded(handler, token);
firstWebView_2Task.join().add_DOMContentLoaded(handler, token);
handler.Release();
}

addProgressListener(ProgressListener.completedAdapter(__ -> writeToDefaultPathDOM()));

IUnknown hostDisp = newHostObject(this::handleCallJava);
long[] hostObj = { COM.VT_DISPATCH, hostDisp.getAddress(), 0 }; // VARIANT
webView.AddHostObjectToScript("swt\0".toCharArray(), hostObj);
wv.AddHostObjectToScript("swt\0".toCharArray(), hostObj);
hostDisp.Release();

browser.addListener(SWT.Dispose, this::browserDispose);
Expand All @@ -497,14 +501,16 @@ void browserDispose(Event event) {
containingEnvironment.instances.remove(this);

// Check for null before releasing
if (webView_2 != null) webView_2.Release();
if (environment2 != null) environment2.Release();
if (settings != null) settings.Release();
if (webView != null) webView.Release();
webView_2 = null;
lastWebViewTask.thenAcceptAsync(it -> {
it.Release();
if (lastWebView_2Task.isDone()) lastWebView_2Task.join().Release();
});
lastWebView_2Task = null;
environment2 = null;
settings = null;
webView = null;
lastWebViewTask = null;

if(controller != null) {
// Bug in WebView2. Closing the controller from an event handler results
Expand Down Expand Up @@ -543,14 +549,14 @@ void browserResize(Event event) {
@Override
public Object evaluate(String script) throws SWTException {
checkDeadlock();

waitForWebviewInitialization();
// Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting.
// Disallow programmatic execution manually.
if (!jsEnabled) return null;

String script2 = "(function() {try { " + script + " } catch (e) { return '" + ERROR_ID + "' + e.message; } })();\0";
String[] pJson = new String[1];
int hr = callAndWait(pJson, completion -> webView.ExecuteScript(script2.toCharArray(), completion));
int hr = callAndWait(pJson, completion -> lastWebViewTask.join().ExecuteScript(script2.toCharArray(), completion));
if (hr != COM.S_OK) error(SWT.ERROR_FAILED_EVALUATE, hr);

Object data = JSON.parse(pJson[0]);
Expand All @@ -566,11 +572,17 @@ public boolean execute(String script) {
// Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting.
// Disallow programmatic execution manually.
if (!jsEnabled) return false;

waitForWebviewInitialization();
IUnknown completion = newCallback((long result, long json) -> COM.S_OK);
int hr = webView.ExecuteScript(stringToWstr(script), completion);
lastWebViewTask.join().ExecuteScript(stringToWstr(script), completion);
completion.Release();
return hr == COM.S_OK;
return true;
}

private void waitForWebviewInitialization() {
while(!lastWebViewTask.isDone() && !lastWebView_2Task.isDone()) {
processNextOSMessage();
}
}

@Override
Expand All @@ -595,8 +607,9 @@ public String getText() {

@Override
public String getUrl() {
waitForWebviewInitialization();
long ppsz[] = new long[1];
webView.get_Source(ppsz);
lastWebViewTask.join().get_Source(ppsz);
return wstrToString(ppsz[0], true);
}

Expand All @@ -617,7 +630,7 @@ int handleCloseRequested(long pView, long pArgs) {

int handleDocumentTitleChanged(long pView, long pArgs) {
long[] ppsz = new long[1];
webView.get_DocumentTitle(ppsz);
lastWebViewTask.join().get_DocumentTitle(ppsz);
String title = wstrToString(ppsz[0], true);
browser.getDisplay().asyncExec(() -> {
if (browser.isDisposed()) return;
Expand Down Expand Up @@ -690,6 +703,7 @@ int handleNavigationStarting(long pView, long pArgs, boolean top) {
execute(sb.toString());
}
} else {
this.html = null;
args.put_Cancel(true);
}
return COM.S_OK;
Expand All @@ -702,7 +716,7 @@ int handleSourceChanged(long pView, long pArgs) {
// is the same between navigations, SourceChanged isn't fired.
// TODO: emit missing location changed events
long[] ppsz = new long[1];
int hr = webView.get_Source(ppsz);
int hr = lastWebViewTask.join().get_Source(ppsz);
if (hr != COM.S_OK) return hr;
String url = wstrToString(ppsz[0], true);
browser.getDisplay().asyncExec(() -> {
Expand Down Expand Up @@ -757,7 +771,7 @@ int handleNavigationCompleted(long pView, long pArgs, boolean top) {
long[] pNavId = new long[1];
args.get_NavigationId(pNavId);
LocationEvent startEvent = navigations.remove(pNavId[0]);
if (webView_2 == null && startEvent != null && startEvent.top) {
if (lastWebView_2Task == null && startEvent != null && startEvent.top) {
// If DOMContentLoaded isn't available, fire
// ProgressListener.completed from here.
sendProgressCompleted();
Expand Down Expand Up @@ -814,8 +828,9 @@ int handleNewWindowRequested(long pView, long pArgs) {
if (openEvent.browser != null && !openEvent.browser.isDisposed()) {
WebBrowser other = openEvent.browser.webBrowser;
args.put_Handled(true);
if (other instanceof Edge) {
args.put_NewWindow(((Edge)other).webView.getAddress());
if (other instanceof Edge edge) {
edge.waitForWebviewInitialization();
args.put_NewWindow(edge.lastWebViewTask.join().getAddress());

// Send show event to the other browser.
WindowEvent showEvent = new WindowEvent (other.browser);
Expand Down Expand Up @@ -921,37 +936,39 @@ int handleMoveFocusRequested(long pView, long pArgs) {
@Override
public boolean isBackEnabled() {
int[] pval = new int[1];
webView.get_CanGoBack(pval);
waitForWebviewInitialization();
lastWebViewTask.join().get_CanGoBack(pval);
return pval[0] != 0;
}

@Override
public boolean isForwardEnabled() {
int[] pval = new int[1];
webView.get_CanGoForward(pval);
waitForWebviewInitialization();
lastWebViewTask.join().get_CanGoForward(pval);
return pval[0] != 0;
}

@Override
public boolean back() {
// Feature in WebView2. GoBack returns S_OK even when CanGoBack is FALSE.
return isBackEnabled() && webView.GoBack() == COM.S_OK;
return isBackEnabled() && lastWebViewTask.join().GoBack() == COM.S_OK;
}

@Override
public boolean forward() {
// Feature in WebView2. GoForward returns S_OK even when CanGoForward is FALSE.
return isForwardEnabled() && webView.GoForward() == COM.S_OK;
return isForwardEnabled() && lastWebViewTask.join().GoForward() == COM.S_OK;
}

@Override
public void refresh() {
webView.Reload();
lastWebViewTask = acceptAsyncAndRunSync(lastWebViewTask, ICoreWebView2::Reload);
}

@Override
public void stop() {
webView.Stop();
lastWebViewTask = acceptAsyncAndRunSync(lastWebViewTask, ICoreWebView2::Stop);
}

@Override
Expand All @@ -975,17 +992,6 @@ 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 All @@ -995,7 +1001,7 @@ private boolean setWebpageData(String url, String postData, String[] headers, St
char[] pszUrl = stringToWstr(url);
this.html = html;
if (postData != null || headers != null) {
if (environment2 == null || webView_2 == null) {
if (environment2 == null || lastWebView_2Task == null) {
SWT.error(SWT.ERROR_NOT_IMPLEMENTED, null, " [WebView2 version 88+ is required to set postData and headers]");
}
long[] ppRequest = new long[1];
Expand All @@ -1020,23 +1026,25 @@ private boolean setWebpageData(String url, String postData, String[] headers, St
if (stream != null) stream.Release();
if (hr != COM.S_OK) error(SWT.ERROR_NO_HANDLES, hr);
IUnknown request = new IUnknown(ppRequest[0]);
hr = webView_2.NavigateWithWebResourceRequest(request);
request.Release();
lastWebView_2Task.thenAcceptAsync(wv -> browser.getDisplay().syncExec(() -> {
wv.NavigateWithWebResourceRequest(request);
request.Release();
browserResize(new Event());
}));
} else {
hr = webView.Navigate(pszUrl);
lastWebViewTask = acceptAsyncAndRunSync(lastWebViewTask, wv -> wv.Navigate(pszUrl));
}
return hr == COM.S_OK;
return true;
}

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;
private <T extends ICoreWebView2> CompletableFuture<T> acceptAsyncAndRunSync(CompletableFuture<T> webViewTask, Consumer<T> action) {
return webViewTask.thenApply(wv -> {
// browser.getDisplay().syncExec(() -> {
action.accept(wv);
browserResize(new Event());
// });
return wv;
});
}

@Override
Expand Down
Loading

0 comments on commit 69bbd42

Please sign in to comment.