From 0b067c93e195d05caaeffaa51ee768c08af70938 Mon Sep 17 00:00:00 2001 From: Amartya Parijat Date: Fri, 16 Aug 2024 16:16:15 +0200 Subject: [PATCH] Handle local resource references in Edge#setText() This contribution allows Edge browser for win32 to serve webpages using setText method where the set html may refer to local resources. about:blank doesn't allow referencing local resources and hence, this contribution uses the URL to a temporary file to navigate to and serve the html allowing references to local resources. The temporary URL is not exposed to the clients and the APIs mimic about:blank in all the places where this URL is used to keep the consistency. contributes to #213 --- .../win32/org/eclipse/swt/browser/Edge.java | 69 ++++++++++++++++--- .../Test_org_eclipse_swt_browser_Browser.java | 32 +++------ 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java b/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java index 65e4bc17047..e42de6eeaea 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java @@ -13,8 +13,11 @@ *******************************************************************************/ package org.eclipse.swt.browser; +import java.io.*; import java.net.*; import java.nio.charset.*; +import java.nio.file.*; +import java.nio.file.Path; import java.time.*; import java.util.*; import java.util.function.*; @@ -43,6 +46,13 @@ class Edge extends WebBrowser { 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"; + /** + * The URI_FOR_CUSTOM_TEXT_PAGE is the path to a temporary html file which is used + * by Edge browser to navigate to for setting html content in the + * DOM of the browser to enable it to load local resources. + */ + private static final URI URI_FOR_CUSTOM_TEXT_PAGE = setupAndGetLocationForCustomTextPage(); + private static final String ABOUT_BLANK = "about:blank"; private record WebViewEnvironment(ICoreWebView2Environment environment, ArrayList instances) { public WebViewEnvironment(ICoreWebView2Environment environment) { @@ -66,6 +76,8 @@ public WebViewEnvironment(ICoreWebView2Environment environment) { HashMap navigations = new HashMap<>(); private boolean ignoreFocus; + private String lastCustomText; + static { NativeClearSessions = () -> { ICoreWebView2CookieManager manager = getCookieManager(); @@ -173,6 +185,18 @@ public WebViewEnvironment(ICoreWebView2Environment environment) { }; } +private static URI setupAndGetLocationForCustomTextPage() { + URI absolutePath; + try { + Path tempFile = Files.createTempFile("base", ".html"); + absolutePath = tempFile.toUri(); + tempFile.toFile().deleteOnExit(); + } catch (IOException e) { + absolutePath = URI.create(ABOUT_BLANK); + } + return absolutePath; +} + static String wstrToString(long psz, boolean free) { if (psz == 0) return ""; int len = OS.wcslen(psz); @@ -447,6 +471,8 @@ public void create(Composite parent, int style) { 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); @@ -559,7 +585,11 @@ public String getText() { public String getUrl() { long ppsz[] = new long[1]; webView.get_Source(ppsz); - return wstrToString(ppsz[0], true); + return getExposedUrl(wstrToString(ppsz[0], true)); +} + +private String getExposedUrl(String url) { + return isLocationForCustomText(url) ? ABOUT_BLANK : url; } int handleCloseRequested(long pView, long pArgs) { @@ -625,7 +655,7 @@ int handleNavigationStarting(long pView, long pArgs, boolean top) { long[] ppszUrl = new long[1]; int hr = args.get_Uri(ppszUrl); if (hr != COM.S_OK) return hr; - String url = wstrToString(ppszUrl[0], true); + String url = getExposedUrl(wstrToString(ppszUrl[0], true)); long[] pNavId = new long[1]; args.get_NavigationId(pNavId); LocationEvent event = new LocationEvent(browser); @@ -666,7 +696,7 @@ int handleSourceChanged(long pView, long pArgs) { long[] ppsz = new long[1]; int hr = webView.get_Source(ppsz); if (hr != COM.S_OK) return hr; - String url = wstrToString(ppsz[0], true); + String url = getExposedUrl(wstrToString(ppsz[0], true)); browser.getDisplay().asyncExec(() -> { if (browser.isDisposed()) return; LocationEvent event = new LocationEvent(browser); @@ -954,15 +984,30 @@ public void stop() { webView.Stop(); } +private boolean isLocationForCustomText(String location) { + try { + return URI_FOR_CUSTOM_TEXT_PAGE.equals(new URI(location)); + } catch (URISyntaxException e) { + return false; + } +} + +private void writeToDefaultPathDOM() { + if(lastCustomText != null && getUrl().equals(ABOUT_BLANK)) { + boolean test = jsEnabled; + jsEnabled = true; + execute("document.open(); document.write(`" + lastCustomText + "`); document.close();"); + jsEnabled = test; + this.lastCustomText = null; + } +} + @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; + return setWebpageData(URI_FOR_CUSTOM_TEXT_PAGE.toASCIIString(), null, null, html); } -@Override -public boolean setUrl(String url, String postData, String[] headers) { +private boolean setWebpageData(String url, String postData, String[] headers, String html) { // 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+.-]*:.*")) { @@ -970,6 +1015,9 @@ public boolean setUrl(String url, String postData, String[] headers) { } int hr; char[] pszUrl = stringToWstr(url); + if(isLocationForCustomText(url)) { + this.lastCustomText = html; + } if (postData != null || headers != null) { if (environment2 == null || webView_2 == null) { SWT.error(SWT.ERROR_NOT_IMPLEMENTED, null, " [WebView2 version 88+ is required to set postData and headers]"); @@ -1004,4 +1052,9 @@ public boolean setUrl(String url, String postData, String[] headers) { return hr == COM.S_OK; } +@Override +public boolean setUrl(String url, String postData, String[] headers) { + return setWebpageData(url, postData, headers, null); +} + } diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java index 38f936db14f..451f2a08889 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_browser_Browser.java @@ -527,7 +527,6 @@ public void test_isLocationForCustomText_setUrlAfterDisposedThrowsSwtException() @Test public void test_isLocationForCustomText_isSetUrlNotCustomTextUrlAfterSetText() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); // Edge doesn't fire a changed event if the URL is the same as the previous location. String url = getValidUrl(); AtomicBoolean locationChanged = new AtomicBoolean(false); browser.addLocationListener(changedAdapter(event -> { @@ -544,7 +543,6 @@ public void test_isLocationForCustomText_isSetUrlNotCustomTextUrlAfterSetText() @Test public void test_isLocationForCustomText_isFirstSetTextURLStillCustomTextUrlAfterSetUrl() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); // Edge doesn't fire a changed event if the URL is the same as the previous location. AtomicBoolean locationChanged = new AtomicBoolean(false); browser.addLocationListener(changedAdapter(event -> locationChanged.set(true))); String url = getValidUrl(); @@ -582,7 +580,6 @@ public void test_isLocationForCustomText_isSetUrlNotCustomTextUrl() { @Test public void test_isLocationForCustomText() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); // Edge doesn't fire a changed event if the URL is the same as the previous location. AtomicBoolean locationChanged = new AtomicBoolean(false); browser.addLocationListener(changedAdapter(e -> locationChanged.set(true))); browser.setText("Hello world"); @@ -601,7 +598,6 @@ public void test_LocationListener_changing() { } @Test public void test_LocationListener_changed() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); AtomicBoolean changedFired = new AtomicBoolean(false); browser.addLocationListener(changedAdapter(e -> changedFired.set(true))); @@ -612,8 +608,6 @@ public void test_LocationListener_changed() { } @Test public void test_LocationListener_changingAndOnlyThenChanged() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); - // Test proper order of events. // Check that 'changed' is only fired after 'changing' has fired at least once. AtomicBoolean changingFired = new AtomicBoolean(false); @@ -657,8 +651,6 @@ else if (!changingFired.get()) @Test public void test_LocationListener_then_ProgressListener() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); - AtomicBoolean locationChanged = new AtomicBoolean(false); AtomicBoolean progressChanged = new AtomicBoolean(false); AtomicBoolean progressChangedAfterLocationChanged = new AtomicBoolean(false); @@ -752,8 +744,6 @@ public void changed(LocationEvent event) { @Test /** Ensue that only one changed and one completed event are fired for url changes */ public void test_LocationListener_ProgressListener_noExtraEvents() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); - AtomicInteger changedCount = new AtomicInteger(0); AtomicInteger completedCount = new AtomicInteger(0); @@ -860,7 +850,6 @@ public void test_OpenWindowListener_open_ChildPopup() { /** Validate event order : Child's visibility should come before progress completed event */ @Test public void test_OpenWindow_Progress_Listener_ValidateEventOrder() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); AtomicBoolean windowOpenFired = new AtomicBoolean(false); AtomicBoolean childCompleted = new AtomicBoolean(false); @@ -1105,7 +1094,6 @@ public void test_TitleListener_event() { assertTrue(errMsg, passed); } - @Test public void test_setText() { String expectedTitle = "Website Title"; @@ -1199,16 +1187,16 @@ private void validateTitleChanged(String expectedTitle, Runnable browserSetFunc) browserSetFunc.run(); shell.open(); - boolean hasFinished = waitForPassCondition(() -> actualTitle.get().length() != 0 - && !actualTitle.get().contains("about:blank")); // Windows sometimes does 2 loads, one "about:blank", and one actual load. - boolean passed = hasFinished && actualTitle.get().equals(expectedTitle); + boolean passed = waitForPassCondition(() -> actualTitle.get().equals(expectedTitle)); String errMsg = ""; - if (!hasFinished) - errMsg = "Test timed out. TitleListener not fired"; - else if (!actualTitle.get().equals(expectedTitle)) { - errMsg = "\nExpected title and actual title do not match." - + "\nExpected: " + expectedTitle - + "\nActual: " + actualTitle; + if (!passed) { + if (actualTitle.get().length() == 0) { + errMsg = "Test timed out. TitleListener not fired"; + } else { + errMsg = "\nExpected title and actual title do not match." + + "\nExpected: " + expectedTitle + + "\nActual: " + actualTitle; + } } assertTrue(errMsg + testLog.toString(), passed); } @@ -1328,8 +1316,6 @@ public void completed(ProgressEvent event) { */ @Test public void test_VisibilityWindowListener_eventSize() { - assumeFalse("behavior is not (yet) supported by Edge browser", isEdge); - shell.setSize(200,300); AtomicBoolean childCompleted = new AtomicBoolean(false); AtomicReference result = new AtomicReference<>(new Point(0,0));