diff --git a/flow-server/src/main/java/com/vaadin/flow/dom/DomListenerRegistration.java b/flow-server/src/main/java/com/vaadin/flow/dom/DomListenerRegistration.java index 6709622ac8c..7606bce2d0e 100644 --- a/flow-server/src/main/java/com/vaadin/flow/dom/DomListenerRegistration.java +++ b/flow-server/src/main/java/com/vaadin/flow/dom/DomListenerRegistration.java @@ -391,4 +391,14 @@ default public DomListenerRegistration preventDefault() { return this; } + /** + * Configures the event listener to bypass the server side security checks + * for modality. Handle with care! Can be ok when transferring data from + * "non-ui" component events through the Element API, like e.g. geolocation + * events. + * + * @return the DomListenerRegistration for further configuration + */ + public DomListenerRegistration allowInert(); + } diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ElementListenerMap.java b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ElementListenerMap.java index 0db7e927604..f14c1feee31 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ElementListenerMap.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ElementListenerMap.java @@ -29,6 +29,9 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.component.UI; import com.vaadin.flow.dom.DebouncePhase; import com.vaadin.flow.dom.DisabledUpdateMode; import com.vaadin.flow.dom.DomEvent; @@ -39,7 +42,6 @@ import com.vaadin.flow.internal.JsonUtils; import com.vaadin.flow.internal.StateNode; import com.vaadin.flow.shared.JsonConstants; - import elemental.json.Json; import elemental.json.JsonObject; import elemental.json.JsonValue; @@ -114,6 +116,7 @@ private static class DomEventListenerWrapper private int debounceTimeout = 0; private EnumSet debouncePhases = NO_TIMEOUT_PHASES; private List unregisterHandlers; + private boolean allowInert; private DomEventListenerWrapper(ElementListenerMap listenerMap, String type, DomEventListener origin) { @@ -265,6 +268,12 @@ private boolean isPropertySynchronized(String propertyName) { .contains(JsonConstants.SYNCHRONIZE_PROPERTY_TOKEN + propertyName); } + + @Override + public DomListenerRegistration allowInert() { + allowInert = true; + return this; + } } /** @@ -427,7 +436,15 @@ public void fireEvent(DomEvent event) { if (listeners == null) { return; } - boolean isElementEnabled = event.getSource().isEnabled(); + + final boolean isElementEnabled = event.getSource().isEnabled(); + + final boolean isNavigationRequest = UI.BrowserNavigateEvent.EVENT_NAME + .equals(event.getType()) + || UI.BrowserLeaveNavigationEvent.EVENT_NAME + .equals(event.getType()); + final boolean inert = event.getSource().getNode().isInert(); + List typeListeners = listeners .get(event.getType()); if (typeListeners == null) { @@ -436,6 +453,15 @@ public void fireEvent(DomEvent event) { List listeners = new ArrayList<>(); for (DomEventListenerWrapper wrapper : typeListeners) { + if (!isNavigationRequest && inert && !wrapper.allowInert) { + // drop as inert + LoggerFactory.getLogger(ElementListenerMap.class.getName()) + .info("Ignored listener invocation for {} event from " + + "the client side for an inert {} element", + event.getType(), event.getSource().getTag()); + continue; + } + if ((isElementEnabled || DisabledUpdateMode.ALWAYS.equals(wrapper.mode)) && wrapper.matchesFilter(event.getEventData()) diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/AbstractRpcInvocationHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/AbstractRpcInvocationHandler.java index 09c7ce06493..838fa4f57dc 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/AbstractRpcInvocationHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/AbstractRpcInvocationHandler.java @@ -19,18 +19,15 @@ import java.util.List; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.vaadin.flow.component.PollEvent; import com.vaadin.flow.component.UI; -import com.vaadin.flow.component.UI.BrowserLeaveNavigationEvent; -import com.vaadin.flow.component.UI.BrowserNavigateEvent; import com.vaadin.flow.internal.StateNode; import com.vaadin.flow.shared.JsonConstants; - import elemental.json.JsonObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Abstract invocation handler implementation with common methods. *

@@ -62,12 +59,12 @@ public Optional handle(UI ui, JsonObject invocationJson) { // ignore RPC requests from the client side for the nodes that are // invisible, disabled or inert if (node.isInactive()) { - getLogger().trace("Ignored RPC for invocation handler '{}' from " + getLogger().info("Ignored RPC for invocation handler '{}' from " + "the client side for an inactive (disabled or invisible) node id='{}'", getClass().getName(), node.getId()); return Optional.empty(); } else if (!allowInert(ui, invocationJson) && node.isInert()) { - getLogger().trace( + getLogger().info( "Ignored RPC for invocation handler '{}' from " + "the client side for an inert node id='{}'", getClass().getName(), node.getId()); @@ -116,22 +113,6 @@ private boolean isPollEventInvocation(JsonObject invocationJson) { invocationJson.getString(JsonConstants.RPC_EVENT_TYPE)); } - private boolean isNavigationInvocation(JsonObject invocationJson) { - if (!invocationJson.hasKey(JsonConstants.RPC_EVENT_TYPE)) { - return false; - } - if (BrowserNavigateEvent.EVENT_NAME.equals( - invocationJson.getString(JsonConstants.RPC_EVENT_TYPE))) { - return true; - } - if (BrowserLeaveNavigationEvent.EVENT_NAME.equals( - invocationJson.getString(JsonConstants.RPC_EVENT_TYPE))) { - return true; - } - return false; - - } - private boolean isPollingEnabledForUI(UI ui) { return ui.getPollInterval() > 0; } @@ -201,8 +182,7 @@ private boolean isLegitimatePollEventInvocation(UI ui, * the current invocation or not. */ protected boolean allowInert(UI ui, JsonObject invocationJson) { - return isValidPollInvocation(ui, invocationJson) - || isNavigationInvocation(invocationJson); + return isValidPollInvocation(ui, invocationJson); } /** diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/EventRpcHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/EventRpcHandler.java index cba97e66bc2..85b12550f79 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/EventRpcHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/rpc/EventRpcHandler.java @@ -17,12 +17,12 @@ import java.util.Optional; +import com.vaadin.flow.component.UI; import com.vaadin.flow.dom.DomEvent; import com.vaadin.flow.dom.Element; import com.vaadin.flow.internal.StateNode; import com.vaadin.flow.internal.nodefeature.ElementListenerMap; import com.vaadin.flow.shared.JsonConstants; - import elemental.json.Json; import elemental.json.JsonObject; @@ -64,4 +64,9 @@ public Optional handleNode(StateNode node, return Optional.empty(); } + @Override + protected boolean allowInert(UI ui, JsonObject invocationJson) { + // handled separately in ElementListenerMap + return true; + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java b/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java index 0add1e5744c..9ffcffcccf8 100644 --- a/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java @@ -50,6 +50,7 @@ import com.vaadin.flow.internal.nodefeature.ElementListenersTest; import com.vaadin.flow.internal.nodefeature.ElementPropertyMap; import com.vaadin.flow.internal.nodefeature.ElementStylePropertyMap; +import com.vaadin.flow.internal.nodefeature.InertData; import com.vaadin.flow.internal.nodefeature.VirtualChildrenList; import com.vaadin.flow.server.MockVaadinServletService; import com.vaadin.flow.server.StreamResource; @@ -59,7 +60,6 @@ import com.vaadin.tests.util.AlwaysLockedVaadinSession; import com.vaadin.tests.util.MockUI; import com.vaadin.tests.util.TestUtil; - import elemental.json.Json; import elemental.json.JsonArray; import elemental.json.JsonObject; @@ -382,6 +382,33 @@ public void listenerReceivesEvents() { Assert.assertEquals(1, listenerCalls.get()); } + @Test + public void listenerReceivesEventsWithAllowInert() { + Element e = ElementFactory.createDiv(); + // Inert the node, verify events no more passed through + InertData inertData = e.getNode().getFeature(InertData.class); + inertData.setInertSelf(true); + inertData.generateChangesFromEmpty(); + + AtomicInteger listenerCalls = new AtomicInteger(0); + DomEventListener myListener = event -> listenerCalls.incrementAndGet(); + + DomListenerRegistration domListenerRegistration = e + .addEventListener("click", myListener); + Assert.assertEquals(0, listenerCalls.get()); + e.getNode().getFeature(ElementListenerMap.class) + .fireEvent(new DomEvent(e, "click", Json.createObject())); + // Event should not go through + Assert.assertEquals(0, listenerCalls.get()); + + // Now should pass inert check and get notified + domListenerRegistration.allowInert(); + e.getNode().getFeature(ElementListenerMap.class) + .fireEvent(new DomEvent(e, "click", Json.createObject())); + Assert.assertEquals(1, listenerCalls.get()); + + } + @Test public void getPropertyDefaults() { Element element = ElementFactory.createDiv(); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/EventRpcHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/EventRpcHandlerTest.java index 64b713e0df9..8db4dc30c2f 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/EventRpcHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/EventRpcHandlerTest.java @@ -22,10 +22,11 @@ import com.vaadin.flow.component.ComponentTest.TestComponent; import com.vaadin.flow.component.UI; +import com.vaadin.flow.dom.DomListenerRegistration; import com.vaadin.flow.dom.Element; import com.vaadin.flow.internal.StateNode; +import com.vaadin.flow.internal.nodefeature.InertData; import com.vaadin.flow.shared.JsonConstants; - import elemental.json.Json; import elemental.json.JsonObject; @@ -53,12 +54,27 @@ public void testElementEventData() throws Exception { ui.add(c); AtomicInteger invocationData = new AtomicInteger(0); - element.addEventListener("test-event", e -> invocationData - .addAndGet((int) e.getEventData().getNumber("nr"))); + DomListenerRegistration domListenerRegistration = element + .addEventListener("test-event", e -> invocationData + .addAndGet((int) e.getEventData().getNumber("nr"))); JsonObject eventData = Json.createObject(); eventData.put("nr", 123); sendElementEvent(element, ui, "test-event", eventData); Assert.assertEquals(123, invocationData.get()); + + // Also verify inert stops the event and allowInert allows to bypass + invocationData.set(0); + eventData.put("nr", 124); + InertData inertData = element.getNode().getFeature(InertData.class); + inertData.setInertSelf(true); + inertData.generateChangesFromEmpty(); + sendElementEvent(element, ui, "test-event", eventData); + Assert.assertEquals(0, invocationData.get()); + // explicitly allow this event listener even when element is inert + domListenerRegistration.allowInert(); + sendElementEvent(element, ui, "test-event", eventData); + Assert.assertEquals(124, invocationData.get()); + } private static JsonObject createElementEventInvocation(Element element,