blob: baefe3db672da0ca018fc2a1952de884bf8b13ad [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerAdapter;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.util.Alarm;
import com.intellij.util.ReflectionUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.basic.BasicPopupMenuUI;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.event.AWTEventListener;
import java.awt.event.HierarchyEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.EventListener;
/**
* This class listens event from ProjectManager and cleanup some
* internal Swing references.
*
* @author Vladimir Kondratyev
*/
public final class SwingCleanuper implements ApplicationComponent{
private final Alarm myAlarm;
/** Invoked by reflection
* @param projectManager */
SwingCleanuper(ProjectManager projectManager){
myAlarm=new Alarm();
projectManager.addProjectManagerListener(
new ProjectManagerAdapter(){
public void projectOpened(final Project project) {
myAlarm.cancelAllRequests();
}
// Swing keeps references to the last focused component inside DefaultKeyboardFocusManager.realOppositeComponent
// which is used to compose next focus event. Actually this component could be an editors or a tool window. To fix this
// memory leak we (if the project was closed and a new one was not opened yet) request focus to the status bar and after
// the focus events have passed the queue, we put 'null' to the DefaultKeyboardFocusManager.realOppositeComponent field.
public void projectClosed(final Project project){
myAlarm.cancelAllRequests();
myAlarm.addRequest(
new Runnable() {
public void run() {
// request focus into some focusable component inside IdeFrame
final IdeFrameImpl frame;
final Window window=KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if(window instanceof IdeFrameImpl){
frame=(IdeFrameImpl)window;
}else{
frame=(IdeFrameImpl)SwingUtilities.getAncestorOfClass(IdeFrameImpl.class,window);
}
if(frame!=null){
final Application app = ApplicationManager.getApplication();
if (app != null && app.isActive()) {
((JComponent)frame.getStatusBar()).requestFocus();
}
}
//noinspection SSBasedInspection
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
// KeyboardFocusManager.newFocusOwner
resetStaticField(KeyboardFocusManager.class, "newFocusOwner");
// Clear "realOppositeComponent", "realOppositeWindow"
final KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
resetField(focusManager, Component.class, "realOppositeComponent");
resetField(focusManager, Window.class, "realOppositeWindow");
// Memory leak on static field in BasicPopupMenuUI
try {
final Object helperObject = ReflectionUtil.getField(BasicPopupMenuUI.class, null, Object.class, "menuKeyboardHelper");
if (null != helperObject) {
resetField(helperObject, Component.class, "lastFocused");
}
}
catch (Exception e) {
// Ignore
}
// Memory leak on javax.swing.TransferHandler$SwingDragGestureRecognizer.component
try{
final Object recognizerObject = ReflectionUtil.getField(TransferHandler.class, null, null, "recognizer");
if(recognizerObject!=null){ // that is memory leak
final Method setComponentMethod = DragGestureRecognizer.class.getDeclaredMethod("setComponent", Component.class);
setComponentMethod.invoke(recognizerObject,new Object[]{null});
}
}
catch (Exception e){
// Ignore
}
try {
fixJTextComponentMemoryLeak();
}
catch(Exception e) {
// Ignore
}
focusManager.setGlobalCurrentFocusCycleRoot(null); //Remove focus leaks
try {
final Method m = ReflectionUtil.getDeclaredMethod(KeyboardFocusManager.class,"setGlobalFocusOwner", Component.class);
m.invoke(focusManager, new Object[]{null});
}
catch (Exception e) {
// Ignore
}
resetStaticField(KeyboardFocusManager.class, "newFocusOwner");
resetStaticField(KeyboardFocusManager.class, "permanentFocusOwner");
resetStaticField(KeyboardFocusManager.class, "currentFocusCycleRoot");
}
}
);
}
},
2500
);
}
}
);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
private final Field myNativeAXResourceField;
{
Field field = null;
if (SystemInfo.isMac) {
try {
field = ReflectionUtil.findField(AccessibleContext.class, Object.class, "nativeAXResource");
}
catch (NoSuchFieldException ignored) {
}
}
myNativeAXResourceField = field;
}
@Override
public void eventDispatched(AWTEvent event) {
if (!SystemInfo.isMac || !Registry.is("ide.mac.fix.accessibleLeak")) return;
HierarchyEvent he = (HierarchyEvent)event;
if ((he.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) > 0) {
if (he.getComponent() != null && !he.getComponent().isShowing()) {
Component c = he.getComponent();
if (c instanceof JTextComponent) {
JTextComponent textComponent = (JTextComponent)c;
CaretListener[] carets = textComponent.getListeners(CaretListener.class);
for (CaretListener each : carets) {
if (isCAccessibleListener(each)) {
textComponent.removeCaretListener(each);
}
}
Document document = textComponent.getDocument();
if (document instanceof AbstractDocument) {
DocumentListener[] documentListeners = ((AbstractDocument)document).getDocumentListeners();
for (DocumentListener each : documentListeners) {
if (isCAccessibleListener(each)) {
document.removeDocumentListener(each);
}
}
}
} else if (c instanceof JProgressBar) {
JProgressBar bar = (JProgressBar)c;
ChangeListener[] changeListeners = bar.getChangeListeners();
for (ChangeListener each : changeListeners) {
if (isCAccessibleListener(each)) {
bar.removeChangeListener(each);
}
}
} else if (c instanceof JSlider) {
JSlider slider = (JSlider)c;
ChangeListener[] changeListeners = slider.getChangeListeners();
for (ChangeListener each : changeListeners) {
if (isCAccessibleListener(each)) {
slider.removeChangeListener(each);
}
}
}
AccessibleContext ac = c.getAccessibleContext();
if (ac != null && myNativeAXResourceField != null) {
try {
Object resource = myNativeAXResourceField.get(ac);
if (resource != null && resource.getClass().getName().equals("apple.awt.CAccessible")) {
Field accessible = ReflectionUtil.findField(resource.getClass(), Accessible.class, "accessible");
accessible.set(resource, null);
}
}
catch (Exception ignored) {
}
}
}
}
}
}, AWTEvent.HIERARCHY_EVENT_MASK);
}
private static boolean isCAccessibleListener(EventListener listener) {
return listener != null && listener.toString().contains("AXTextChangeNotifier");
}
private static void resetField(Object object, Class type, @NonNls String name) {
try {
ReflectionUtil.resetField(object, ReflectionUtil.findField(object.getClass(), type, name));
}
catch (Exception e) {
// Ignore
}
}
private static void resetStaticField(@NotNull Class aClass, @NotNull @NonNls String name) {
ReflectionUtil.resetField(aClass, null, name);
}
public final void disposeComponent(){}
@NotNull
public final String getComponentName(){
return "SwingCleanuper";
}
public final void initComponent() { }
private static void fixJTextComponentMemoryLeak() {
final JTextComponent component = ReflectionUtil.getField(JTextComponent.class, null, JTextComponent.class, "focusedComponent");
if (component != null && !component.isDisplayable()){
ReflectionUtil.resetField(JTextComponent.class, JTextComponent.class, "focusedComponent");
}
}
}