From: <mgu...@us...> - 2013-01-24 08:06:58
|
Revision: 8031 http://sourceforge.net/p/htmlunit/code/8031 Author: mguillem Date: 2013-01-24 08:06:51 +0000 (Thu, 24 Jan 2013) Log Message: ----------- JavaScript: place document before window in scope chain for event handlers defined in HTML attributes. Fixed many incorrect unit tests that were calling document.open and not window.open as awaited. Issue 898 Modified Paths: -------------- trunk/htmlunit/src/changes/changes.xml trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlSubmitInput.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventHandler.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventListenersContainer.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLElement.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlSelectTest.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/EventTest.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/WindowTest.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElement2Test.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElementTest.java Modified: trunk/htmlunit/src/changes/changes.xml =================================================================== --- trunk/htmlunit/src/changes/changes.xml 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/changes/changes.xml 2013-01-24 08:06:51 UTC (rev 8031) @@ -8,6 +8,9 @@ <body> <release version="2.12" date="???" description="Bugfixes, CSS3 Selectors"> + <action type="fix" dev="mguillem" issue="898"> + JavaScript: place document before window in scope chain for event handlers defined in HTML attributes. + </action> <action type="add" dev="mguillem"> JavaScript: added basic support for SVGAngle and SVGMatrix. </action> Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlSubmitInput.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlSubmitInput.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlSubmitInput.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -20,6 +20,7 @@ import java.io.PrintWriter; import java.util.Map; +import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.SgmlPage; import com.gargoylesoftware.htmlunit.util.NameValuePair; import com.gargoylesoftware.htmlunit.util.StringUtils; @@ -53,11 +54,31 @@ */ HtmlSubmitInput(final String namespaceURI, final String qualifiedName, final SgmlPage page, final Map<String, DomAttr> attributes) { - super(namespaceURI, qualifiedName, page, attributes); - if (hasFeature(SUBMITINPUT_DEFAULT_VALUE_IF_VALUE_NOT_DEFINED) - && !hasAttribute("value")) { - setAttribute("value", DEFAULT_VALUE); + super(namespaceURI, qualifiedName, page, addValueIfNeeded(page, attributes)); + } + + /** + * Add missing attribute if needed by fixing attribute map rather to add it afterwards as this second option + * triggers the instantiation of the script object at a time where the DOM node has not yet been added to its + * parent. + */ + private static Map<String, DomAttr> addValueIfNeeded(final SgmlPage page, + final Map<String, DomAttr> attributes) { + + final BrowserVersion browserVersion = page.getWebClient().getBrowserVersion(); + if (browserVersion.hasFeature(SUBMITINPUT_DEFAULT_VALUE_IF_VALUE_NOT_DEFINED)) { + for (final String key : attributes.keySet()) { + if ("value".equalsIgnoreCase(key)) { + return attributes; // value attribute was specified + } + } + + // value attribute was not specified, add it + final DomAttr newAttr = new DomAttr(page, null, "value", DEFAULT_VALUE, true); + attributes.put("value", newAttr); } + + return attributes; } /** Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventHandler.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventHandler.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventHandler.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -75,6 +75,7 @@ if (realFunction_ == null) { realFunction_ = cx.compileFunction(jsObj, jsSnippet_, eventName_ + " event for " + node_ + " in " + node_.getPage().getUrl(), 0, null); + realFunction_.setParentScope(jsObj); } final Object result = realFunction_.call(cx, scope, thisObj, args); Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventListenersContainer.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventListenersContainer.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/EventListenersContainer.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -205,7 +205,8 @@ if (LOG.isDebugEnabled()) { LOG.debug("Executing " + event.getType() + " handler for " + node); } - return page.executeJavaScriptFunctionIfPossible(handler, jsNode_, propHandlerArgs, node); + return page.executeJavaScriptFunctionIfPossible(handler, jsNode_, + propHandlerArgs, page); } return null; } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLElement.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLElement.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLElement.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -386,6 +386,7 @@ @Override public void setDomNode(final DomNode domNode) { super.setDomNode(domNode); + setParentScope(getWindow().getDocument()); /** * Convert JavaScript snippets defined in the attribute map to executable event handlers. Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlSelectTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlSelectTest.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlSelectTest.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -627,7 +627,7 @@ final String htmlContent = "<html><head><title>foo</title></head><body>\n" + "<form id='form1'>\n" - + "<select name='select1' id='select1' onchange='open(\"about:blank\", \"_blank\")'>\n" + + "<select name='select1' id='select1' onchange='window.open(\"about:blank\", \"_blank\")'>\n" + " <option id='option1'>Option1</option>\n" + " <option id='option2' selected>Number Two</option>\n" + "</select>\n" Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/EventTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/EventTest.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/EventTest.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -22,6 +22,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -272,7 +273,7 @@ */ @Test @Alerts("frame1") - public void testEventScope() throws Exception { + public void thisInEventHandler() throws Exception { final String html = "<html><head></head>\n" + "<body>\n" @@ -534,12 +535,11 @@ /** * Regression test for bug - * <a href="http://sourceforge.net/tracker/?func=detail&aid=2851920&group_id=47038&atid=448266">2851920</a>. + * <a href="http://sourceforge.net/p/htmlunit/bugs/898/">898</a>. * Name resolution doesn't work the same in inline handlers than in "normal" JS code! * @throws Exception if the test fails */ @Test - @NotYetImplemented @Alerts(FF = { "form1 -> custom", "form2 -> [object HTMLFormElement]", "form1: [object HTMLFormElement]", "form2: [object HTMLFormElement]", "form1 -> custom", "form2 -> [object HTMLFormElement]" }, @@ -670,4 +670,122 @@ final String text = addedValue.trim().replaceAll("\r", ""); assertEquals(StringUtils.join(getExpectedAlerts(), "\n"), text); } + + /** + * Test that the parent scope of the event handler defined in HTML attributes is "document". + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = { "2from window", "1from document" }, + IE = { "1from document", "3from window" }) + public void eventHandlersParentScope() throws Exception { + final String html = "<html><body>\n" + + "<button name='button1' id='button1' onclick='alert(1 + foo)'>click me</button>\n" + + "<script>\n" + + " if (window.addEventListener) {\n" + + " window.addEventListener('click', function() { alert(2 + foo); }, true);\n" + + " }\n" + + " else if (window.attachEvent) {\n" + + " window.attachEvent('onclick', function() { alert(3 + foo); });\n" + + " }\n" + + "document.foo = 'from document';\n" + + "var foo = 'from window';\n" + + "</script>\n" + + "</body></html>"; + + final WebDriver driver = loadPage2(html); + driver.findElement(By.id("button1")).click(); + assertEquals(getExpectedAlerts(), getCollectedAlerts(driver)); + } + + /** + * Test that the parent scopes chain for an event handler. + * @throws Exception if the test fails + */ + @Test + @Alerts({ "from theField", "from theForm", "from document", "from window" }) + public void eventHandlersParentScopeChain_formFields() throws Exception { + eventHandlersParentScopeChain("<button", "</button>"); + eventHandlersParentScopeChain("<input type='text'", ""); + eventHandlersParentScopeChain("<input type='submit' value='xxx'", ""); + // case without value attribute was failing first with IE due to the way the value attribute was added + eventHandlersParentScopeChain("<input type='submit'", ""); + } + + /** + * Test that the parent scopes chain for an event handler. + * @throws Exception if the test fails + */ + @Test + @Alerts({ "from theField", "from document", "from document", "from window" }) + public void eventHandlersParentScopeChain_span() throws Exception { + eventHandlersParentScopeChain("<span", "</span>"); + } + + private void eventHandlersParentScopeChain(final String startTag, final String endTag) throws Exception { + final String html = "<html><body id='body'>\n" + + "<form id='theForm'>\n" + + "<div id='theDiv'>\n" + + startTag + " id='theField' onclick='alert(foo); return false;'>click me" + endTag + "\n" + + "</div>\n" + + "</form>\n" + + "<script>\n" + + "var foo = 'from window';\n" + + "document.foo = 'from document';\n" + + "document.body.foo = 'from body';\n" + + "document.getElementById('theForm').foo = 'from theForm';\n" + + "document.getElementById('theDiv').foo = 'from theDiv';\n" + + "document.getElementById('theField').foo = 'from theField';\n" + + "</script>\n" + + "</body></html>"; + + final WebDriver driver = loadPage2(html); + final WebElement field = driver.findElement(By.id("theField")); + field.click(); + + final JavascriptExecutor jsExecutor = (JavascriptExecutor) driver; + + // remove property on field + jsExecutor.executeScript("delete document.getElementById('theField').foo"); + field.click(); + + // remove property on form + jsExecutor.executeScript("delete document.getElementById('theForm').foo"); + field.click(); + + // remove property on document + jsExecutor.executeScript("delete document.foo"); + field.click(); + + assertEquals(getExpectedAlerts(), getCollectedAlerts(driver)); + } + + /** + * Test that the function open resolves to document.open within a handler defined by an attribute. + * This was wrong (even in unit tests) up to HtmlUnit-2.12. + * @throws Exception if the test fails + */ + @Test + @Alerts("from document") + public void eventHandlers_functionOpen() throws Exception { + final String html = "<html><body>\n" + + "<button id='button1' onclick='identify(open)'>click me</button>\n" + + "<script>\n" + + "function identify(fnOpen) {\n" + + " var origin = 'unknown';\n" + + " if (fnOpen === window.open) {\n" + + " origin = 'from window';\n" + + " }\n" + + " else if (fnOpen === document.open) {\n" + + " origin = 'from document';\n" + + " }\n" + + " alert(origin);\n" + + "}\n" + + "</script>\n" + + "</body></html>"; + + final WebDriver driver = loadPage2(html); + driver.findElement(By.id("button1")).click(); + assertEquals(getExpectedAlerts(), getCollectedAlerts(driver)); + } } Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/WindowTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/WindowTest.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/WindowTest.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -121,7 +121,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"MyNewWindow\").focus(); " + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"MyNewWindow\").focus(); " + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; @@ -226,7 +226,7 @@ final String secondContent = "<html><head><title>Second</title></head><body>\n" + " <a id='link' " - + "onClick='open(\"" + URL_THIRD + "\", \"_blank\").focus(); '>\n" + + "onClick='window.open(\"" + URL_THIRD + "\", \"_blank\").focus(); '>\n" + "Click me</a>\n" + "</body></html>"; final String thirdContent @@ -285,7 +285,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"_self\"); " + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"_self\"); " + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; @@ -336,7 +336,7 @@ + "</body></html>"; final String thirdContent = "<html><head><title>Third</title></head><body>\n" - + " <a id='link' onClick='open(\"http://fourth\", \"_top\"); " + + " <a id='link' onClick='window.open(\"http://fourth\", \"_top\"); " + "return false;'>Click me</a>\n" + "</body></html>"; final String fourthContent = "<html><head><title>Fourth</title></head><body></body></html>"; @@ -403,7 +403,7 @@ + "</body></html>"; final String thirdContent = "<html><head><title>Third</title></head><body>\n" - + " <a id='link' onClick='open(\"http://fourth\", \"_parent\"); " + + " <a id='link' onClick='window.open(\"http://fourth\", \"_parent\"); " + "return false;'>Click me</a>\n" + "</body></html>"; final String fourthContent = "<html><head><title>Fourth</title></head><body></body></html>"; @@ -1579,7 +1579,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; @@ -1630,7 +1630,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; @@ -1675,7 +1675,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; @@ -1720,7 +1720,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; @@ -1765,7 +1765,7 @@ final String firstContent = "<html><head><title>First</title></head><body>\n" + "<form name='form1'>\n" - + " <a id='link' onClick='open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + + " <a id='link' onClick='window.open(\"" + URL_SECOND + "\", \"_blank\").focus(); return false;'" + "return false;'>Click me</a>\n" + "</form>\n" + "</body></html>"; Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElement2Test.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElement2Test.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElement2Test.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -14,11 +14,13 @@ */ package com.gargoylesoftware.htmlunit.javascript.host.html; +import static com.gargoylesoftware.htmlunit.BrowserRunner.Browser.FF17; import static com.gargoylesoftware.htmlunit.BrowserRunner.Browser.IE; -import static com.gargoylesoftware.htmlunit.BrowserRunner.Browser.FF17; import org.junit.Test; import org.junit.runner.RunWith; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; import com.gargoylesoftware.htmlunit.BrowserRunner; import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts; @@ -745,4 +747,32 @@ + "</body></html>"; loadPageWithAlerts2(html); } + + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({ "loaded", "loaded", "loaded" }) + public void onLoadCalledEachTimeFrameContentChanges() throws Exception { + final String html = + "<html>\n" + + " <body>\n" + + " <iframe id='testFrame' onload='alert(\"loaded\");'></iframe>\n" + + " <div id='d1' onclick='i.contentWindow.location.replace(\"blah.html\")'>1</div>\n" + + " <div id='d2' onclick='i.contentWindow.location.href=\"blah.html\"'>2</div>\n" + + " <script>var i = document.getElementById('testFrame')</script>\n" + + " </body>\n" + + "</html>"; + + final String frameHtml = "<html><body>foo</body></html>"; + + getMockWebConnection().setDefaultResponse(frameHtml); + + final WebDriver driver = loadPage2(html); + + driver.findElement(By.id("d1")).click(); + driver.findElement(By.id("d2")).click(); + + assertEquals(getExpectedAlerts(), getCollectedAlerts(driver)); + } } Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElementTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElementTest.java 2013-01-23 13:37:53 UTC (rev 8030) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/html/HTMLIFrameElementTest.java 2013-01-24 08:06:51 UTC (rev 8031) @@ -135,41 +135,6 @@ } /** - * @throws Exception if an error occurs - */ - @Test - @Alerts({ "loaded", "loaded", "loaded" }) - public void onLoadCalledEachTimeFrameContentChanges() throws Exception { - final String html = - "<html>\n" - + " <body>\n" - + " <iframe id='i' onload='alert(\"loaded\");'></iframe>\n" - + " <div id='d1' onclick='i.contentWindow.location.replace(\"blah.html\")'>1</div>\n" - + " <div id='d2' onclick='i.contentWindow.location.href=\"blah.html\"'>2</div>\n" - + " <script>var i = document.getElementById(\"i\")</script>\n" - + " </body>\n" - + "</html>"; - - final String frameHtml = "<html><body>foo</body></html>"; - - final WebClient webClient = getWebClient(); - final MockWebConnection webConnection = new MockWebConnection(); - - webConnection.setDefaultResponse(frameHtml); - webConnection.setResponse(URL_FIRST, html); - webClient.setWebConnection(webConnection); - - final List<String> collectedAlerts = new ArrayList<String>(); - webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts)); - - final HtmlPage page = webClient.getPage(URL_FIRST); - page.getHtmlElementById("d1").click(); - page.getHtmlElementById("d2").click(); - - assertEquals(getExpectedAlerts(), collectedAlerts); - } - - /** * @throws Exception if the test fails */ @Test |