]> source.dussan.org Git - gwtquery.git/commitdiff
Remove all references in the dataCache object whenever any queued function finishes...
authorManolo Carrasco <manolo@apache.org>
Tue, 2 Oct 2012 10:26:47 +0000 (12:26 +0200)
committerManolo Carrasco <manolo@apache.org>
Tue, 2 Oct 2012 10:26:47 +0000 (12:26 +0200)
gwtquery-core/src/main/java/com/google/gwt/query/client/Function.java
gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java
gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/Effects.java
gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/QueuePlugin.java
gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/ClipAnimation.java
gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryEffectsTestGwt.java

index f8207a371373bf8cc31a35dbc4759d352791a35a..f6a7b82cc44945244b37f708c747c99dc3c99927 100644 (file)
@@ -219,6 +219,8 @@ public abstract class Function {
 
   /**
    * Override this method for bound event handlers.
+   * 
+   * @return boolean: false means stop propagation and prevent default 
    */
   public boolean f(Event e) {
     setEvent(e);
index a2abd8ca146feaf129b00987a6bb1da8b100f245..831e9b616830c1bddae1ef81ff5bee4b1ff78954 100644 (file)
@@ -15,6 +15,11 @@ package com.google.gwt.query.client;
 \r
 import static com.google.gwt.query.client.plugins.QueuePlugin.Queue;\r
 \r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
 import com.google.gwt.core.client.GWT;\r
 import com.google.gwt.core.client.JavaScriptObject;\r
 import com.google.gwt.core.client.JsArray;\r
@@ -61,11 +66,6 @@ import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.GqUi;\r
 import com.google.gwt.user.client.ui.Widget;\r
 \r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.List;\r
-import java.util.Map;\r
-\r
 /**\r
  * GwtQuery is a GWT clone of the popular jQuery library.\r
  */\r
@@ -123,9 +123,9 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public static final BodyElement body = Document.get().getBody();\r
 \r
   /**\r
-   * Object to store element data.\r
+   * Object to store element data (public so as we can access to it from tests).\r
    */\r
-  protected static JsCache dataCache = null;\r
+  public static JsCache dataCache = null;\r
 \r
   /**\r
    * The document element in the current page.\r
@@ -153,6 +153,8 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public static Class<GQuery> GQUERY = GQuery.class;\r
 \r
   private static final String OLD_DATA_PREFIX = "old-";\r
+  \r
+  private static final String OLD_DISPLAY = OLD_DATA_PREFIX + "display";\r
 \r
   private static JsMap<Class<? extends GQuery>, Plugin<? extends GQuery>> plugins;\r
 \r
@@ -422,25 +424,32 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
     return GQuery.data(e, key, null);\r
   }\r
 \r
-  protected static <S> Object data(Element item, String name, S value) {\r
+  /**\r
+   * We store data in js object which has this structure:\r
+   * \r
+   *  datacache [element_hash] [key] = value\r
+   * \r
+   * @return the value stored in the element with the given name\r
+   */\r
+  protected static <S> Object data(Element element, String key, S value) {\r
     if (dataCache == null) {\r
       windowData = JavaScriptObject.createObject().cast();\r
       dataCache = JavaScriptObject.createObject().cast();\r
     }\r
-    item = item == window || item.getNodeName() == null ? windowData : item;\r
-    if (item == null) {\r
-      return value;\r
-    }\r
-    int id = item.hashCode();\r
-    if (name != null && !dataCache.exists(id)) {\r
-      dataCache.put(id, JsCache.createObject().cast());\r
-    }\r
+    element = element == window || element.getNodeName() == null ? windowData : element;\r
+    if (element != null && key != null) {\r
+      int id = element.hashCode();\r
 \r
-    JsCache d = dataCache.getCache(id);\r
-    if (name != null && value != null) {\r
-      d.put(name, value);\r
+      if (value == null) {\r
+        return dataCache.exists(id) ? dataCache.getCache(id).get(key) : null;\r
+      }\r
+      \r
+      if (!dataCache.exists(id)) {\r
+        dataCache.put(id, JsCache.createObject().cast());\r
+      }\r
+      dataCache.getCache(id).put(key, value);\r
     }\r
-    return name != null ? d.get(name) : id;\r
+    return value;\r
   }\r
 \r
   /**\r
@@ -2283,10 +2292,10 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
    */\r
   public GQuery hide() {\r
     for (Element e : elements) {\r
-      String currentDisplay = e.getStyle().getDisplay();\r
-      Object old = data(e, "oldDisplay", null);\r
-      if (old == null && !"none".equals(currentDisplay)) {\r
-        data(e, "oldDisplay", getStyleImpl().curCSS(e, "display", false));\r
+      String currentDisplay = getStyleImpl().curCSS(e, "display", false);\r
+      Object old = data(e, OLD_DISPLAY, null);\r
+      if (old == null && !currentDisplay.matches("(|none)")) {\r
+        data(e, OLD_DISPLAY, currentDisplay);\r
       }\r
     }\r
 \r
@@ -3503,7 +3512,7 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
     return this;\r
   }\r
 \r
-  private void removeData(Element item, String name) {\r
+  protected void removeData(Element item, String name) {\r
     if (dataCache == null) {\r
       windowData = JavaScriptObject.createObject().cast();\r
       dataCache = JavaScriptObject.createObject().cast();\r
@@ -3518,6 +3527,7 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
         removeData(item, null);\r
       }\r
     } else {\r
+      // when the element cache is empty we remove its entry to save memory (issue 132)\r
       dataCache.delete(id);\r
     }\r
   }\r
@@ -3654,7 +3664,9 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public void restoreCssAttrs(String... cssProps) {\r
     for (Element e : elements) {\r
       for (String a : cssProps) {\r
-        getStyleImpl().setStyleProperty(e, a, (String) data(e, OLD_DATA_PREFIX + a, null));\r
+        String datakey = OLD_DATA_PREFIX + a;\r
+        getStyleImpl().setStyleProperty(e, a, (String) data(e, datakey, null));\r
+        removeData(e, datakey);\r
       }\r
     }\r
   }\r
@@ -3830,7 +3842,7 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public GQuery show() {\r
     for (Element e : elements) {\r
       String currentDisplay = e.getStyle().getDisplay();\r
-      String oldDisplay = (String) data(e, "oldDisplay", null);\r
+      String oldDisplay = (String) data(e, OLD_DISPLAY, null);\r
 \r
       // reset the display\r
       if (oldDisplay == null && "none".equals(currentDisplay)) {\r
@@ -3841,7 +3853,7 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
       // check if the stylesheet impose display: none. If it is the case, determine\r
       // the default display for the tag and store it at the element level\r
       if ("".equals(currentDisplay) && !getStyleImpl().isVisible(e)) {\r
-        data(e, "oldDisplay", getStyleImpl().defaultDisplay(e.getNodeName()));\r
+        data(e, OLD_DISPLAY, getStyleImpl().defaultDisplay(e.getNodeName()));\r
       }\r
     }\r
 \r
@@ -3853,9 +3865,10 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
       String currentDisplay = e.getStyle().getDisplay();\r
       if ("".equals(currentDisplay) || "none".equals(currentDisplay)) {\r
         getStyleImpl().setStyleProperty(e, "display",\r
-            JsUtils.or((String) data(e, "oldDisplay", null), ""));\r
+            JsUtils.or((String) data(e, OLD_DISPLAY, null), ""));\r
       }\r
     }\r
+    removeData(OLD_DISPLAY);\r
     return this;\r
   }\r
 \r
index e3f69e99a829d6817bce78e1024f538244710b9d..604ab6e62b52ac66310db03f20d85fc5301be5f2 100755 (executable)
@@ -21,10 +21,10 @@ import com.google.gwt.query.client.Function;
 import com.google.gwt.query.client.GQuery;\r
 import com.google.gwt.query.client.Properties;\r
 import com.google.gwt.query.client.plugins.effects.ClipAnimation;\r
-import com.google.gwt.query.client.plugins.effects.Fx;\r
-import com.google.gwt.query.client.plugins.effects.PropertiesAnimation;\r
 import com.google.gwt.query.client.plugins.effects.ClipAnimation.Action;\r
 import com.google.gwt.query.client.plugins.effects.ClipAnimation.Direction;\r
+import com.google.gwt.query.client.plugins.effects.Fx;\r
+import com.google.gwt.query.client.plugins.effects.PropertiesAnimation;\r
 import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;\r
 \r
 /**\r
@@ -36,16 +36,27 @@ public class Effects extends QueuePlugin<Effects> {
    * Class to access protected methods in Animation. \r
    */\r
   public static abstract class GQAnimation  extends Animation {\r
+\r
+    private static final String ACTUAL_ANIMATION = "EffectsRunnning";\r
     \r
+    // Each Animation is associated to one element\r
     protected Element e;\r
     \r
     protected void onStart() {\r
+      // Mark this animation as actual, so as we can stop it in the GQuery.stop() method \r
+      $(e).data(ACTUAL_ANIMATION, this);\r
       super.onStart();\r
     }\r
     protected void onComplete() {\r
-      $(e).remove(ACTUAL_ANIMATION);\r
+      // avoid memory leak (issue #132)\r
+      $(e).removeData(ACTUAL_ANIMATION);\r
       super.onComplete();\r
     }\r
+    public void cancel() {\r
+      // avoid memory leak (issue #132)\r
+      $(e).removeData(ACTUAL_ANIMATION);\r
+      super.cancel();\r
+    }\r
   }\r
   \r
   /**\r
@@ -57,7 +68,6 @@ public class Effects extends QueuePlugin<Effects> {
     public static final int SLOW = 600;\r
   }\r
 \r
-  private static final String ACTUAL_ANIMATION = "EffectsRunnning";\r
   \r
   public static final Class<Effects> Effects = GQuery.registerPlugin(\r
       Effects.class, new Plugin<Effects>() {\r
@@ -77,14 +87,12 @@ public class Effects extends QueuePlugin<Effects> {
     } else {\r
       queue(e, DEFAULT_NAME, new Function() {\r
         public void cancel(Element e) {\r
-          Animation anim = (Animation) data(e, ACTUAL_ANIMATION, null);\r
+          Animation anim = (Animation) data(e, GQAnimation.ACTUAL_ANIMATION, null);\r
           if (anim != null) {\r
-            remove(ACTUAL_ANIMATION);\r
             anim.cancel();\r
           }\r
         }\r
         public void f(Element e) {\r
-          data(e, ACTUAL_ANIMATION, anim);\r
           anim.run(duration);\r
         }\r
       });\r
index dc97b4b340ae4dd1e1dd27ad820b8a86aba94304..1c2a570a2f62d2f189d631eb6c17e2ddfd72cc90 100644 (file)
@@ -137,7 +137,8 @@ public class QueuePlugin<T extends QueuePlugin<?>> extends GQuery {
    * in the named queue.
    */
   public int queue(String name) {
-    return isEmpty() ? 0 : queue(get(0), name, null).size();
+    Queue<Object> q = isEmpty() ? null : queue(get(0), name, null);
+    return q == null? 0 : q.size();
   }
   
   /**
@@ -264,6 +265,9 @@ public class QueuePlugin<T extends QueuePlugin<?>> extends GQuery {
         if (f instanceof Function) {
           ((Function) f).fe(elem);
         }
+      } else {
+        // if it is the last function remove the queue to avoid leaks (issue 132)
+        removeData(elem, name);
       }
     }
   }
@@ -272,10 +276,10 @@ public class QueuePlugin<T extends QueuePlugin<?>> extends GQuery {
   protected <S> Queue<S> queue(Element elem, String name, S func) {
     if (elem != null) {
       Queue<S> q = (Queue<S>) data(elem, name, null);
-      if (q == null) {
-        q = (Queue<S>) data(elem, name, new LinkedList<S>());
-      }
       if (func != null) {
+        if (q == null) {
+          q = (Queue<S>) data(elem, name, new LinkedList<S>());
+        }
         q.add(func);
         if (q.size() == 1) {
           if (func instanceof Function) {
index 3af409388673c3d9c5b85c7695059969f177fc9c..bb842815769f1b6a315d7c9a5c267ac20501e5a2 100755 (executable)
@@ -53,7 +53,6 @@ public class ClipAnimation extends GQAnimation {
   Action action;\r
   Corner corner;\r
   Direction direction;\r
-  Element e;\r
   int percent;\r
   private GQuery back = Effects.$();\r
   private Function[] funcs;\r
index f286d6f911b9d8d489eab6a2d4ecc0cb30647e26..a4ca55090b1a7af0a9cb1baac52a8b1a75f59b89 100644 (file)
@@ -27,7 +27,9 @@ import com.google.gwt.query.client.plugins.effects.PropertiesAnimation;
 import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * Test class for testing gwtquery effects plugin api.
@@ -424,4 +426,62 @@ public class GQueryEffectsTestGwt extends GWTTestCase {
     timerLongTime.schedule(2200);
   
   }
+  
+  
+  int animationRunCounter = 0;
+  public void testQueuesAndDataLeaks_issue132() {
+    
+    final Widget w = new Label("some animation");
+    w.setVisible(false);
+    RootPanel.get().add(w);
+    w.getElement().setId("e");
+    GQuery g = $(w);
+    
+    int test_duration = 1000;
+    int fx_duration = 200;
+    final int loops = test_duration / fx_duration;
+    
+    // Queue a set of effects which will use the data cache
+    for (int i = 0; i < loops ; i++) {
+      final char[] bulk = new char[5*1024*1024]; // let's leak 5MBs
+      g.fadeToggle(fx_duration, new Function() {
+        public void f() {
+          animationRunCounter ++;
+          bulk[0] = 0; // we keep it in handler
+        }
+      });
+    }
+    
+    // Testing delay as well
+    g.delay(fx_duration, new Function(){
+      public void f() {
+        animationRunCounter ++;
+      }
+    });
+    
+    // We do the assertions after all effects have been run
+    g.queue(new Function() {
+      public void f() {
+        // after running queue method it is mandatory to call dequeue,
+        // otherwise the queue get stuck
+        $(this).dequeue();
+        // Check that all animations and the delayed function has been run
+        assertEquals(loops + 1, animationRunCounter);
+        
+        // Check that nothings is left in the dataCache object
+        assertEquals(0, GQuery.dataCache.length());
+        
+        // Check that getting queue size does not initialize the data
+        // object for this object
+        assertEquals(0, $(this).queue());
+        assertEquals(0, GQuery.dataCache.length());
+        
+        // Mark the test as success and stop delay timer
+        finishTest();
+      };
+    });
+    
+    // delay the test enough to run all animations
+    delayTestFinish(test_duration * 2);
+  }
 }