IDEMPIERE-5520 Navigation between Tabs leave Detached DOM objects (Leak) (#1648)

* IDEMPIERE-5520 Navigation between Tabs leave Detached DOM objects (Leak)

- Fix NPE

* IDEMPIERE-5520 Navigation between Tabs leave Detached DOM objects (Leak)

- refine NPE fix
- fix a potential leak in billboard

* IDEMPIERE-5520 Navigation between Tabs leave Detached DOM objects (Leak)

- reduce detached html elemenet leak for change role
This commit is contained in:
hengsin 2023-01-26 18:20:03 +08:00 committed by GitHub
parent a9ae3b8652
commit d4c3685761
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 107 additions and 18 deletions

View File

@ -628,17 +628,19 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
if (Env.getCtx().get(ServerContextURLHandler.SERVER_CONTEXT_URL_HANDLER) != null)
properties.put(ServerContextURLHandler.SERVER_CONTEXT_URL_HANDLER, Env.getCtx().get(ServerContextURLHandler.SERVER_CONTEXT_URL_HANDLER));
//desktop cleanup
IDesktop appDesktop = getAppDeskop();
if (appDesktop != null)
appDesktop.logout(T -> {if (T) asyncChangeRole(httpRequest.getSession(), locale, properties);});
}
private void asyncChangeRole(HttpSession httpSession, Locale locale, Properties properties) {
//stop key listener
if (keyListener != null) {
keyListener.detach();
keyListener = null;
}
//desktop cleanup
IDesktop appDesktop = getAppDeskop();
if (appDesktop != null)
appDesktop.logout();
//remove all children component
getChildren().clear();
@ -648,7 +650,7 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
this.setPage(page);
//clear session attributes
Enumeration<String> attributes = httpRequest.getSession().getAttributeNames();
Enumeration<String> attributes = httpSession.getAttributeNames();
while(attributes.hasMoreElements()) {
String attribute = attributes.nextElement();
@ -656,7 +658,7 @@ public class AdempiereWebUI extends Window implements EventListener<Event>, IWeb
if (attribute.contains("zkoss."))
continue;
httpRequest.getSession().removeAttribute(attribute);
httpSession.removeAttribute(attribute);
}
//logout ad_session

View File

@ -80,6 +80,7 @@ import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zul.Button;
import org.zkoss.zul.impl.InputElement;
import com.lowagie.text.DocumentException;
@ -881,7 +882,7 @@ public final class AEnv
* @param c
*/
public static void detachInputElement(Component c) {
if (c instanceof InputElement) {
if (c instanceof InputElement || c instanceof Button) {
c.detach();
}
if (c.getChildren().size() > 0) {

View File

@ -184,15 +184,17 @@ public class Tab extends org.zkoss.zul.Tab
}
//Workaround for detached HTML input element leak
boolean attached = getDesktop() != null;
if (panel != null) {
Executions.schedule(getDesktop(), e -> panel.detach(), new Event("onCloseLinkedPanel"));
if (attached)
Executions.schedule(getDesktop(), e -> panel.detach(), new Event("onCloseLinkedPanel"));
}
detach();
if (panel != null) {
//Workaround for detached HTML input element leak
if (panel.getChildren().size() > 0) {
if (attached && panel.getChildren().size() > 0) {
Component[] childs = panel.getChildren().toArray(new Component[0]);
for(Component c : childs) {
AEnv.detachInputElement(c);
@ -204,6 +206,9 @@ public class Tab extends org.zkoss.zul.Tab
if (include instanceof Include) {
include.detach();
}
if (!attached)
panel.detach();
}
}

View File

@ -30,6 +30,7 @@ import org.adempiere.base.event.EventManager;
import org.adempiere.base.event.IEventManager;
import org.adempiere.base.event.IEventTopics;
import org.adempiere.model.MBroadcastMessage;
import org.adempiere.util.Callback;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.adwindow.ADWindow;
@ -806,7 +807,30 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
return layout;
}
@Override
public void logout() {
logout(null);
}
@Override
public void logout(Callback<Boolean> callback) {
if (layout != null && layout.getDesktop() != null
&& Executions.getCurrent() != null && Executions.getCurrent().getNativeRequest() != null) {
//close all tabs
List<Component> tabs = windowContainer.getComponent().getTabs().getChildren();
int end = tabs.size() - 1;
for (int i = end; i >= 0; i--) {
((Tab)tabs.get( i )).close();
}
AEnv.detachInputElement(layout);
//schedule async logout
Executions.schedule(layout.getDesktop(), e -> asyncLogout(callback), new Event("onAsyncLogout"));
} else {
asyncLogout(callback);
}
}
private void asyncLogout(Callback<Boolean> callback) {
unbindEventManager();
if (dashboardController != null) {
dashboardController.onLogOut();
@ -817,10 +841,17 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
sideController.onLogOut();
sideController = null;
}
if (layout != null) {
layout.detach();
if (callback != null) {
if (layout != null && layout.getDesktop() != null
&& Executions.getCurrent() != null && Executions.getCurrent().getNativeRequest() != null) {
Executions.schedule(layout.getDesktop(), e -> callback.onCallback(Boolean.TRUE), new Event("onAsyncLogoutCallback"));
} else {
callback.onCallback(Boolean.TRUE);
}
}
layout = null;
layout = null;
pnlHead = null;
max = null;
m_desktop = null;
@ -1012,6 +1043,7 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
updateHelpContext(X_AD_CtxHelp.CTXTYPE_Task, taskId);
}
@Override
public boolean isPendingWindow() {
List<Object> windows = getWindows();
if (windows != null) {

View File

@ -201,17 +201,60 @@ public interface IDesktop extends UIPart {
*/
public void logout();
/**
*
* @param callback callback after logout
*/
public default void logout(Callback<Boolean> callback) {
logout();
if (callback != null) {
callback.onCallback(Boolean.TRUE);
}
}
/**
* update help content in help/info panel
* @param ctxTypes
* @param recordId
*/
public void updateHelpContext(String ctxType, int recordId);
/**
* update tooltip content in help/info panel
* @param gridField
*/
public void updateHelpTooltip(GridField gridField);
/**
* update tooltip content in help/info panel
* @param hdr
* @param desc
* @param help
* @param otherContent
*/
public void updateHelpTooltip(String hdr, String desc, String help, String otherContent);
/**
* update quick info (status line) in help/info panel
* @param gridTab
*/
public void updateHelpQuickInfo(GridTab gridTab);
/**
*
* @return true if there are changes not save yet
*/
public boolean isPendingWindow();
/**
* update tab title by windowNo
* @param title
* @param windowNo
*/
public void setTabTitle(String title, int windowNo);
/**
* render home tab
*/
public void renderHomeTab();
}

View File

@ -424,7 +424,7 @@ public class WindowContainer extends AbstractUIPart implements EventListener<Eve
Tab tab = (Tab)event.getTarget();
org.zkoss.zul.Tabpanel panel = tab.getLinkedPanel();
if (panel == null) {
System.console().printf("error");
return;
}
Component component = panel.getFirstChild();
if (component != null && component.getAttribute(ITabOnSelectHandler.ATTRIBUTE_KEY) instanceof ITabOnSelectHandler)
@ -740,6 +740,9 @@ public class WindowContainer extends AbstractUIPart implements EventListener<Eve
*/
public void setTabTitle(int tabNo, String title, String tooltip)
{
if (tabNo < 0 || tabNo >= tabbox.getTabs().getChildren().size())
return;
org.zkoss.zul.Tabs tabs = tabbox.getTabs();
Tab tab = (Tab) tabs.getChildren().get(tabNo);
setTabTitle(title, tab);

View File

@ -45,6 +45,9 @@ public class ServerPushTemplate {
* @param callback
*/
public void executeAsync(final IServerPushCallback callback) {
if (!desktop.isAlive())
return;
try {
EventListener<Event> task = new EventListener<Event>() {
@Override

View File

@ -188,9 +188,9 @@ public class ChartRendererServiceImpl implements IChartRendererService {
if (Executions.getCurrent() != null)
{
String script = "var parent = jq('#" + parent.getUuid() + "');";
script += "var billboard = parent.children().first(); ";
script += "var div = parent.children().eq(1); ";
String script = "(function() {let parent = jq('#" + parent.getUuid() + "');";
script += "let billboard = parent.children().first(); ";
script += "let div = parent.children().eq(1); ";
script += "if (billboard.children().length == 0) {";
script += "div.show(); ";
script += "billboard.hide(); ";
@ -198,7 +198,7 @@ public class ChartRendererServiceImpl implements IChartRendererService {
script += "else {";
script += "div.hide(); ";
script += "billboard.show(); ";
script += "}";
script += "}})();";
Clients.response(new AuScript(script));
}