Skip to content

Commit

Permalink
fix: prevent flush changes when synchronizing properties (#15583)
Browse files Browse the repository at this point in the history
DOM events related to property synchronization must not flush changes
to avoid wrong execution order of events on server side.
For example, an 'has-input-value-changed' event may occur before the
actual value of a field has been changed and a flush will trigger
listeners with an old value.

Fixes #15615
  • Loading branch information
mcollovati committed Jan 20, 2023
1 parent bc5c72c commit 6adb7e9
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.vaadin.client.flow.binding;

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

Expand Down Expand Up @@ -1316,11 +1315,17 @@ private void handleDomEvent(Event event, BindingContext context) {
if (sendNow) {
// Send if there were not filters or at least one matched

// Flush all debounced events so that they don't happen
// in wrong order in the server-side
List<Consumer<String>> executedCommands = Debouncer.flushAll();
boolean commandAlreadyExecuted = false;
boolean flushPendingChanges = synchronizeProperties.isEmpty();

if (!executedCommands.contains(sendCommand)) {
if (flushPendingChanges) {
// Flush all debounced events so that they don't happen
// in wrong order in the server-side
commandAlreadyExecuted = Debouncer.flushAll()
.contains(sendCommand);
}

if (!commandAlreadyExecuted) {
sendCommand.accept(null);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import com.vaadin.client.ApplicationConfiguration;
import com.vaadin.client.ClientEngineTestBase;
import com.vaadin.client.ExistingElementMap;
import com.vaadin.client.InitialPropertiesHandler;
import com.vaadin.client.Registry;
import com.vaadin.client.flow.binding.Binder;
import com.vaadin.client.flow.binding.Debouncer;
import com.vaadin.client.flow.collection.JsArray;
import com.vaadin.client.flow.collection.JsCollections;
import com.vaadin.client.flow.collection.JsMap;
Expand Down Expand Up @@ -188,6 +190,78 @@ public void testDomListenerSynchronization() {
assertSynchronized("offsetWidth");
}

public void testFlushPendingChangesOnDomEvent() {
Browser.getDocument().getBody().appendChild(element);
Binder.bind(node, element);

AtomicInteger commandExecution = new AtomicInteger();
Debouncer debouncer = Debouncer.getOrCreate(element, "on-value:false",
300);
debouncer.trigger(JsCollections.<String> set()
.add(JsonConstants.EVENT_PHASE_TRAILING), phase -> {
if (phase == null) {
commandExecution.incrementAndGet();
} else if (JsonConstants.EVENT_PHASE_TRAILING
.equals(phase)) {
finishTest();
}
});

String constantPoolKey = "expressionsKey";
JsonObject expressions = Json.createObject();
expressions.put(
JsonConstants.SYNCHRONIZE_PROPERTY_TOKEN + "offsetWidth",
false);
GwtBasicElementBinderTest.addToConstantPool(constantPool,
constantPoolKey, expressions);
node.getMap(NodeFeatures.ELEMENT_LISTENERS).getProperty("event1")
.setValue(constantPoolKey);
Reactive.flush();

dispatchEvent("event1");

assertEquals("Changes should have not been flushed", 0,
commandExecution.get());

// Wait for debouncer to be unregistered
delayTestFinish(1000);
}

public void testDoNotFlushPendingChangesOnPropertySynchronization() {
Browser.getDocument().getBody().appendChild(element);
Binder.bind(node, element);

AtomicInteger commandExecution = new AtomicInteger();
Debouncer debouncer = Debouncer.getOrCreate(element, "on-value:false",
300);
debouncer.trigger(JsCollections.<String> set()
.add(JsonConstants.EVENT_PHASE_TRAILING), phase -> {
if (phase == null) {
commandExecution.incrementAndGet();
} else if (JsonConstants.EVENT_PHASE_TRAILING
.equals(phase)) {
finishTest();
}
});

String constantPoolKey = "expressionsKey";
JsonObject expressions = Json.createObject();
node.getMap(NodeFeatures.ELEMENT_LISTENERS).getProperty("event1")
.setValue(constantPoolKey);
GwtBasicElementBinderTest.addToConstantPool(constantPool,
constantPoolKey, expressions);

Reactive.flush();

dispatchEvent("event1");

assertEquals("Changes should have been flushed", 1,
commandExecution.get());

// Wait for debouncer to be unregistered
delayTestFinish(1000);
}

protected StateNode createNode() {
return new StateNode(0, tree);
}
Expand Down

0 comments on commit 6adb7e9

Please sign in to comment.