| /******************************************************************************* |
| * Copyright (c) 2020 Nikita Nemkin and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Nikita Nemkin <nikita@nemkin.ru> - initial implementation |
| *******************************************************************************/ |
| package org.eclipse.swt.browser; |
| |
| import java.nio.charset.*; |
| import java.util.*; |
| |
| import org.eclipse.swt.*; |
| import org.eclipse.swt.graphics.*; |
| import org.eclipse.swt.internal.*; |
| import org.eclipse.swt.internal.ole.win32.*; |
| import org.eclipse.swt.internal.win32.*; |
| import org.eclipse.swt.widgets.*; |
| |
| class Edge extends WebBrowser { |
| static { |
| Library.loadLibrary("WebView2Loader", false); |
| } |
| |
| // WebView2Loader.dll compatible version. This is NOT the minimal required version. |
| static final String SDK_TARGET_VERSION = "89.0.721.0"; |
| |
| // Display.getData() keys |
| static final String APPLOCAL_DIR_KEY = "org.eclipse.swt.internal.win32.appLocalDir"; |
| |
| // System.getProperty() keys |
| static final String BROWSER_DIR_PROP = "org.eclipse.swt.browser.EdgeDir"; |
| static final String BROWSER_ARGS_PROP = "org.eclipse.swt.browser.EdgeArgs"; |
| static final String DATA_DIR_PROP = "org.eclipse.swt.browser.EdgeDataDir"; |
| static final String LANGUAGE_PROP = "org.eclipse.swt.browser.EdgeLanguage"; |
| static final String VERSIONT_PROP = "org.eclipse.swt.browser.EdgeVersion"; |
| |
| static String DataDir; |
| static ICoreWebView2Environment Environment; |
| |
| ICoreWebView2 webView; |
| ICoreWebView2_2 webView2; |
| ICoreWebView2Controller controller; |
| ICoreWebView2Settings settings; |
| ICoreWebView2Environment2 environment2; |
| |
| static boolean inCallback; |
| boolean inNewWindow; |
| HashMap<Long, LocationEvent> navigations = new HashMap<>(); |
| |
| static String wstrToString(long psz, boolean free) { |
| if (psz == 0) return ""; |
| int len = OS.wcslen(psz); |
| char[] data = new char[len]; |
| OS.MoveMemory(data, psz, len * Character.BYTES); |
| if (free) OS.CoTaskMemFree(psz); |
| return String.valueOf(data); |
| } |
| |
| static String bstrToString(long bstr) { |
| if (bstr == 0) return ""; |
| int len = COM.SysStringLen(bstr); |
| char[] data = new char[len]; |
| OS.MoveMemory(data, bstr, len * Character.BYTES); |
| return String.valueOf(data); |
| } |
| |
| static void error(int code, int hr) { |
| SWT.error(code, null, String.format(" [0x%08x]", hr)); |
| } |
| |
| static IUnknown newCallback(ICoreWebView2SwtCallback handler) { |
| long punk = COM.CreateSwtWebView2Callback((arg0, arg1) -> { |
| inCallback = true; |
| try { |
| return handler.Invoke(arg0, arg1); |
| } finally { |
| inCallback = false; |
| } |
| }); |
| if (punk == 0) error(SWT.ERROR_NO_HANDLES, COM.E_OUTOFMEMORY); |
| return new IUnknown(punk); |
| } |
| |
| IUnknown newHostObject(ICoreWebView2SwtHost handler) { |
| long pdisp = COM.CreateSwtWebView2Host(handler); |
| if (pdisp == 0) error(SWT.ERROR_NO_HANDLES, COM.E_OUTOFMEMORY); |
| return new IUnknown(pdisp); |
| } |
| |
| void checkDeadlock() { |
| // Feature in WebView2. All event handlers, completion handlers |
| // 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) { |
| SWT.error(SWT.ERROR_FAILED_EVALUATE, null, " [WebView2: deadlock detected]"); |
| } |
| } |
| |
| ICoreWebView2Environment createEnvironment() { |
| if (Environment != null) return Environment; |
| Display display = Display.getCurrent(); |
| |
| // Gather customization properties |
| String browserDir = System.getProperty(BROWSER_DIR_PROP); |
| String dataDir = System.getProperty(DATA_DIR_PROP); |
| String browserArgs = System.getProperty(BROWSER_ARGS_PROP); |
| String language = System.getProperty(LANGUAGE_PROP); |
| if (dataDir == null) { |
| // WebView2 will append "\\EBWebView" |
| dataDir = (String)display.getData(APPLOCAL_DIR_KEY); |
| } |
| |
| // Initialize options |
| long pOpts = COM.CreateSwtWebView2Options(); |
| if (pOpts == 0) error(SWT.ERROR_NO_HANDLES, COM.E_OUTOFMEMORY); |
| ICoreWebView2EnvironmentOptions options = new ICoreWebView2EnvironmentOptions(pOpts); |
| char[] pVersion = (SDK_TARGET_VERSION + "\0").toCharArray(); |
| options.put_TargetCompatibleBrowserVersion(pVersion); |
| if (browserArgs != null) { |
| char[] pBrowserArgs = (browserArgs + "\0").toCharArray(); |
| options.put_AdditionalBrowserArguments(pBrowserArgs); |
| } |
| if (language != null) { |
| char[] pLanguage = (language + "\0").toCharArray(); |
| options.put_Language(pLanguage); |
| } |
| |
| // Create the environment |
| char[] pBrowserDir = (browserDir != null) ? (browserDir + "\0").toCharArray() : null; |
| char[] pDataDir = (dataDir + "\0").toCharArray(); |
| int[] pResult = {COM.S_OK}; |
| long[] ppEnv = new long[1]; |
| IUnknown completion = newCallback((result, pEnv) -> { |
| pResult[0] = (int)result; |
| if ((int)result == COM.S_OK) { |
| ppEnv[0] = pEnv; |
| new IUnknown(pEnv).AddRef(); |
| } |
| return COM.S_OK; |
| }); |
| pResult[0] = COM.CreateCoreWebView2EnvironmentWithOptions( |
| pBrowserDir, pDataDir, options.getAddress(), completion.getAddress()); |
| completion.Release(); |
| options.Release(); |
| if (pResult[0] == OS.HRESULT_FROM_WIN32(OS.ERROR_FILE_NOT_FOUND)) { |
| SWT.error(SWT.ERROR_NOT_IMPLEMENTED, null, " [WebView2 runtime not found]"); |
| } |
| while (pResult[0] == COM.S_OK && ppEnv[0] == 0) { |
| if (!display.readAndDispatch()) display.sleep(); |
| } |
| if (pResult[0] != COM.S_OK) error(SWT.ERROR_NO_HANDLES, pResult[0]); |
| Environment = new ICoreWebView2Environment(ppEnv[0]); |
| DataDir = dataDir; |
| |
| // Save Edge version for reporting |
| long[] ppVersion = new long[1]; |
| Environment.get_BrowserVersionString(ppVersion); |
| String version = wstrToString(ppVersion[0], true); |
| System.setProperty(VERSIONT_PROP, version); |
| |
| // Destroy the environment on app exit. |
| display.disposeExec(() -> { |
| Environment.Release(); |
| Environment = null; |
| }); |
| return Environment; |
| } |
| |
| @Override |
| public void create(Composite parent, int style) { |
| checkDeadlock(); |
| ICoreWebView2Environment environment = createEnvironment(); |
| |
| int[] pResult = {COM.S_OK}; |
| long[] ppv = new long[1], ppHost = new long[1]; |
| int hr = environment.QueryInterface(COM.IID_ICoreWebView2Environment2, ppv); |
| if (hr == COM.S_OK) environment2 = new ICoreWebView2Environment2(ppv[0]); |
| |
| IUnknown completion = newCallback((result, pHost) -> { |
| pResult[0] = (int)result; |
| if ((int)result == COM.S_OK) { |
| ppHost[0] = pHost; |
| new IUnknown(pHost).AddRef(); |
| } |
| return COM.S_OK; |
| }); |
| pResult[0] = environment.CreateCoreWebView2Controller(browser.handle, completion); |
| completion.Release(); |
| Display display = browser.getDisplay(); |
| while (pResult[0] == COM.S_OK && ppHost[0] == 0) { |
| if (!display.readAndDispatch()) display.sleep(); |
| } |
| if (pResult[0] != COM.S_OK) error(SWT.ERROR_NO_HANDLES, pResult[0]); |
| controller = new ICoreWebView2Controller(ppHost[0]); |
| |
| controller.get_CoreWebView2(ppv); |
| webView = new ICoreWebView2(ppv[0]); |
| webView.get_Settings(ppv); |
| settings = new ICoreWebView2Settings(ppv[0]); |
| hr = webView.QueryInterface(COM.IID_ICoreWebView2_2, ppv); |
| if (hr == COM.S_OK) webView2 = new ICoreWebView2_2(ppv[0]); |
| |
| long[] token = new long[1]; |
| IUnknown handler; |
| handler = newCallback(this::handleCloseRequested); |
| webView.add_WindowCloseRequested(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleNavigationStarting); |
| webView.add_NavigationStarting(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleFrameNavigationStarting); |
| webView.add_FrameNavigationStarting(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleNavigationCompleted); |
| webView.add_NavigationCompleted(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleFrameNavigationCompleted); |
| webView.add_FrameNavigationCompleted(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleDocumentTitleChanged); |
| webView.add_DocumentTitleChanged(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleNewWindowRequested); |
| webView.add_NewWindowRequested(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleSourceChanged); |
| webView.add_SourceChanged(handler, token); |
| handler.Release(); |
| handler = newCallback(this::handleMoveFocusRequested); |
| controller.add_MoveFocusRequested(handler, token); |
| handler.Release(); |
| if (webView2 != null) { |
| handler = newCallback(this::handleDOMContentLoaded); |
| webView2.add_DOMContentLoaded(handler, token); |
| handler.Release(); |
| } |
| |
| IUnknown hostDisp = newHostObject(this::handleCallJava); |
| long[] hostObj = { COM.VT_DISPATCH, hostDisp.getAddress(), 0 }; // VARIANT |
| webView.AddHostObjectToScript("swt\0".toCharArray(), hostObj); |
| hostDisp.Release(); |
| |
| browser.addListener(SWT.Dispose, this::browserDispose); |
| browser.addListener(SWT.FocusIn, this::browserFocusIn); |
| browser.addListener(SWT.Resize, this::browserResize); |
| browser.addListener(SWT.Move, this::browserMove); |
| } |
| |
| void browserDispose(Event event) { |
| if (webView2 != null) webView2.Release(); |
| if (environment2 != null) environment2.Release(); |
| settings.Release(); |
| webView.Release(); |
| webView2 = 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(); |
| } |
| controller = null; |
| } |
| |
| void browserFocusIn(Event event) { |
| // TODO: directional traversals |
| controller.MoveFocus(COM.COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); |
| } |
| |
| void browserMove(Event event) { |
| controller.NotifyParentWindowPositionChanged(); |
| } |
| |
| void browserResize(Event event) { |
| RECT rect = new RECT(); |
| OS.GetClientRect(browser.handle, rect); |
| controller.put_Bounds(rect); |
| controller.put_IsVisible(true); |
| } |
| |
| @Override |
| public Object evaluate(String script) throws SWTException { |
| checkDeadlock(); |
| |
| // Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting. |
| // Disallow programmatic execution manually. |
| if (!jsEnabled) return null; |
| |
| int[] pResult = new int[1]; |
| String[] pJson = new String[1]; |
| IUnknown completion = newCallback((result, pszJson) -> { |
| pResult[0] = (int)result; |
| if ((int)result == COM.S_OK) { |
| pJson[0] = wstrToString(pszJson, false); |
| } |
| return COM.S_OK; |
| }); |
| script = "(function() {try { " + script + " } catch (e) { return '" + ERROR_ID + "' + e.message; } })();\0"; |
| pResult[0] = webView.ExecuteScript(script.toCharArray(), completion); |
| completion.Release(); |
| Display display = browser.getDisplay(); |
| while (pResult[0] == COM.S_OK && pJson[0] == null) { |
| if (!display.readAndDispatch()) display.sleep(); |
| } |
| if (pResult[0] != COM.S_OK) error(SWT.ERROR_FAILED_EVALUATE, pResult[0]); |
| Object data = JSON.parse(pJson[0]); |
| if (data instanceof String && ((String) data).startsWith(ERROR_ID)) { |
| String errorMessage = ((String) data).substring(ERROR_ID.length()); |
| throw new SWTException (SWT.ERROR_FAILED_EVALUATE, errorMessage); |
| } |
| return data; |
| } |
| |
| @Override |
| public boolean execute(String script) { |
| // Feature in WebView2. ExecuteScript works regardless of IsScriptEnabled setting. |
| // Disallow programmatic execution manually. |
| if (!jsEnabled) return false; |
| |
| IUnknown completion = newCallback((long result, long json) -> COM.S_OK); |
| int hr = webView.ExecuteScript((script + "\0").toCharArray(), completion); |
| completion.Release(); |
| return hr == COM.S_OK; |
| } |
| |
| @Override |
| public String getBrowserType() { |
| return "edge"; |
| } |
| |
| @Override |
| String getJavaCallDeclaration() { |
| return "if (!window.callJava) { window.callJava = function(index, token, args) {\n" |
| +"return JSON.parse(window.chrome.webview.hostObjects.sync.swt.CallJava(index, token, JSON.stringify(args)));\n" |
| +"}};\n"; |
| } |
| |
| @Override |
| public String getText() { |
| return (String)evaluate("return document.documentElement.outerHTML;"); |
| } |
| |
| @Override |
| public String getUrl() { |
| long ppsz[] = new long[1]; |
| webView.get_Source(ppsz); |
| return wstrToString(ppsz[0], true); |
| } |
| |
| int handleCloseRequested(long pView, long pArgs) { |
| browser.getDisplay().asyncExec(() -> { |
| if (browser.isDisposed()) return; |
| WindowEvent event = new WindowEvent(browser); |
| event.display = browser.getDisplay(); |
| event.widget = browser; |
| for (CloseWindowListener listener : closeWindowListeners) { |
| listener.close(event); |
| if (browser.isDisposed()) return; |
| } |
| browser.dispose(); |
| }); |
| return COM.S_OK; |
| } |
| |
| int handleDocumentTitleChanged(long pView, long pArgs) { |
| long[] ppsz = new long[1]; |
| webView.get_DocumentTitle(ppsz); |
| String title = wstrToString(ppsz[0], true); |
| browser.getDisplay().asyncExec(() -> { |
| if (browser.isDisposed()) return; |
| TitleEvent event = new TitleEvent(browser); |
| event.display = browser.getDisplay(); |
| event.widget = browser; |
| event.title = title; |
| for (TitleListener listener : titleListeners) { |
| listener.changed(event); |
| if (browser.isDisposed()) return; |
| } |
| }); |
| return COM.S_OK; |
| } |
| |
| long handleCallJava(int index, long bstrToken, long bstrArgsJson) { |
| Object result = null; |
| String token = bstrToString(bstrToken); |
| BrowserFunction function = functions.get(index); |
| if (function != null && token.equals (function.token)) { |
| try { |
| String argsJson = bstrToString(bstrArgsJson); |
| Object args = (Object[]) JSON.parse(argsJson.toCharArray()); |
| result = function.function ((Object[]) args); |
| } catch (Throwable e) { |
| result = WebBrowser.CreateErrorString(e.getLocalizedMessage()); |
| } |
| } |
| String json = JSON.stringify(result); |
| return COM.SysAllocStringLen(json.toCharArray(), json.length()); |
| } |
| |
| int handleFrameNavigationStarting(long pView, long pArgs) { |
| return handleNavigationStarting(pView, pArgs, false); |
| } |
| |
| int handleNavigationStarting(long pView, long pArgs) { |
| return handleNavigationStarting(pView, pArgs, true); |
| } |
| |
| int handleNavigationStarting(long pView, long pArgs, boolean top) { |
| ICoreWebView2NavigationStartingEventArgs args = new ICoreWebView2NavigationStartingEventArgs(pArgs); |
| long[] ppszUrl = new long[1]; |
| int hr = args.get_Uri(ppszUrl); |
| if (hr != COM.S_OK) return hr; |
| String url = wstrToString(ppszUrl[0], true); |
| long[] pNavId = new long[1]; |
| args.get_NavigationId(pNavId); |
| LocationEvent event = new LocationEvent(browser); |
| event.display = browser.getDisplay(); |
| event.widget = browser; |
| event.location = url; |
| event.top = top; |
| event.doit = true; |
| for (LocationListener listener : locationListeners) { |
| listener.changing(event); |
| if (browser.isDisposed()) return COM.S_OK; |
| } |
| if (event.doit) { |
| // Save location and top for all events that use navigationId. |
| navigations.put(pNavId[0], event); |
| jsEnabled = jsEnabledOnNextPage; |
| settings.put_IsScriptEnabled(jsEnabled); |
| // Register browser functions in the new document. |
| if (!functions.isEmpty()) { |
| StringBuilder sb = new StringBuilder(); |
| for (BrowserFunction function : functions.values()) { |
| sb.append(function.functionString); |
| } |
| execute(sb.toString()); |
| } |
| } else { |
| args.put_Cancel(true); |
| } |
| return COM.S_OK; |
| } |
| |
| int handleSourceChanged(long pView, long pArgs) { |
| // Feature in WebView2. Navigations to data URIs set the Source |
| // to an empty string. Navigations with NavigateToString set the Source |
| // to about:blank. Initial Source is about:blank. If Source value |
| // 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); |
| if (hr != COM.S_OK) return hr; |
| String url = wstrToString(ppsz[0], true); |
| browser.getDisplay().asyncExec(() -> { |
| if (browser.isDisposed()) return; |
| LocationEvent event = new LocationEvent(browser); |
| event.display = browser.getDisplay(); |
| event.widget = browser; |
| event.location = url; |
| event.top = true; |
| for (LocationListener listener : locationListeners) { |
| listener.changed(event); |
| if (browser.isDisposed()) return; |
| } |
| }); |
| return COM.S_OK; |
| } |
| |
| void sendProgressCompleted() { |
| browser.getDisplay().asyncExec(() -> { |
| if (browser.isDisposed()) return; |
| ProgressEvent event = new ProgressEvent(browser); |
| event.display = browser.getDisplay(); |
| event.widget = browser; |
| for (ProgressListener listener : progressListeners) { |
| listener.completed(event); |
| if (browser.isDisposed()) return; |
| } |
| }); |
| } |
| |
| int handleDOMContentLoaded(long pView, long pArgs) { |
| ICoreWebView2DOMContentLoadedEventArgs args = new ICoreWebView2DOMContentLoadedEventArgs(pArgs); |
| long[] pNavId = new long[1]; |
| args.get_NavigationId(pNavId); |
| LocationEvent startEvent = navigations.get(pNavId[0]); |
| if (startEvent != null && startEvent.top) { |
| sendProgressCompleted(); |
| } |
| return COM.S_OK; |
| } |
| |
| int handleNavigationCompleted(long pView, long pArgs) { |
| return handleNavigationCompleted(pView, pArgs, true); |
| } |
| |
| int handleFrameNavigationCompleted(long pView, long pArgs) { |
| return handleNavigationCompleted(pView, pArgs, false); |
| } |
| |
| int handleNavigationCompleted(long pView, long pArgs, boolean top) { |
| ICoreWebView2NavigationCompletedEventArgs args = new ICoreWebView2NavigationCompletedEventArgs(pArgs); |
| long[] pNavId = new long[1]; |
| args.get_NavigationId(pNavId); |
| LocationEvent startEvent = navigations.remove(pNavId[0]); |
| if (webView2 == null && startEvent != null && startEvent.top) { |
| // If DOMContentLoaded isn't available, fire |
| // ProgressListener.completed from here. |
| sendProgressCompleted(); |
| } |
| return COM.S_OK; |
| } |
| |
| void updateWindowFeatures(ICoreWebView2NewWindowRequestedEventArgs args, WindowEvent event) { |
| long[] ppv = new long[1]; |
| int hr = args.get_WindowFeatures(ppv); |
| if (hr != COM.S_OK) return; |
| ICoreWebView2WindowFeatures features = new ICoreWebView2WindowFeatures(ppv[0]); |
| |
| int[] px = new int[1], py = new int[1]; |
| features.get_HasPosition(px); |
| if (px[0] != 0) { |
| features.get_Left(px); |
| features.get_Top(py); |
| event.location = new Point(px[0], py[0]); |
| } |
| features.get_HasSize(px); |
| if (px[0] != 0) { |
| features.get_Width(px); |
| features.get_Height(py); |
| event.size = new Point(px[0], py[0]); |
| } |
| // event.addressBar = ??? |
| features.get_ShouldDisplayMenuBar(px); |
| event.menuBar = px[0] != 0; |
| features.get_ShouldDisplayStatus(px); |
| event.statusBar = px[0] != 0; |
| features.get_ShouldDisplayToolbar(px); |
| event.toolBar = px[0] != 0; |
| } |
| |
| int handleNewWindowRequested(long pView, long pArgs) { |
| ICoreWebView2NewWindowRequestedEventArgs args = new ICoreWebView2NewWindowRequestedEventArgs(pArgs); |
| args.AddRef(); |
| long[] ppv = new long[1]; |
| args.GetDeferral(ppv); |
| ICoreWebView2Deferral deferral = new ICoreWebView2Deferral(ppv[0]); |
| inNewWindow = true; |
| browser.getDisplay().asyncExec(() -> { |
| try { |
| if (browser.isDisposed()) return; |
| WindowEvent openEvent = new WindowEvent(browser); |
| openEvent.display = browser.getDisplay(); |
| openEvent.widget = browser; |
| openEvent.required = false; |
| for (OpenWindowListener openListener : openWindowListeners) { |
| openListener.open(openEvent); |
| if (browser.isDisposed()) return; |
| } |
| 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()); |
| |
| // Send show event to the other browser. |
| WindowEvent showEvent = new WindowEvent (other.browser); |
| showEvent.display = browser.getDisplay(); |
| showEvent.widget = other.browser; |
| updateWindowFeatures(args, showEvent); |
| for (VisibilityWindowListener showListener : other.visibilityWindowListeners) { |
| showListener.show(showEvent); |
| if (other.browser.isDisposed()) return; |
| } |
| } |
| } else if (openEvent.required) { |
| args.put_Handled(true); |
| } |
| } finally { |
| deferral.Complete(); |
| deferral.Release(); |
| args.Release(); |
| inNewWindow = false; |
| } |
| }); |
| return COM.S_OK; |
| } |
| |
| int handleMoveFocusRequested(long pView, long pArgs) { |
| ICoreWebView2MoveFocusRequestedEventArgs args = new ICoreWebView2MoveFocusRequestedEventArgs(pArgs); |
| int[] pReason = new int[1]; |
| args.get_Reason(pReason); |
| args.put_Handled(true); |
| switch (pReason[0]) { |
| case COM.COREWEBVIEW2_MOVE_FOCUS_REASON_NEXT: |
| browser.traverse(SWT.TRAVERSE_TAB_NEXT); |
| break; |
| case COM.COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS: |
| browser.traverse(SWT.TRAVERSE_TAB_PREVIOUS); |
| break; |
| } |
| return COM.S_OK; |
| } |
| |
| @Override |
| public boolean isBackEnabled() { |
| int[] pval = new int[1]; |
| webView.get_CanGoBack(pval); |
| return pval[0] != 0; |
| } |
| |
| @Override |
| public boolean isForwardEnabled() { |
| int[] pval = new int[1]; |
| webView.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; |
| } |
| |
| @Override |
| public boolean forward() { |
| // Feature in WebView2. GoForward returns S_OK even when CanGoForward is FALSE. |
| return isForwardEnabled() && webView.GoForward() == COM.S_OK; |
| } |
| |
| @Override |
| public void refresh() { |
| webView.Reload(); |
| } |
| |
| @Override |
| public void stop() { |
| webView.Stop(); |
| } |
| |
| @Override |
| public boolean setText(String html, boolean trusted) { |
| char[] data = new char[html.length() + 1]; |
| html.getChars(0, html.length(), data, 0); |
| return webView.NavigateToString(data) == COM.S_OK; |
| } |
| |
| @Override |
| public boolean setUrl(String url, String postData, String[] headers) { |
| // 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+.-]*:.*")) { |
| url = "http://" + url; |
| } |
| int hr; |
| char[] pszUrl = (url + "\0").toCharArray(); |
| if (postData != null || headers != null) { |
| if (environment2 == null || webView2 == null) { |
| SWT.error(SWT.ERROR_NOT_IMPLEMENTED, null, " [WebView2 version 88+ is required to set postData and headers]"); |
| } |
| long[] ppRequest = new long[1]; |
| char[] pszMethod = null; |
| char[] pszHeaders = null; |
| IStream stream = null; |
| if (postData != null) { |
| pszMethod = "POST\0".toCharArray(); |
| byte[] postDataBytes = postData.getBytes(StandardCharsets.UTF_8); |
| long pStream = COM.SHCreateMemStream(postDataBytes, postData.length()); |
| if (pStream == 0) error(SWT.ERROR_NO_HANDLES, COM.E_OUTOFMEMORY); |
| stream = new IStream(pStream); |
| } else { |
| pszMethod = "GET\0".toCharArray(); |
| } |
| if (headers != null) { |
| String hblock = String.join("\r\n", Arrays.asList(headers)); |
| pszHeaders = new char[hblock.length() + 1]; |
| hblock.getChars(0, hblock.length(), pszHeaders, 0); |
| } |
| hr = environment2.CreateWebResourceRequest(pszUrl, pszMethod, stream, pszHeaders, ppRequest); |
| if (stream != null) stream.Release(); |
| if (hr != COM.S_OK) error(SWT.ERROR_NO_HANDLES, hr); |
| IUnknown request = new IUnknown(ppRequest[0]); |
| hr = webView2.NavigateWithWebResourceRequest(request); |
| request.Release(); |
| } else { |
| hr = webView.Navigate(pszUrl); |
| } |
| return hr == COM.S_OK; |
| } |
| |
| } |