From: <rb...@us...> - 2017-12-23 14:35:34
|
Revision: 15038 http://sourceforge.net/p/htmlunit/code/15038 Author: rbri Date: 2017-12-23 14:35:32 +0000 (Sat, 23 Dec 2017) Log Message: ----------- fix: NPE thrown if JS "location.href = 'xyz'" called inside Promise.then() Issue 1941 Modified Paths: -------------- trunk/htmlunit/src/changes/changes.xml trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/Promise.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/Location2Test.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/PromiseTest.java Modified: trunk/htmlunit/src/changes/changes.xml =================================================================== --- trunk/htmlunit/src/changes/changes.xml 2017-12-22 17:42:23 UTC (rev 15037) +++ trunk/htmlunit/src/changes/changes.xml 2017-12-23 14:35:32 UTC (rev 15038) @@ -8,6 +8,10 @@ <body> <release version="2.29" date="xx, 2017" description="Bugfixes, Chrome 63, WebStart support"> + <action type="fix" dev="rbri" issue="1941 "> + JavaScript: Promise has to provide the same context setup as the rest + and call processPostponedActions afterwards + </action> <action type="fix" dev="rbri" issue="1939 "> If the coords of an image map entry are not valid ignore this entry. </action> Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/Promise.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/Promise.java 2017-12-22 17:42:23 UTC (rev 15037) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/Promise.java 2017-12-23 14:35:32 UTC (rev 15038) @@ -14,6 +14,7 @@ */ package com.gargoylesoftware.htmlunit.javascript.host; +import static com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine.KEY_STARTING_SCOPE; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF; @@ -20,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Stack; import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable; import com.gargoylesoftware.htmlunit.javascript.background.BasicJavaScriptJob; @@ -110,8 +112,24 @@ } }; + final Context cx = Context.getCurrentContext(); try { - fun.call(Context.getCurrentContext(), window, window, new Object[] {resolve, reject}); + // KEY_STARTING_SCOPE maintains a stack of scopes + @SuppressWarnings("unchecked") + Stack<Scriptable> stack = (Stack<Scriptable>) cx.getThreadLocal(KEY_STARTING_SCOPE); + if (null == stack) { + stack = new Stack<>(); + cx.putThreadLocal(KEY_STARTING_SCOPE, stack); + } + stack.push(window); + try { + fun.call(cx, window, window, new Object[] {resolve, reject}); + } + finally { + stack.pop(); + } + + window.getWebWindow().getWebClient().getJavaScriptEngine().processPostponedActions(); } catch (final JavaScriptException e) { thisPromise.settle(false, e.getValue(), window); @@ -331,7 +349,7 @@ @Override public void run() { - Context.enter(); + final Context cx = Context.enter(); try { Function toExecute = null; if (thisPromise.state_ == PromiseState.FULFILLED && onFulfilled instanceof Function) { @@ -350,8 +368,22 @@ callbackResult = dummy; } else { - callbackResult = toExecute.call(Context.getCurrentContext(), - window, thisPromise, new Object[] {value_}); + // KEY_STARTING_SCOPE maintains a stack of scopes + @SuppressWarnings("unchecked") + Stack<Scriptable> stack = (Stack<Scriptable>) cx.getThreadLocal(KEY_STARTING_SCOPE); + if (null == stack) { + stack = new Stack<>(); + cx.putThreadLocal(KEY_STARTING_SCOPE, stack); + } + stack.push(window); + try { + callbackResult = toExecute.call(cx, window, thisPromise, new Object[] {value_}); + } + finally { + stack.pop(); + } + + window.getWebWindow().getWebClient().getJavaScriptEngine().processPostponedActions(); } if (callbackResult instanceof Promise) { final Promise resultPromise = (Promise) callbackResult; Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/Location2Test.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/Location2Test.java 2017-12-22 17:42:23 UTC (rev 15037) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/Location2Test.java 2017-12-23 14:35:32 UTC (rev 15038) @@ -795,36 +795,39 @@ final String html = "<html><head><title>Frameset</title></head>\n" + "<frameset rows='20%,80%'>\n" + " <frame src='menu.html' name='menu'>\n" - + " <frame src='' name='content'>\n" + + " <frame src='content.html' name='content'>\n" + "</frameset></html>"; final String menu = "<html><head><title>Menu</title></head>\n" + "<body>\n" - + " <a id='link' href='content.html' target='content'>Link</a>\n" - + " <a id='jsLink' href='#' onclick=\"javascript:top.content.location='content.html';\">jsLink</a>\n" + + " <a id='link' href='newContent.html' target='content'>Link</a>\n" + + " <a id='jsLink' href='#' " + + "onclick=\"javascript:top.content.location='newContent.html';\">jsLink</a>\n" + "</body></html>"; final String content = "<html><head><title>Content</title></head><body><p>content</p></body></html>"; + final String newContent = "<html><head><title>New Content</title></head><body><p>new content</p></body></html>"; final MockWebConnection conn = getMockWebConnection(); conn.setResponse(new URL(URL_FIRST, "menu.html"), menu); conn.setResponse(new URL(URL_FIRST, "content.html"), content); + conn.setResponse(new URL(URL_FIRST, "newContent.html"), newContent); expandExpectedAlertsVariables(URL_FIRST); final WebDriver driver = loadPage2(html); - assertEquals(2, conn.getRequestCount()); + assertEquals(3, conn.getRequestCount()); // click an anchor with href and target driver.switchTo().frame(0); driver.findElement(By.id("link")).click(); - assertEquals(3, conn.getRequestCount()); + assertEquals(4, conn.getRequestCount()); Map<String, String> lastAdditionalHeaders = conn.getLastAdditionalHeaders(); assertEquals(getExpectedAlerts()[0], lastAdditionalHeaders.get(HttpHeader.REFERER)); // click an anchor with onclick which sets frame.location driver.findElement(By.id("jsLink")).click(); - assertEquals(4, conn.getRequestCount()); + assertEquals(5, conn.getRequestCount()); lastAdditionalHeaders = conn.getLastAdditionalHeaders(); assertEquals(getExpectedAlerts()[0], lastAdditionalHeaders.get(HttpHeader.REFERER)); } Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/PromiseTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/PromiseTest.java 2017-12-22 17:42:23 UTC (rev 15037) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/PromiseTest.java 2017-12-23 14:35:32 UTC (rev 15038) @@ -1600,4 +1600,35 @@ final String text = driver.findElement(By.id("log")).getAttribute("value").trim().replaceAll("\r", ""); assertEquals(String.join("\n", getExpectedAlerts()), text); } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "", + IE = "test") + public void changeLocationFromPromise() throws Exception { + final String html = + "<html>\n" + + "<head>\n" + + " <title>test</title>\n" + + " <script>\n" + + " function test() {\n" + + " if (window.Promise) {\n" + + " Promise.resolve(1).then(function () {\n" + + " location.href = 'about:blank';\n" + + " });\n" + + " }\n" + + " }\n" + + " </script>\n" + + "</head>\n" + + "<body onload='test()'>\n" + + " <textarea id='log' cols='80' rows='40'></textarea>\n" + + "</body>\n" + + "</html>"; + + final WebDriver driver = loadPage2(html); + Thread.sleep(200); + assertEquals(getExpectedAlerts()[0], driver.getTitle()); + } } |