\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
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
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
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
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
*/\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
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
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
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
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
// 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
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
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
* 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
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
} 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
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.
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);
+ }
}