From: <rb...@us...> - 2017-07-03 17:37:30
|
Revision: 14642 http://sourceforge.net/p/htmlunit/code/14642 Author: rbri Date: 2017-07-03 17:37:27 +0000 (Mon, 03 Jul 2017) Log Message: ----------- take care of label elements during event bubbling; they have to trigger a click event for the associated element Modified Paths: -------------- trunk/htmlunit/src/changes/changes.xml trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomElement.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event2.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/EventTarget.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent2.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlLabelTest.java Modified: trunk/htmlunit/src/changes/changes.xml =================================================================== --- trunk/htmlunit/src/changes/changes.xml 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/changes/changes.xml 2017-07-03 17:37:27 UTC (rev 14642) @@ -9,6 +9,9 @@ <body> <release version="2.28" date="???" description="Bugfixes"> <action type="fix" dev="rbri"> + Take care of label elements during event bubbling; they have to trigger a click event for the associated element. + </action> + <action type="fix" dev="rbri"> JavaScript: fix window.getComputedStyle() pseudo handling if pseudo param starts with double colon. </action> <action type="update" dev="rbri"> Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomElement.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomElement.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomElement.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -892,15 +892,40 @@ * @return the page contained in the current window as returned by {@link WebClient#getCurrentWindow()} * @exception IOException if an IO error occurs */ - @SuppressWarnings("unchecked") - protected <P extends Page> P click(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, + public <P extends Page> P click(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final boolean triggerMouseEvents) throws IOException { + return click(shiftKey, ctrlKey, altKey, triggerMouseEvents, false, false); + } + /** + * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> + * + * Simulates clicking on this element, returning the page in the window that has the focus + * after the element has been clicked. Note that the returned page may or may not be the same + * as the original page, depending on the type of element being clicked, the presence of JavaScript + * action listeners, etc. + * + * @param shiftKey {@code true} if SHIFT is pressed during the click + * @param ctrlKey {@code true} if CTRL is pressed during the click + * @param altKey {@code true} if ALT is pressed during the click + * @param triggerMouseEvents if true trigger the mouse events also + * @param ignoreVisibility whether to ignore visibility or not + * @param disableProcessLabelAfterBubbling ignore label processing + * @param <P> the page type + * @return the page contained in the current window as returned by {@link WebClient#getCurrentWindow()} + * @exception IOException if an IO error occurs + */ + @SuppressWarnings("unchecked") + public <P extends Page> P click(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, + final boolean triggerMouseEvents, final boolean ignoreVisibility, + final boolean disableProcessLabelAfterBubbling) throws IOException { + // make enclosing window the current one final SgmlPage page = getPage(); page.getWebClient().setCurrentWindow(page.getEnclosingWindow()); - if (!isDisplayed() || !(page instanceof HtmlPage) + if ((!ignoreVisibility && !isDisplayed()) + || !(page instanceof HtmlPage) || this instanceof DisabledElement && ((DisabledElement) this).isDisabled()) { return (P) page; } @@ -927,7 +952,7 @@ } if (getPage().getWebClient().getJavaScriptEngine() instanceof NashornJavaScriptEngine) { - final Event2 event; + final MouseEvent2 event; if (getPage().getWebClient().getBrowserVersion().hasFeature(EVENT_ONCLICK_USES_POINTEREVENT)) { event = new PointerEvent2(getEventTargetElement(), MouseEvent.TYPE_CLICK, shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_LEFT); @@ -936,10 +961,14 @@ event = new MouseEvent2(getEventTargetElement(), MouseEvent.TYPE_CLICK, shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_LEFT); } + + if (disableProcessLabelAfterBubbling) { + event.disableProcessLabelAfterBubbling(); + } return (P) click(event, false); } - final Event event; + final MouseEvent event; if (getPage().getWebClient().getBrowserVersion().hasFeature(EVENT_ONCLICK_USES_POINTEREVENT)) { event = new PointerEvent(getEventTargetElement(), MouseEvent.TYPE_CLICK, shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_LEFT); @@ -948,7 +977,11 @@ event = new MouseEvent(getEventTargetElement(), MouseEvent.TYPE_CLICK, shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_LEFT); } - return (P) click(event, false); + + if (disableProcessLabelAfterBubbling) { + event.disableProcessLabelAfterBubbling(); + } + return (P) click(event, ignoreVisibility); } } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -666,4 +666,15 @@ builder.append(");"); return builder.toString(); } + + /** + * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> + * + * If we click on a label, we have to simulate a click on the element referenced by the 'for' attribute also. + * To support this for special events we have this method here. + * @return false in this default impl + */ + public boolean processLabelAfterBubbling() { + return false; + } } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event2.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event2.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/Event2.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -1097,4 +1097,15 @@ return META_MASK; } } + + /** + * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> + * + * If we click on a label, we have to simulate a click on the element referenced by the 'for' attribute also. + * To support this for special events we have this method here. + * @return false in this default impl + */ + public boolean processLabelAfterBubbling() { + return false; + } } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/EventTarget.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/EventTarget.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/EventTarget.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -32,6 +32,8 @@ import com.gargoylesoftware.htmlunit.html.DomDocumentFragment; import com.gargoylesoftware.htmlunit.html.DomElement; import com.gargoylesoftware.htmlunit.html.DomNode; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlLabel; import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor; @@ -38,6 +40,7 @@ import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction; import com.gargoylesoftware.htmlunit.javascript.host.Window; import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement; +import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLLabelElement; import net.sourceforge.htmlunit.corejs.javascript.Context; import net.sourceforge.htmlunit.corejs.javascript.Function; @@ -179,7 +182,13 @@ // bubbling phase event.setEventPhase(Event.AT_TARGET); eventTarget = this; + HtmlLabel label = null; + final boolean processLabelAfterBubbling = event.processLabelAfterBubbling(); + while (eventTarget != null) { + if (label == null && processLabelAfterBubbling && eventTarget instanceof HTMLLabelElement) { + label = (HtmlLabel) eventTarget.getDomNodeOrNull(); + } final EventTarget jsNode = eventTarget; final EventListenersContainer elc = jsNode.eventListenersContainer_; if (elc != null && !(jsNode instanceof Window) && (isAttached || !(jsNode instanceof HTMLElement))) { @@ -197,6 +206,19 @@ event.setEventPhase(Event.BUBBLING_PHASE); } + if (label != null) { + System.out.println("call " + label); + final HtmlElement element = label.getReferencedElement(); + if (element != null) { + try { + element.click(event.isShiftKey(), event.isCtrlKey(), event.isAltKey(), false, true, true); + } + catch (final IOException e) { + // ignore for now + } + } + } + if (isAttached || windowEventIfDetached) { final ScriptResult r = windowsListeners.executeBubblingListeners(event, args, propHandlerArgs); result = ScriptResult.combine(r, result, ie); Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -39,6 +39,7 @@ * @author Marc Guillemot * @author Ahmed Ashour * @author Frank Danek + * @author Ronald Brill */ @JsxClass public class MouseEvent extends UIEvent { @@ -107,6 +108,9 @@ /** The button code according to W3C (0: left button, 1: middle button, 2: right button). */ private int button_; + /** Switch to disable label handling if we already processing the event triggered from label processing */ + private boolean processLabelAfterBubbling_ = true; + /** * Used to build the prototype. */ @@ -376,4 +380,19 @@ public boolean isShiftKey() { return super.isShiftKey(); } + + /** + * {@inheritDoc} Overridden take care of click events. + */ + @Override + public boolean processLabelAfterBubbling() { + return MouseEvent.TYPE_CLICK == getType() && processLabelAfterBubbling_; + } + + /** + * Disable the lable processing if we are already processing one. + */ + public void disableProcessLabelAfterBubbling() { + processLabelAfterBubbling_ = false; + } } Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent2.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent2.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/event/MouseEvent2.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -85,6 +85,9 @@ /** The button code according to W3C (0: left button, 1: middle button, 2: right button). */ private int button_; + /** Switch to disable label handling if we already processing the event triggered from label processing */ + private boolean processLabelAfterBubbling_ = true; + /** * Used to build the prototype. */ @@ -222,4 +225,19 @@ super("MouseEvent"); } } + + /** + * {@inheritDoc} Overridden take care of click events. + */ + @Override + public boolean processLabelAfterBubbling() { + return MouseEvent.TYPE_CLICK == getType() && processLabelAfterBubbling_; + } + + /** + * Disable the lable processing if we are already processing one. + */ + public void disableProcessLabelAfterBubbling() { + processLabelAfterBubbling_ = false; + } } Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlLabelTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlLabelTest.java 2017-07-03 17:33:52 UTC (rev 14641) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/html/HtmlLabelTest.java 2017-07-03 17:37:27 UTC (rev 14642) @@ -72,4 +72,99 @@ driver.findElement(By.id("label1")).click(); verifyAlerts(driver, getExpectedAlerts()); } + + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({"click span1", "click radio1Label", "click listItem1", "click list", + "click radio1", "click radio1Label", + "click listItem1", "click list"}) + public void triggerRadioComplexCase() throws Exception { + final String html = "<html>\n" + + "<body>\n" + + " <ul onclick='alert(\"click list\")'>\n" + + " <li onclick='alert(\"click listItem1\")'>\n" + + " <label id='radio1Label' for='radio1' onclick='alert(\"click radio1Label\")'>\n" + + " <span>\n" + + " <input id='radio1' name='radios' value='1' type='radio'" + + "onclick='alert(\"click radio1\");'>\n" + + " <span id='radio1Span' onclick='alert(\"click span1\")'>Radio 1</span>\n" + + " </span>\n" + + " </label>\n" + + " </li>\n" + + "</ul>\n" + + "<button id='check' onclick='alert(document.getElementById(\"radio1\").checked)'>Check</button>\n" + + "</body></html>"; + + final WebDriver driver = loadPage2(html); + driver.findElement(By.id("radio1Span")).click(); + verifyAlerts(driver, getExpectedAlerts()); + + driver.findElement(By.id("check")).click(); + verifyAlerts(driver, "true"); + } + + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({"click span1", "click radio1Label", "click listItem1", "click list", + "click radio1", "click radio1Label", + "click listItem1", "click list"}) + public void triggerRadioComplexCaseHidden() throws Exception { + final String html = "<html>\n" + + "<body>\n" + + " <ul onclick='alert(\"click list\")'>\n" + + " <li onclick='alert(\"click listItem1\")'>\n" + + " <label id='radio1Label' for='radio1' onclick='alert(\"click radio1Label\")'>\n" + + " <span>\n" + + " <input id='radio1' name='radios' value='1' type='radio' style='display: none;'" + + "onclick='alert(\"click radio1\");'>\n" + + " <span id='radio1Span' onclick='alert(\"click span1\")'>Radio 1</span>\n" + + " </span>\n" + + " </label>\n" + + " </li>\n" + + "</ul>\n" + + "<button id='check' onclick='alert(document.getElementById(\"radio1\").checked)'>Check</button>\n" + + "</body></html>"; + + final WebDriver driver = loadPage2(html); + driver.findElement(By.id("radio1Span")).click(); + verifyAlerts(2000000, driver, getExpectedAlerts()); + + driver.findElement(By.id("check")).click(); + verifyAlerts(driver, "true"); + } + + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({"click span1", "click radio1Label", "click listItem1", "click list", + "click radio1Label", "click listItem1", "click list"}) + public void triggerRadioComplexCaseDisabled() throws Exception { + final String html = "<html>\n" + + "<body>\n" + + " <ul onclick='alert(\"click list\")'>\n" + + " <li onclick='alert(\"click listItem1\")'>\n" + + " <label id='radio1Label' for='radio1' onclick='alert(\"click radio1Label\")'>\n" + + " <span>\n" + + " <input id='radio1' name='radios' value='1' type='radio' disabled" + + "onclick='alert(\"click radio1\");'>\n" + + " <span id='radio1Span' onclick='alert(\"click span1\")'>Radio 1</span>\n" + + " </span>\n" + + " </label>\n" + + " </li>\n" + + "</ul>\n" + + "<button id='check' onclick='alert(document.getElementById(\"radio1\").checked)'>Check</button>\n" + + "</body></html>"; + + final WebDriver driver = loadPage2(html); + driver.findElement(By.id("radio1Span")).click(); + verifyAlerts(driver, getExpectedAlerts()); + + driver.findElement(By.id("check")).click(); + verifyAlerts(driver, "true"); + } } |