Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:allow explicitly reveiving certain dom events for inert elements #19121

Merged
merged 14 commits into from
Apr 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,6 @@
*/
package com.vaadin.flow.internal.nodefeature;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import com.vaadin.flow.dom.DebouncePhase;
import com.vaadin.flow.dom.DisabledUpdateMode;
import com.vaadin.flow.dom.DomEvent;
Expand All @@ -39,10 +25,24 @@
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;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

/**
* Map of DOM events with server-side listeners. The key set of this map
Expand Down Expand Up @@ -114,6 +114,7 @@ private static class DomEventListenerWrapper
private int debounceTimeout = 0;
private EnumSet<DebouncePhase> debouncePhases = NO_TIMEOUT_PHASES;
private List<SerializableRunnable> unregisterHandlers;
private boolean allowIntert;
mstahv marked this conversation as resolved.
Show resolved Hide resolved

private DomEventListenerWrapper(ElementListenerMap listenerMap,
String type, DomEventListener origin) {
Expand Down Expand Up @@ -265,6 +266,12 @@ private boolean isPropertySynchronized(String propertyName) {
.contains(JsonConstants.SYNCHRONIZE_PROPERTY_TOKEN
+ propertyName);
}

@Override
public DomListenerRegistration allowInert() {
allowIntert = true;
return this;
}
}

/**
Expand Down Expand Up @@ -427,7 +434,9 @@ public void fireEvent(DomEvent event) {
if (listeners == null) {
return;
}
boolean isElementEnabled = event.getSource().isEnabled();
final boolean isElementEnabled = event.getSource().isEnabled();
final boolean inert = event.getSource().getNode().isInert();

List<DomEventListenerWrapper> typeListeners = listeners
.get(event.getType());
if (typeListeners == null) {
Expand All @@ -436,6 +445,15 @@ public void fireEvent(DomEvent event) {

List<DomEventListener> listeners = new ArrayList<>();
for (DomEventListenerWrapper wrapper : typeListeners) {
if (inert && !wrapper.allowIntert) {
// drop as inert
LoggerFactory.getLogger(ElementListenerMap.class.getName())
.info("Ignored listener invocation from "
+ "the client side for an inert {} element",
event.getSource().getTag());
mstahv marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

if ((isElementEnabled
|| DisabledUpdateMode.ALWAYS.equals(wrapper.mode))
&& wrapper.matchesFilter(event.getEventData())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
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.NodeOwner;
import com.vaadin.flow.internal.StateNode;
import com.vaadin.flow.internal.StateTree;
import com.vaadin.flow.shared.JsonConstants;

import elemental.json.JsonObject;
Expand Down Expand Up @@ -62,12 +64,12 @@ public Optional<Runnable> 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 "
tepi marked this conversation as resolved.
Show resolved Hide resolved
+ "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(
tepi marked this conversation as resolved.
Show resolved Hide resolved
"Ignored RPC for invocation handler '{}' from "
+ "the client side for an inert node id='{}'",
getClass().getName(), node.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

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;
Expand Down Expand Up @@ -64,4 +65,9 @@ public Optional<Runnable> handleNode(StateNode node,
return Optional.empty();
}

@Override
protected boolean allowInert(UI ui, JsonObject invocationJson) {
// handled separately in ElementListenerMap
return true;
}
}
28 changes: 28 additions & 0 deletions flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;

import com.vaadin.flow.internal.nodefeature.InertData;
import net.jcip.annotations.NotThreadSafe;
import org.junit.Assert;
import org.junit.Test;
Expand Down Expand Up @@ -382,6 +383,33 @@ public void listenerReceivesEvents() {
Assert.assertEquals(1, listenerCalls.get());
}

@Test
public void listenerReceivesEventsWithAllowIntert() {
mstahv marked this conversation as resolved.
Show resolved Hide resolved
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package com.vaadin.flow.uitest.ui;

import com.vaadin.flow.component.UI;
mstahv marked this conversation as resolved.
Show resolved Hide resolved
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
mstahv marked this conversation as resolved.
Show resolved Hide resolved
import com.vaadin.flow.router.Route;
import com.vaadin.flow.uitest.servlet.ViewTestLayout;

Expand Down