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

fix: trigger refresh on hotswap only for redefined classes (#20684) (CP: 24.6) #20751

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 44 additions & 35 deletions flow-server/src/main/java/com/vaadin/flow/hotswap/Hotswapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ private void onHotswapInternal(HashSet<Class<?>> classes,
}
}
EnumMap<UIRefreshStrategy, List<UI>> refreshActions = computeRefreshStrategies(
vaadinSessions, classes);
classes, redefined);
boolean uiTreeNeedsRefresh = !refreshActions.isEmpty();
if (forceBrowserReload || uiTreeNeedsRefresh) {
triggerClientUpdate(refreshActions, forceBrowserReload);
Expand Down Expand Up @@ -306,13 +306,12 @@ private enum UIRefreshStrategy {
}

private EnumMap<UIRefreshStrategy, List<UI>> computeRefreshStrategies(
Set<VaadinSession> vaadinSessions, Set<Class<?>> changedClasses) {
Set<Class<?>> changedClasses, boolean redefined) {
EnumMap<UIRefreshStrategy, List<UI>> uisToRefresh = new EnumMap<>(
UIRefreshStrategy.class);
forEachActiveUI(ui -> uisToRefresh
.computeIfAbsent(computeRefreshStrategy(ui, changedClasses),
k -> new ArrayList<>())
.add(ui));
forEachActiveUI(ui -> uisToRefresh.computeIfAbsent(
computeRefreshStrategy(ui, changedClasses, redefined),
k -> new ArrayList<>()).add(ui));

uisToRefresh.remove(UIRefreshStrategy.SKIP);
return uisToRefresh;
Expand All @@ -331,7 +330,7 @@ private void forEachActiveUI(Consumer<UI> consumer) {
}

private UIRefreshStrategy computeRefreshStrategy(UI ui,
Set<Class<?>> changedClasses) {
Set<Class<?>> changedClasses, boolean redefined) {
List<HasElement> targetsChain = new ArrayList<>(
ui.getActiveRouterTargetsChain());
if (targetsChain.isEmpty()) {
Expand All @@ -350,21 +349,30 @@ private UIRefreshStrategy computeRefreshStrategy(UI ui,
.distinct().toList();

UIRefreshStrategy refreshStrategy;
// A full chain refresh should be triggered if there are modal
// components, since they could be attached to UI or parent layouts
if (ui.hasModalComponent()) {
refreshStrategy = UIRefreshStrategy.PUSH_REFRESH_CHAIN;
} else if (!targetChainChangedItems.isEmpty()) {
refreshStrategy = targetChainChangedItems.stream()
.allMatch(chainItem -> chainItem == route)
? UIRefreshStrategy.PUSH_REFRESH_ROUTE
: UIRefreshStrategy.PUSH_REFRESH_CHAIN;
if (redefined) {
// A full chain refresh should be triggered if there are modal
// components, since they could be attached to UI or parent layouts
if (ui.hasModalComponent()) {
refreshStrategy = UIRefreshStrategy.PUSH_REFRESH_CHAIN;
} else if (!targetChainChangedItems.isEmpty()) {
refreshStrategy = targetChainChangedItems.stream()
.allMatch(chainItem -> chainItem == route)
? UIRefreshStrategy.PUSH_REFRESH_ROUTE
: UIRefreshStrategy.PUSH_REFRESH_CHAIN;
} else {
// Look into the UI tree to find if any component is instance of
// a changed class. If so, detect its parent route or layout to
// determine the refresh strategy.
refreshStrategy = computeRefreshStrategyForUITree(ui,
changedClasses, targetsChain, route);
}
} else {
// Look into the UI tree to find if any component is instance of
// a changed class. If so, detect its parent route or layout to
// determine the refresh strategy.
refreshStrategy = computeRefreshStrategyForUITree(ui,
changedClasses, targetsChain, route);
// prevent refresh for classes loaded for the first time since
// it shouldn't impact current view, unless they are newly defined
// auto layouts.
// For example, it prevents refresh caused by Dialog related classes
// loaded for the first time when the dialog is opened
refreshStrategy = UIRefreshStrategy.SKIP;
}

// A different layout might have been applied after hotswap
Expand All @@ -374,20 +382,21 @@ private UIRefreshStrategy computeRefreshStrategy(UI ui,
String currentPath = ui.getActiveViewLocation().getPath();
RouteTarget routeTarget = registry
.getNavigationRouteTarget(currentPath).getRouteTarget();
if (routeTarget != null && (
// parent layout changed
routeTarget.getParentLayouts().stream()
.anyMatch(changedClasses::contains) ||
// applied auto layout changed
RouteUtil.isAutolayoutEnabled(routeTarget.getTarget(),
currentPath)
&& registry.hasLayout(currentPath)
&& RouteUtil
.collectRouteParentLayouts(
registry.getLayout(currentPath))
.stream()
.anyMatch(changedClasses::contains))) {
refreshStrategy = UIRefreshStrategy.PUSH_REFRESH_CHAIN;
if (routeTarget != null) {
// parent layout changed
if ((redefined && routeTarget.getParentLayouts().stream()
.anyMatch(changedClasses::contains)) ||
// applied auto layout changed or added
RouteUtil.isAutolayoutEnabled(routeTarget.getTarget(),
currentPath)
&& registry.hasLayout(currentPath)
&& RouteUtil
.collectRouteParentLayouts(
registry.getLayout(currentPath))
.stream()
.anyMatch(changedClasses::contains)) {
refreshStrategy = UIRefreshStrategy.PUSH_REFRESH_CHAIN;
}
}
}

Expand Down
Loading