Attempts to do the following:
1) Cleanup all outstanding non-primitive properties from $wnd
2) Remove all registered event listeners
3) detach all widgets from rootpanel
4) Cancel any continuously running timers, I/Os in progress, etc
5) Delete all code from the iframe scope (window != $wnd)
6) erase registration from __gwt_activeModules and null out nocache.js module function
7) Remove iframe hosting the module (if present)
Even with all of this cleanup, Chrome's heap profiler still shows leaks on the simplest modules. This is a work in progress to support portal-like services that want to load/unload many GWT modules within a long running page.
Currently, IE paths which use attachEvent() have not been patched.
By default, support is disabled and there should only be a negligible impact on codesize.
Review at http://gwt-code-reviews.appspot.com/1827804
Review by: skybrian@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11539 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/Core.gwt.xml b/user/src/com/google/gwt/core/Core.gwt.xml
index 7279e77..a6d3d68 100644
--- a/user/src/com/google/gwt/core/Core.gwt.xml
+++ b/user/src/com/google/gwt/core/Core.gwt.xml
@@ -35,6 +35,15 @@
<inherits name="com.google.gwt.core.XSLinker" />
<inherits name="com.google.gwt.core.CrossSiteIframeLinker" />
+ <!-- When true, compiles in support for GWT.unloadModule(), otherwise it is a no-op. -->
+ <define-property name="gwt.unloadEnabled" values="false, true"/>
+ <set-property name="gwt.unloadEnabled" value="false"/>
+
+ <replace-with class="com.google.gwt.core.client.impl.UnloadSupportEnabled">
+ <when-property-is name="gwt.unloadEnabled" value="true"/>
+ <when-type-is class="com.google.gwt.core.client.impl.UnloadSupport"/>
+ </replace-with>
+
<define-linker name="soycReport" class="com.google.gwt.core.linker.SoycReportLinker" />
<define-linker name="symbolMaps" class="com.google.gwt.core.linker.SymbolMapsLinker" />
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index 42174ae..f9b68b2 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -85,6 +85,10 @@
return com.google.gwt.core.shared.GWT.<T>create(classLiteral);
}
+ public static void exportUnloadModule() {
+ Impl.exportUnloadModule();
+ }
+
/**
* Gets the URL prefix of the hosting page, useful for prepending to relative
* paths of resources which may be relative to the host page. Typically, you
@@ -291,4 +295,15 @@
private static native String getVersion0() /*-{
return $gwt_version;
}-*/;
+
+ /**
+ * If enabled via <set-property name="gwt.unloadEnabled" value="true"/> invoking this method causes the module
+ * to be removed from memory and all {@link com.google.gwt.core.client.impl.Disposable} instances to be
+ * cleaned up. This method is not typically called by the GWT module itself, but exported so that another module
+ * may call it.
+ * @see com.google.gwt.core.client.GWT#exportUnloadModule()
+ */
+ private static void unloadModule() {
+ Impl.unloadModule();
+ }
}
diff --git a/user/src/com/google/gwt/core/client/impl/Disposable.java b/user/src/com/google/gwt/core/client/impl/Disposable.java
new file mode 100644
index 0000000..7c04d96
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/Disposable.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.gwt.core.client.impl;
+
+/**
+ * Disposable objects are registered to be called when GWT.unloadModule() is invoked allowing a GWT module to clean
+ * up danging DOM references and other exported or leaking references.
+ * @see Impl#scheduleDispose
+ */
+public interface Disposable {
+ void dispose();
+}
diff --git a/user/src/com/google/gwt/core/client/impl/Impl.java b/user/src/com/google/gwt/core/client/impl/Impl.java
index 3534d80..0093672 100644
--- a/user/src/com/google/gwt/core/client/impl/Impl.java
+++ b/user/src/com/google/gwt/core/client/impl/Impl.java
@@ -26,6 +26,8 @@
*/
public final class Impl {
+ public static boolean moduleUnloaded = false;
+
private static final int WATCHDOG_ENTRY_DEPTH_CHECK_INTERVAL_MS = 2000;
/**
@@ -44,6 +46,25 @@
*/
private static int watchdogEntryDepthTimerId = -1;
+ private static UnloadSupport unloadSupport = GWT.isScript() ?
+ (UnloadSupport) GWT.create(UnloadSupport.class) : new UnloadSupport();
+
+ static {
+ exportUnloadModule();
+ }
+
+ public static void clearInterval(int timerId) {
+ unloadSupport.clearInterval(timerId);
+ }
+
+ public static void clearTimeout(int timerId) {
+ unloadSupport.clearTimeout(timerId);
+ }
+
+ public static void dispose(Disposable d) {
+ unloadSupport.dispose(d);
+ }
+
/**
* This method should be used whenever GWT code is entered from a JS context
* and there is no GWT code in the same module on the call stack. Examples
@@ -58,7 +79,7 @@
* The function passed to this method will be invoked via
* <code>Function.apply()</code> with the current <code>this</code> value and
* the invocation arguments passed to <code>$entry</code>.
- *
+ *
* @param jsFunction a JS function to invoke, which is typically a JSNI
* reference to a static Java method
* @return the value returned when <code>jsFunction</code> is invoked, or
@@ -79,6 +100,10 @@
};
}-*/;
+ public static void exportUnloadModule() {
+ unloadSupport.exportUnloadModule();
+ }
+
/**
* Gets an identity-based hash code on the passed-in Object by adding an
* expando. This method should not be used with <code>null</code> or any
@@ -134,7 +159,7 @@
* Returns the obfuscated name of members in the compiled output. This is a
* thin wrapper around JNameOf AST nodes and is therefore meaningless to
* implement in Development Mode.
- *
+ *
* @param jsniIdent a string literal specifying a type, field, or method. Raw
* type names may also be used to obtain the name of the type's seed
* function.
@@ -193,6 +218,10 @@
return entryDepth > 0;
}
+ public static boolean isModuleUnloaded() {
+ return moduleUnloaded;
+ }
+
/**
* Indicates if <code>$entry</code> is present on the stack more than once.
*/
@@ -213,6 +242,25 @@
}
}-*/;
+ public static void scheduleDispose(Disposable d) {
+ unloadSupport.scheduleDispose(d);
+ }
+
+ public static int setInterval(JavaScriptObject func, int time) {
+ return unloadSupport.setInterval(func, time);
+ }
+
+ public static int setTimeout(JavaScriptObject func, int time) {
+ return unloadSupport.setTimeout(func, time);
+ }
+
+ public static void unloadModule() {
+ if (unloadSupport.isUnloadSupported()) {
+ moduleUnloaded = true;
+ unloadSupport.disposeAll();
+ }
+ }
+
private static native Object apply(Object jsFunction, Object thisObj,
Object args) /*-{
if (@com.google.gwt.core.client.GWT::isScript()()) {
@@ -254,6 +302,10 @@
*/
private static Object entry0(Object jsFunction, Object thisObj,
Object args) throws Throwable {
+ // if module is unloaded, don't run anything
+ if (unloadSupport.isUnloadSupported() && Impl.isModuleUnloaded()) {
+ return null;
+ }
boolean initialEntry = enter();
try {
@@ -324,7 +376,7 @@
}-*/;
private static native void watchdogEntryDepthCancel(int timerId) /*-{
- $wnd.clearTimeout(timerId);
+ @com.google.gwt.core.client.impl.Impl::clearTimeout(I)(timerId);
}-*/;
private static void watchdogEntryDepthRun() {
@@ -337,7 +389,7 @@
}
private static native int watchdogEntryDepthSchedule() /*-{
- return $wnd.setTimeout(function() {
+ return @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
@com.google.gwt.core.client.impl.Impl::watchdogEntryDepthRun()();
}, 10);
}-*/;
diff --git a/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java b/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
index 9e66154..37b0d78 100644
--- a/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
+++ b/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
@@ -189,7 +189,7 @@
private static native void scheduleFixedDelayImpl(RepeatingCommand cmd,
int delayMs) /*-{
- $wnd.setTimeout(function() {
+ @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
// $entry takes care of uncaught exception handling
var ret = $entry(@com.google.gwt.core.client.impl.SchedulerImpl::execute(Lcom/google/gwt/core/client/Scheduler$RepeatingCommand;))(cmd);
if (!@com.google.gwt.core.client.GWT::isScript()()) {
@@ -197,7 +197,7 @@
ret = ret == true;
}
if (ret) {
- $wnd.setTimeout(arguments.callee, delayMs);
+ @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(arguments.callee, delayMs);
}
}, delayMs);
}-*/;
@@ -213,10 +213,10 @@
}
if (!ret) {
// Either canceled or threw an exception
- $wnd.clearInterval(arguments.callee.token);
+ @com.google.gwt.core.client.impl.Impl::clearInterval(I)(arguments.callee.token);
}
};
- fn.token = $wnd.setInterval(fn, delayMs);
+ fn.token = @com.google.gwt.core.client.impl.Impl::setInterval(Lcom/google/gwt/core/client/JavaScriptObject;I)(fn, delayMs);
}-*/;
/**
diff --git a/user/src/com/google/gwt/core/client/impl/UnloadSupport.java b/user/src/com/google/gwt/core/client/impl/UnloadSupport.java
new file mode 100644
index 0000000..153b970
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/UnloadSupport.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.gwt.core.client.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Rebound class to enable/disable support for {@link com.google.gwt.core.client.GWT#unloadModule()}
+ */
+public class UnloadSupport {
+
+ static native void clearInterval0(int timerId) /*-{
+ $wnd.clearInterval(timerId);
+ }-*/;
+
+ static native void clearTimeout0(int timerId) /*-{
+ $wnd.clearTimeout(timerId);
+ }-*/;
+
+ static native int setInterval0(JavaScriptObject func, int time) /*-{
+ var timerId = $wnd.setInterval(function () {
+ func();
+ }, time);
+ return timerId;
+ }-*/;
+
+ static native int setTimeout0(JavaScriptObject func, int time, Disposable disposeable) /*-{
+ var timerId = $wnd.setTimeout(function () {
+ func();
+ if (disposeable != null) {
+ @com.google.gwt.core.client.impl.Impl::dispose(Lcom/google/gwt/core/client/impl/Disposable;)(disposeable);
+ }
+ }, time);
+ return timerId;
+ }-*/;
+
+ public void exportUnloadModule() {
+ }
+
+ /**
+ * Return true if {@link com.google.gwt.core.client.GWT#unloadModule()} is enabled. Default is false.
+ */
+ public boolean isUnloadSupported() {
+ return false;
+ }
+
+ void clearInterval(int timerId) {
+ clearInterval0(timerId);
+ }
+
+ void clearTimeout(int timerId) {
+ clearTimeout0(timerId);
+ }
+
+ void dispose(Disposable d) {
+ if (d != null) {
+ d.dispose();
+ }
+ }
+
+ void disposeAll() {
+ }
+
+ void scheduleDispose(Disposable d) {
+ }
+
+
+ int setInterval(JavaScriptObject func, int time) {
+ return setInterval0(func, time);
+ }
+
+ int setTimeout(JavaScriptObject func, int time) {
+ return setTimeout0(func, time, null);
+ }
+}
diff --git a/user/src/com/google/gwt/core/client/impl/UnloadSupportEnabled.java b/user/src/com/google/gwt/core/client/impl/UnloadSupportEnabled.java
new file mode 100644
index 0000000..41c8505
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/UnloadSupportEnabled.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * 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.google.gwt.core.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Rebound version switches on unload support.
+ */
+@GwtScriptOnly
+public class UnloadSupportEnabled extends UnloadSupport {
+
+ static class TimerDisposable implements Disposable {
+
+ int timerId;
+ private Map<Integer, Disposable> timeoutMap;
+ private boolean timeout;
+
+ public TimerDisposable(Map<Integer, Disposable> timeoutMap, boolean isTimeout) {
+ this.timeoutMap = timeoutMap;
+ timeout = isTimeout;
+ }
+
+ @Override
+ public void dispose() {
+ timeoutMap.remove(timerId);
+ if (timeout) {
+ clearTimeout0(timerId);
+ } else {
+ clearInterval0(timerId);
+ }
+ }
+ }
+
+ private Map<Integer, Disposable> timeouts = new HashMap<Integer, Disposable>();
+ private Map<Integer, Disposable> intervals = new HashMap<Integer, Disposable>();
+ private Set<Disposable> disposables = new LinkedHashSet<Disposable>();
+
+ public native void exportUnloadModule() /*-{
+ if (!$wnd.__gwt_activeModules) {
+ $wnd.__gwt_activeModules = {};
+ }
+ var $moduleName = __gwtModuleFunction.__moduleName;
+ // unfortunately GWT.getModuleName() isn't set up yet when this is called
+ var activeModule = $wnd.__gwt_activeModules[$moduleName];
+ if (!activeModule) {
+ activeModule = {};
+ $wnd.__gwt_activeModules[$moduleName] = activeModule;
+ }
+ activeModule.unloadModule = function () {
+ @com.google.gwt.core.client.GWT::unloadModule()();
+
+ delete $wnd.__gwt_activeModules[$moduleName];
+ var modFunc = $moduleName.replace(/\./g, '_');
+ $wnd[modFunc] = null;
+ setTimeout(function () {
+ // Browsers without Object.keys don't benefit from nulling window
+ var keys = Object.keys ? Object.keys(window) : [];
+ // Browsers without window.frameElement don't benefit from removing the iframe
+ var frame = window.frameElement;
+ var i;
+ // null out seedTable and class entries
+ for (key in @com.google.gwt.lang.SeedUtil::seedTable) {
+ var obj = @com.google.gwt.lang.SeedUtil::seedTable[key];
+ obj.prototype.@java.lang.Object::___clazz = null;
+ }
+ @com.google.gwt.lang.SeedUtil::seedTable = null;
+ // String is special cased
+ String.prototype.@java.lang.Object::___clazz = null;
+ for (i = 0; i < keys.length; i++) {
+ try {
+ window[keys[i]] = null;
+ } catch (e) {
+ }
+ }
+ if ($wnd != window && frame) {
+ frame.parentNode.removeChild(frame);
+ }
+ }, 1);
+ };
+ }-*/;
+
+ public boolean isUnloadSupported() {
+ return true;
+ }
+
+ public int setInterval(JavaScriptObject func, int time) {
+ if (!Impl.isModuleUnloaded()) {
+ TimerDisposable disposable = new TimerDisposable(intervals, false);
+ final int timerId = setInterval0(func, time);
+ intervals.put(timerId, disposable);
+ disposable.timerId = timerId;
+ scheduleDispose(disposable);
+ return timerId;
+ }
+ return -1;
+ }
+
+ void clearInterval(int timerId) {
+ if (timerId != -1) {
+ dispose(intervals.get(timerId));
+ }
+ }
+
+ void clearTimeout(int timerId) {
+ if (timerId != -1) {
+ dispose(timeouts.get(timerId));
+ }
+ }
+
+ void dispose(Disposable d) {
+ if (d != null) {
+ try {
+ d.dispose();
+ } catch (Throwable e) {
+ GWT.UncaughtExceptionHandler uncaughtExceptionHandler = GWT.getUncaughtExceptionHandler();
+ if (uncaughtExceptionHandler != null) {
+ uncaughtExceptionHandler.onUncaughtException(e);
+ }
+ }
+ disposables.remove(d);
+ }
+ }
+
+ void disposeAll() {
+ LinkedHashSet<Disposable> copy = new LinkedHashSet<Disposable>(disposables);
+ for (Disposable d : copy) {
+ dispose(d);
+ }
+ }
+
+ void scheduleDispose(Disposable d) {
+ disposables.add(d);
+ }
+
+ int setTimeout(JavaScriptObject func, int time) {
+ if (!Impl.isModuleUnloaded()) {
+ TimerDisposable disposable = new TimerDisposable(timeouts, true);
+ final int timerId = UnloadSupport.setTimeout0(func, time, disposable);
+ timeouts.put(timerId, disposable);
+ disposable.timerId = timerId;
+ scheduleDispose(disposable);
+ return timerId;
+ }
+ return -1;
+ }
+}
diff --git a/user/src/com/google/gwt/geolocation/client/Geolocation.java b/user/src/com/google/gwt/geolocation/client/Geolocation.java
index 16ac210..f3a03d1 100644
--- a/user/src/com/google/gwt/geolocation/client/Geolocation.java
+++ b/user/src/com/google/gwt/geolocation/client/Geolocation.java
@@ -19,6 +19,8 @@
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.impl.Disposable;
+import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.dom.client.PartialSupport;
/**
@@ -259,7 +261,14 @@
* {@link #clearWatch(int)} to stop watching the user's position.
*/
public int watchPosition(Callback<Position, PositionError> callback) {
- return watchPosition(callback, null);
+ final int watchId = watchPosition(callback, null);
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ clearWatch(watchId);
+ }
+ });
+ return watchId;
}
/**
diff --git a/user/src/com/google/gwt/storage/client/StorageImpl.java b/user/src/com/google/gwt/storage/client/StorageImpl.java
index b76b40f..e79b8fc 100644
--- a/user/src/com/google/gwt/storage/client/StorageImpl.java
+++ b/user/src/com/google/gwt/storage/client/StorageImpl.java
@@ -19,6 +19,8 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.impl.Disposable;
+import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.event.shared.HandlerRegistration;
import java.util.ArrayList;
@@ -93,9 +95,17 @@
if (storageEventHandlers.size() == 1) {
addStorageEventHandler0();
}
+
+ final Disposable disposeHandler = new Disposable() {
+ @Override
+ public void dispose() {
+ StorageImpl.this.removeStorageEventHandler(handler);
+ }
+ };
+ Impl.scheduleDispose(disposeHandler);
return new HandlerRegistration() {
public void removeHandler() {
- StorageImpl.this.removeStorageEventHandler(handler);
+ Impl.dispose(disposeHandler);
}
};
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplStandard.java b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplStandard.java
index 0d72e69..cc516d9 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplStandard.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplStandard.java
@@ -46,7 +46,7 @@
/**
* Handle an event from a cell. Used by {@link #initEventSystem()}.
- *
+ *
* @param event the event to handle.
*/
private static void handleNonBubblingEvent(Event event) {
@@ -77,8 +77,8 @@
/**
* Check if the specified element handles the a non-bubbling event.
- *
- * @param elem the element to check
+ *
+ * @param elem the element to check
* @param typeName the non-bubbling event
* @return true if the event is handled, false if not
*/
@@ -101,7 +101,7 @@
}
@Override
- protected int sinkEvent(Widget widget, String typeName) {
+ protected int sinkEvent(Widget widget, final String typeName) {
if (nonBubblingEvents.contains(typeName)) {
// Initialize the event system.
if (dispatchNonBubblingEvent == null) {
@@ -109,10 +109,11 @@
}
// Sink the non-bubbling event.
- Element elem = widget.getElement();
+ final Element elem = widget.getElement();
if (!isNonBubblingEventHandled(elem, typeName)) {
elem.setAttribute("__gwtCellBasedWidgetImplDispatching" + typeName, "true");
sinkEventImpl(elem, typeName);
+ markDisposeEventImpl(elem, typeName);
}
return -1;
} else {
@@ -124,15 +125,26 @@
* Initialize the event system.
*/
private native void initEventSystem() /*-{
- @com.google.gwt.user.cellview.client.CellBasedWidgetImplStandard::dispatchNonBubblingEvent = $entry(function(evt) {
+ @com.google.gwt.user.cellview.client.CellBasedWidgetImplStandard::dispatchNonBubblingEvent = $entry(function (evt) {
@com.google.gwt.user.cellview.client.CellBasedWidgetImplStandard::handleNonBubblingEvent(Lcom/google/gwt/user/client/Event;)(evt);
});
}-*/;
/**
+ * Dispose of an event listener on the element.
+ *
+ * @param elem the element to sink the event on
+ * @param typeName the name of the event to sink
+ */
+ private native void markDisposeEventImpl(Element elem, String typeName) /*-{
+ @com.google.gwt.user.client.impl.DOMImpl::addDisposableEvent(*)(elem,
+ typeName, @com.google.gwt.user.cellview.client.CellBasedWidgetImplStandard::dispatchNonBubblingEvent, true);
+ }-*/;
+
+ /**
* Sink an event on the element.
- *
- * @param elem the element to sink the event on
+ *
+ * @param elem the element to sink the event on
* @param typeName the name of the event to sink
*/
private native void sinkEventImpl(Element elem, String typeName) /*-{
diff --git a/user/src/com/google/gwt/user/client/History.java b/user/src/com/google/gwt/user/client/History.java
index 7507570..c8f9030 100644
--- a/user/src/com/google/gwt/user/client/History.java
+++ b/user/src/com/google/gwt/user/client/History.java
@@ -16,6 +16,8 @@
package com.google.gwt.user.client;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.Disposable;
+import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.impl.HistoryImpl;
@@ -70,6 +72,13 @@
+ "<iframe src=\"javascript:''\" id='__gwt_historyFrame' "
+ "style='position:absolute;width:0;height:0;border:0'>"
+ "</iframe>");
+ } else {
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ impl.dispose();
+ }
+ });
}
}
diff --git a/user/src/com/google/gwt/user/client/Timer.java b/user/src/com/google/gwt/user/client/Timer.java
index fc6e375..8b801a3 100644
--- a/user/src/com/google/gwt/user/client/Timer.java
+++ b/user/src/com/google/gwt/user/client/Timer.java
@@ -50,21 +50,21 @@
}
private static native void clearInterval(int id) /*-{
- $wnd.clearInterval(id);
+ @com.google.gwt.core.client.impl.Impl::clearInterval(I)(id);
}-*/;
private static native void clearTimeout(int id) /*-{
- $wnd.clearTimeout(id);
+ @com.google.gwt.core.client.impl.Impl::clearTimeout(I)(id);
}-*/;
private static native int createInterval(Timer timer, int period) /*-{
- return $wnd.setInterval(
- $entry(function() { timer.@com.google.gwt.user.client.Timer::fire()(); }),
+ return @com.google.gwt.core.client.impl.Impl::setInterval(Lcom/google/gwt/core/client/JavaScriptObject;I)(
+ $entry(function() { timer.@com.google.gwt.user.client.Timer::fire()(); }),
period);
}-*/;
private static native int createTimeout(Timer timer, int delay) /*-{
- return $wnd.setTimeout(
+ return @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(
$entry(function() { timer.@com.google.gwt.user.client.Timer::fire()(); }),
delay);
}-*/;
diff --git a/user/src/com/google/gwt/user/client/Window.java b/user/src/com/google/gwt/user/client/Window.java
index 68eec21..71f1801 100644
--- a/user/src/com/google/gwt/user/client/Window.java
+++ b/user/src/com/google/gwt/user/client/Window.java
@@ -16,6 +16,8 @@
package com.google.gwt.user.client;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.Disposable;
+import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
@@ -72,7 +74,7 @@
* Get the message that will be presented to the user in a confirmation
* dialog that asks the user whether or not she wishes to navigate away from
* the page.
- *
+ *
* @return the message to display to the user, or null
*/
public String getMessage() {
@@ -84,7 +86,7 @@
* confirmation dialog that asks the user whether or not she wishes to
* navigate away from the page. If multiple handlers set the message, the
* last message will be displayed; all others will be ignored.
- *
+ *
* @param message the message to display to the user, or null
*/
public void setMessage(String message) {
@@ -104,7 +106,7 @@
/**
* Fired just before the browser window closes or navigates to a different
* site. No user-interface may be displayed during shutdown.
- *
+ *
* @param event the event
*/
void onWindowClosing(Window.ClosingEvent event);
@@ -115,7 +117,6 @@
* object contains information about the current URL and methods to manipulate
* it. <code>Location</code> is a very simple wrapper, so not all browser
* quirks are hidden from the user.
- *
*/
public static class Location {
private static String cachedQueryString = "";
@@ -123,7 +124,7 @@
/**
* Assigns the window to a new URL. All GWT state will be lost.
- *
+ *
* @param newURL the new URL
*/
public static native void assign(String newURL) /*-{
@@ -132,7 +133,7 @@
/**
* Create a {@link UrlBuilder} based on this {@link Location}.
- *
+ *
* @return the new builder
*/
public static UrlBuilder createUrlBuilder() {
@@ -166,7 +167,7 @@
/**
* Gets the string to the right of the URL's hash.
- *
+ *
* @return the string to the right of the URL's hash.
*/
public static String getHash() {
@@ -175,7 +176,7 @@
/**
* Gets the URL's host and port name.
- *
+ *
* @return the host and port name
*/
public static native String getHost() /*-{
@@ -184,7 +185,7 @@
/**
* Gets the URL's host name.
- *
+ *
* @return the host name
*/
public static native String getHostName() /*-{
@@ -193,7 +194,7 @@
/**
* Gets the entire URL.
- *
+ *
* @return the URL
*/
public static native String getHref() /*-{
@@ -204,7 +205,7 @@
* Gets the URL's parameter of the specified name. Note that if multiple
* parameters have been specified with the same name, the last one will be
* returned.
- *
+ *
* @param name the name of the URL's parameter
* @return the value of the URL's parameter, or null if missing
*/
@@ -219,11 +220,18 @@
}
/**
+<<<<<<< HEAD
+ * Returns a Map of the URL query parameters for the host page; since
+ * changing the map would not change the window's location, the map returned
+ * is immutable.
+ *
+=======
* Returns an immutable Map of the URL query parameters for the host page
* at the time this method was called.
* Any changes to the window's location will be reflected in the result
* of subsequent calls.
*
+>>>>>>> 7c80e9495a6c632e391e6131ae487ff0a16635a7
* @return a map from URL query parameter names to a list of values
*/
public static Map<String, List<String>> getParameterMap() {
@@ -233,7 +241,7 @@
/**
* Gets the path to the URL.
- *
+ *
* @return the path to the URL.
*/
public static native String getPath() /*-{
@@ -242,7 +250,7 @@
/**
* Gets the URL's port.
- *
+ *
* @return the URL's port
*/
public static native String getPort() /*-{
@@ -251,7 +259,7 @@
/**
* Gets the URL's protocol.
- *
+ *
* @return the URL's protocol.
*/
public static native String getProtocol() /*-{
@@ -260,7 +268,7 @@
/**
* Gets the URL's query string.
- *
+ *
* @return the URL's query string
*/
public static String getQueryString() {
@@ -277,7 +285,7 @@
/**
* Replaces the current URL with a new one. All GWT state will be lost. In
* the browser's history, the current URL will be replaced by the new URL.
- *
+ *
* @param newURL the new URL
*/
public static native void replace(String newURL) /*-{
@@ -287,7 +295,7 @@
/**
* Builds the immutable map from String to List<String> that we'll return in
* getParameterMap(). Package-protected for testing.
- *
+ *
* @return a map from the
*/
static Map<String, List<String>> buildListParamMap(String queryString) {
@@ -423,9 +431,9 @@
/**
* Construct a new {@link Window.ScrollEvent}.
- *
+ *
* @param scrollLeft the left scroll position
- * @param scrollTop the top scroll position
+ * @param scrollTop the top scroll position
*/
private ScrollEvent(int scrollLeft, int scrollTop) {
this.scrollLeft = scrollLeft;
@@ -439,7 +447,7 @@
/**
* Gets the window's scroll left.
- *
+ *
* @return window's scroll left
*/
public int getScrollLeft() {
@@ -448,7 +456,7 @@
/**
* Get the window's scroll top.
- *
+ *
* @return the window's scroll top
*/
public int getScrollTop() {
@@ -467,7 +475,7 @@
public interface ScrollHandler extends EventHandler {
/**
* Fired when the browser window is scrolled.
- *
+ *
* @param event the event
*/
void onWindowScroll(Window.ScrollEvent event);
@@ -505,7 +513,7 @@
/**
* Adds a {@link CloseEvent} handler.
- *
+ *
* @param handler the handler
* @return returns the handler registration
*/
@@ -516,7 +524,7 @@
/**
* Adds a {@link ResizeEvent} handler.
- *
+ *
* @param handler the handler
* @return returns the handler registration
*/
@@ -528,10 +536,10 @@
/**
* Adds a listener to receive window closing events.
- *
+ *
+ * @param listener the listener to be informed when the window is closing
* @deprecated use {@link Window#addWindowClosingHandler(ClosingHandler)} and
* {@link Window#addCloseHandler(CloseHandler)} instead
- * @param listener the listener to be informed when the window is closing
*/
@Deprecated
public static void addWindowCloseListener(WindowCloseListener listener) {
@@ -540,7 +548,7 @@
/**
* Adds a {@link Window.ClosingEvent} handler.
- *
+ *
* @param handler the handler
* @return returns the handler registration
*/
@@ -552,7 +560,7 @@
/**
* Adds a listener to receive window resize events.
- *
+ *
* @param listener the listener to be informed when the window is resized
* @deprecated use {@link Window#addResizeHandler(ResizeHandler)} instead
*/
@@ -563,7 +571,7 @@
/**
* Adds a {@link Window.ScrollEvent} handler.
- *
+ *
* @param handler the handler
* @return returns the handler registration
*/
@@ -576,7 +584,7 @@
/**
* Adds a listener to receive window scroll events.
- *
+ *
* @param listener the listener to be informed when the window is scrolled
* @deprecated use {@link Window#addWindowScrollHandler(ScrollHandler)}
* instead
@@ -588,7 +596,7 @@
/**
* Displays a message in a modal dialog box.
- *
+ *
* @param msg the message to be displayed.
*/
public static native void alert(String msg) /*-{
@@ -598,7 +606,7 @@
/**
* Displays a message in a modal dialog box, along with the standard 'OK' and
* 'Cancel' buttons.
- *
+ *
* @param msg the message to be displayed.
* @return <code>true</code> if 'OK' is clicked, <code>false</code> if
* 'Cancel' is clicked.
@@ -611,7 +619,7 @@
* Use this method to explicitly disable the window's scrollbars. Applications
* that choose to resize their user-interfaces to fit within the window's
* client area will normally want to disable window scrolling.
- *
+ *
* @param enable <code>false</code> to disable window scrolling
*/
public static void enableScrolling(boolean enable) {
@@ -621,7 +629,7 @@
/**
* Gets the height of the browser window's client area excluding the scroll
* bar.
- *
+ *
* @return the window's client height
*/
public static int getClientHeight() {
@@ -631,7 +639,7 @@
/**
* Gets the width of the browser window's client area excluding the vertical
* scroll bar.
- *
+ *
* @return the window's client width
*/
public static int getClientWidth() {
@@ -640,7 +648,7 @@
/**
* Gets the window's scroll left.
- *
+ *
* @return window's scroll left
*/
public static int getScrollLeft() {
@@ -649,7 +657,7 @@
/**
* Get the window's scroll top.
- *
+ *
* @return the window's scroll top
*/
public static int getScrollTop() {
@@ -658,7 +666,7 @@
/**
* Gets the browser window's current title.
- *
+ *
* @return the window's title.
*/
public static native String getTitle() /*-{
@@ -674,9 +682,9 @@
* </p>
*
* @param dx A positive or a negative number that specifies how many pixels
- * to move the left edge by
+ * to move the left edge by
* @param dy A positive or a negative number that specifies how many
- * pixels to move the top edge by
+ * pixels to move the top edge by
*/
public static native void moveBy(int dx, int dy) /*-{
$wnd.moveBy(dx, dy);
@@ -700,9 +708,9 @@
* Opens a new browser window. The "name" and "features" arguments are
* specified <a href=
* 'http://developer.mozilla.org/en/docs/DOM:window.open'>here</a>.
- *
- * @param url the URL that the new window will display
- * @param name the name of the window (e.g. "_blank")
+ *
+ * @param url the URL that the new window will display
+ * @param name the name of the window (e.g. "_blank")
* @param features the features to be enabled/disabled on this window
*/
public static native void open(String url, String name, String features) /*-{
@@ -720,8 +728,8 @@
/**
* Displays a request for information in a modal dialog box, along with the
* standard 'OK' and 'Cancel' buttons.
- *
- * @param msg the message to be displayed
+ *
+ * @param msg the message to be displayed
* @param initialValue the initial value in the dialog's text field
* @return the value entered by the user if 'OK' was pressed, or
* <code>null</code> if 'Cancel' was pressed
@@ -732,7 +740,7 @@
/**
* Removes a window closing listener.
- *
+ *
* @param listener the listener to be removed
*/
@Deprecated
@@ -742,7 +750,7 @@
/**
* Removes a window resize listener.
- *
+ *
* @param listener the listener to be removed
*/
@Deprecated
@@ -752,7 +760,7 @@
/**
* Removes a window scroll listener.
- *
+ *
* @param listener the listener to be removed
*/
@Deprecated
@@ -770,10 +778,10 @@
* by Window.open() with a supplied width and height.
* </p>
*
- * @param width A positive or a negative number that specifies how many pixels
- * to resize the width by
+ * @param width A positive or a negative number that specifies how many pixels
+ * to resize the width by
* @param height A positive or a negative number that specifies how many
- * pixels to resize the height by
+ * pixels to resize the height by
*/
public static native void resizeBy(int width, int height) /*-{
$wnd.resizeBy(width, height);
@@ -786,7 +794,7 @@
* by Window.open() with a supplied width and height.
* </p>
*
- * @param width The width of the window, in pixels
+ * @param width The width of the window, in pixels
* @param height The height of the window, in pixels
*/
public static native void resizeTo(int width, int height) /*-{
@@ -795,9 +803,9 @@
/**
* Scroll the window to the specified position.
- *
+ *
* @param left the left scroll position
- * @param top the top scroll position
+ * @param top the top scroll position
*/
public static native void scrollTo(int left, int top) /*-{
$wnd.scrollTo(left, top);
@@ -808,7 +816,7 @@
* sometimes necessary to do this because some browsers, such as Internet
* Explorer, add margins by default, which can confound attempts to resize
* panels to fit exactly within the window.
- *
+ *
* @param size the window's new margin size, in CSS units.
*/
public static native void setMargin(String size) /*-{
@@ -827,7 +835,7 @@
/**
* Sets the browser window's title.
- *
+ *
* @param title the new window title.
*/
public static native void setTitle(String title) /*-{
@@ -871,9 +879,9 @@
/**
* Adds this handler to the Window.
- *
- * @param <H> the type of handler to add
- * @param type the event type
+ *
+ * @param <H> the type of handler to add
+ * @param type the event type
* @param handler the handler
* @return {@link HandlerRegistration} used to remove the handler
*/
@@ -884,7 +892,7 @@
/**
* Fires an event.
- *
+ *
* @param event the event
*/
private static void fireEvent(GwtEvent<?> event) {
@@ -903,6 +911,12 @@
private static void maybeInitializeCloseHandlers() {
if (GWT.isClient() && !closeHandlersInitialized) {
impl.initWindowCloseHandler();
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ impl.disposeWindowCloseHandlers();
+ }
+ });
closeHandlersInitialized = true;
}
}
@@ -910,6 +924,12 @@
private static void maybeInitializeResizeHandlers() {
if (GWT.isClient() && !resizeHandlersInitialized) {
impl.initWindowResizeHandler();
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ impl.disposeWindowResizeHandlers();
+ }
+ });
resizeHandlersInitialized = true;
}
}
@@ -917,6 +937,12 @@
private static void maybeInitializeScrollHandlers() {
if (GWT.isClient() && !scrollHandlersInitialized) {
impl.initWindowScrollHandler();
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ impl.disposeWindowScrollHandlers();
+ }
+ });
scrollHandlersInitialized = true;
}
}
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImpl.java b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
index c0bdbf9..c5b8187 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
@@ -15,7 +15,12 @@
*/
package com.google.gwt.user.client.impl;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.impl.Disposable;
+import com.google.gwt.core.client.impl.Impl;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NodeList;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
@@ -28,6 +33,37 @@
protected static boolean eventSystemIsInitialized;
/**
+ * Registers a raw DOM event listener to be cleaned up when the module is unloaded.
+ */
+ public static native void addDisposableEvent(com.google.gwt.dom.client.Element elem, String event,
+ JavaScriptObject handler, boolean capture) /*-{
+ elem.__gwt_disposeEvent = elem.__gwt_disposeEvent || [];
+ elem.__gwt_disposeEvent.push({event: event, handler: handler, capture: capture});
+ }-*/;
+
+ /**
+ * Scan all DOM elements looking for event listeners from our module and remove event listeners from them.
+ * @param dom
+ */
+ public static void cleanupDOM(DOMImpl dom) {
+ NodeList<com.google.gwt.dom.client.Element> allElements = Document.get().getElementsByTagName("*");
+ for (int i = 0; i < allElements.getLength(); i++) {
+ com.google.gwt.dom.client.Element elem = allElements.getItem(i);
+ Element userElem = (Element) elem;
+ if (dom.getEventsSunk(userElem) != 0) {
+ dom.sinkEvents(userElem, 0);
+ }
+ EventListener listener = dom.getEventListener(userElem);
+ // nulls out event listener if and only if it was assigned from our module
+ if (GWT.isScript() && listener != null && isMyListener(listener)) {
+ dom.setEventListener(userElem, null);
+ }
+ // cleans up DOM-style addEventListener registered handlers
+ maybeRemoveDisposableEvent(elem);
+ }
+ }
+
+ /**
* Returns <code>true</code>if the object is an instance of EventListener and
* the object belongs to this module.
*/
@@ -46,6 +82,17 @@
&& (object instanceof com.google.gwt.user.client.EventListener);
}
+ private static native void maybeRemoveDisposableEvent(com.google.gwt.dom.client.Element elem) /*-{
+ var diEvents = elem.__gwt_disposeEvent;
+ if (diEvents) {
+ for (var i = 0, l = diEvents.length; i < l; i++) {
+ var diEvent = diEvents[i];
+ elem.removeEventListener(diEvent.event, diEvent.handler, diEvent.capture);
+ elem.__gwt_disposeEvent = null;
+ }
+ }
+ }-*/;
+
public native void eventCancelBubble(Event evt, boolean cancel) /*-{
evt.cancelBubble = cancel;
}-*/;
@@ -119,6 +166,13 @@
public void maybeInitializeEventSystem() {
if (!eventSystemIsInitialized) {
initEventSystem();
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ disposeEventSystem();
+ cleanupDOM(DOMImpl.this);
+ }
+ });
eventSystemIsInitialized = true;
}
}
@@ -135,6 +189,8 @@
public abstract void sinkEvents(Element elem, int eventBits);
+ protected abstract void disposeEventSystem();
+
/**
* Initializes the event dispatch system.
*/
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java b/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java
index 9f62b5a..eb08c98 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplMozilla.java
@@ -23,6 +23,16 @@
class DOMImplMozilla extends DOMImplStandard {
@Override
+ public void disposeEvents(Element elem) {
+ super.disposeEvents(elem);
+ disposeEventsMozilla(elem);
+ }
+
+ public native void disposeEventsMozilla(Element elem) /*-{
+ elem.removeEventListener('DOMMouseScroll', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
+ }-*/;
+
+ @Override
public void sinkEvents(Element elem, int bits) {
super.sinkEvents(elem, bits);
sinkEventsMozilla(elem, bits);
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
index 1140c23..c2ce06a 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
@@ -39,6 +39,10 @@
private static JavaScriptObject dispatchUnhandledEvent;
+ public void disposeEvents(Element elem) {
+ sinkEventsImpl(elem, 0);
+ }
+
@Override
public Element eventGetFromElement(Event evt) {
if (evt.getType().equals(BrowserEvents.MOUSEOVER)) {
@@ -49,9 +53,9 @@
return evt.getEventTarget().cast();
}
- return null;
+ return null;
}
-
+
@Override
public Element eventGetToElement(Event evt) {
if (evt.getType().equals(BrowserEvents.MOUSEOVER)) {
@@ -64,7 +68,7 @@
return null;
}
-
+
@Override
public native Element getChild(Element elem, int index) /*-{
var count = 0, child = elem.firstChild;
@@ -140,7 +144,7 @@
maybeInitializeEventSystem();
sinkBitlessEventImpl(elem, eventTypeName);
}
-
+
@Override
public void sinkEvents(Element elem, int bits) {
maybeInitializeEventSystem();
@@ -148,6 +152,31 @@
}
@Override
+ protected native void disposeEventSystem() /*-{
+ $wnd.removeEventListener('click', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('dblclick', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('mousedown', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('mouseup', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('mousemove', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('mouseover', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('mouseout', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('mousewheel', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('keydown', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedEvent, true);
+ $wnd.removeEventListener('keyup', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedEvent, true);
+ $wnd.removeEventListener('keypress', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedEvent, true);
+
+ // Touch and gesture events are not actually mouse events, but we treat
+ // them as such, so that DOM#setCapture() and DOM#releaseCapture() work.
+ $wnd.removeEventListener('touchstart', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('touchmove', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('touchend', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('touchcancel', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('gesturestart', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('gesturechange', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ $wnd.removeEventListener('gestureend', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
+ }-*/;
+
+ @Override
protected native void initEventSystem() /*-{
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedEvent = $entry(function(evt) {
if (!@com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)(evt)) {
@@ -194,7 +223,7 @@
evt.stopPropagation();
}
}
- }
+ }
});
$wnd.addEventListener('click', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent, true);
@@ -247,8 +276,8 @@
case "ended":
case "progress":
// First call removeEventListener, so as not to add the same event listener more than once
- elem.removeEventListener(eventTypeName, @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
- elem.addEventListener(eventTypeName, @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
+ elem.removeEventListener(eventTypeName, @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
+ elem.addEventListener(eventTypeName, @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
break;
default:
// catch missing cases
@@ -260,7 +289,7 @@
var chMask = (elem.__eventBits || 0) ^ bits;
elem.__eventBits = bits;
if (!chMask) return;
-
+
if (chMask & 0x00001) elem.onclick = (bits & 0x00001) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
if (chMask & 0x00002) elem.ondblclick = (bits & 0x00002) ?
@@ -295,25 +324,25 @@
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchUnhandledEvent : null;
if (chMask & 0x10000) elem.onerror = (bits & 0x10000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x20000) elem.onmousewheel = (bits & 0x20000) ?
+ if (chMask & 0x20000) elem.onmousewheel = (bits & 0x20000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x40000) elem.oncontextmenu = (bits & 0x40000) ?
+ if (chMask & 0x40000) elem.oncontextmenu = (bits & 0x40000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x80000) elem.onpaste = (bits & 0x80000) ?
+ if (chMask & 0x80000) elem.onpaste = (bits & 0x80000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x100000) elem.ontouchstart = (bits & 0x100000) ?
+ if (chMask & 0x100000) elem.ontouchstart = (bits & 0x100000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x200000) elem.ontouchmove = (bits & 0x200000) ?
+ if (chMask & 0x200000) elem.ontouchmove = (bits & 0x200000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x400000) elem.ontouchend = (bits & 0x400000) ?
+ if (chMask & 0x400000) elem.ontouchend = (bits & 0x400000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x800000) elem.ontouchcancel= (bits & 0x800000) ?
+ if (chMask & 0x800000) elem.ontouchcancel= (bits & 0x800000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x1000000) elem.ongesturestart =(bits & 0x1000000) ?
+ if (chMask & 0x1000000) elem.ongesturestart =(bits & 0x1000000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x2000000) elem.ongesturechange =(bits & 0x2000000) ?
+ if (chMask & 0x2000000) elem.ongesturechange =(bits & 0x2000000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
- if (chMask & 0x4000000) elem.ongestureend = (bits & 0x4000000) ?
+ if (chMask & 0x4000000) elem.ongestureend = (bits & 0x4000000) ?
@com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
}-*/;
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
index 94d196d..6ea47ff 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
@@ -229,6 +229,11 @@
sinkEventsImpl(elem, bits);
}
+ @Override
+ protected void disposeEventSystem() {
+ // TODO(cromwellian) support shutdown of event system if possible
+ }
+
private native void releaseCaptureImpl(Element elem) /*-{
elem.releaseCapture();
}-*/;
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImpl.java b/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
index 8406763..af675cd 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.user.client.impl;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
@@ -58,6 +59,8 @@
HistoryImpl.token = token;
}
+ private JavaScriptObject oldHandler;
+
private HandlerManager handlers = new HandlerManager(null);
/**
@@ -71,6 +74,10 @@
return handlers.addHandler(ValueChangeEvent.getType(), handler);
}
+ public native void dispose() /*-{
+ $wnd.onhashchange = this.@com.google.gwt.user.client.impl.HistoryImpl::oldHandler;
+ }-*/;
+
public native String encodeFragment(String fragment) /*-{
// encodeURI() does *not* encode the '#' character.
return encodeURI(fragment).replace("#", "%23");
@@ -104,7 +111,7 @@
var historyImpl = this;
- var oldHandler = $wnd.onhashchange;
+ historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::oldHandler = $wnd.onhashchange;
$wnd.onhashchange = $entry(function() {
var token = '', hash = $wnd.location.hash;
@@ -113,7 +120,7 @@
}
historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
-
+ var oldHandler = historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::oldHandler;
if (oldHandler) {
oldHandler();
}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java b/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java
index 9a530fc..2015f7d 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java
@@ -46,7 +46,7 @@
});
var checkHistoryCycle = function() {
- $wnd.setTimeout(checkHistoryCycle, 250);
+ @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(checkHistoryCycle, 250);
checkHistory();
}
diff --git a/user/src/com/google/gwt/user/client/impl/WindowImpl.java b/user/src/com/google/gwt/user/client/impl/WindowImpl.java
index 71e625e..0d288f1 100644
--- a/user/src/com/google/gwt/user/client/impl/WindowImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/WindowImpl.java
@@ -15,11 +15,30 @@
*/
package com.google.gwt.user.client.impl;
+import com.google.gwt.core.client.JavaScriptObject;
+
/**
* Native implementation associated with
* {@link com.google.gwt.user.client.Window}.
*/
public class WindowImpl {
+ private JavaScriptObject oldOnResize;
+ private JavaScriptObject oldOnScroll;
+ private JavaScriptObject oldOnBeforeUnload;
+ private JavaScriptObject oldOnUnload;
+
+ public native void disposeWindowCloseHandlers() /*-{
+ $wnd.onbeforeunload = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnBeforeUnload;
+ $wnd.onunload = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnUnload;
+ }-*/;
+
+ public native void disposeWindowResizeHandlers() /*-{
+ $wnd.onresize = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnResize;
+ }-*/;
+
+ public native void disposeWindowScrollHandlers() /*-{
+ $wnd.onscroll = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnScroll;
+ }-*/;
public native String getHash() /*-{
return $wnd.location.hash;
@@ -30,8 +49,8 @@
}-*/;
public native void initWindowCloseHandler() /*-{
- var oldOnBeforeUnload = $wnd.onbeforeunload;
- var oldOnUnload = $wnd.onunload;
+ var oldOnBeforeUnload = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnBeforeUnload = $wnd.onbeforeunload;
+ var oldOnUnload = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnUnload = $wnd.onunload;
// Old mozilla doesn't like $entry's explicit return statement and
// will always pop up a confirmation dialog. This is worked around by
@@ -69,7 +88,7 @@
}-*/;
public native void initWindowResizeHandler() /*-{
- var oldOnResize = $wnd.onresize;
+ var oldOnResize = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnResize = $wnd.onresize;
$wnd.onresize = $entry(function(evt) {
try {
@com.google.gwt.user.client.Window::onResize()();
@@ -80,7 +99,7 @@
}-*/;
public native void initWindowScrollHandler() /*-{
- var oldOnScroll = $wnd.onscroll;
+ var oldOnScroll = this.@com.google.gwt.user.client.impl.WindowImpl::oldOnScroll = $wnd.onscroll;
$wnd.onscroll = $entry(function(evt) {
try {
@com.google.gwt.user.client.Window::onScroll()();
diff --git a/user/src/com/google/gwt/user/client/ui/PotentialElement.java b/user/src/com/google/gwt/user/client/ui/PotentialElement.java
index 83d06d9..1f68985 100644
--- a/user/src/com/google/gwt/user/client/ui/PotentialElement.java
+++ b/user/src/com/google/gwt/user/client/ui/PotentialElement.java
@@ -16,9 +16,9 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.builder.shared.HtmlElementBuilder;
import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
+import com.google.gwt.dom.builder.shared.HtmlElementBuilder;
+import com.google.gwt.dom.client.Element;
/**
* EXPERIMENTAL and subject to change. Do not use this in production code.
diff --git a/user/src/com/google/gwt/user/client/ui/RootPanel.java b/user/src/com/google/gwt/user/client/ui/RootPanel.java
index ac2797a..8d22da0 100644
--- a/user/src/com/google/gwt/user/client/ui/RootPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/RootPanel.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.impl.Disposable;
+import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.dom.client.BodyElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
@@ -259,6 +261,12 @@
}-*/;
private static void hookWindowClosing() {
+ Impl.scheduleDispose(new Disposable() {
+ @Override
+ public void dispose() {
+ detachWidgets();
+ }
+ });
// Catch the window closing event.
Window.addCloseHandler(new CloseHandler<Window>() {
public void onClose(CloseEvent<Window> closeEvent) {
diff --git a/user/src/com/google/gwt/user/client/ui/impl/FocusImplStandard.java b/user/src/com/google/gwt/user/client/ui/impl/FocusImplStandard.java
index be1d0b4..ccb18e6 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/FocusImplStandard.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/FocusImplStandard.java
@@ -56,7 +56,9 @@
// involving the div and input). This also allows us to share a single
// set of handlers among every focusable item.
input.addEventListener('focus', focusHandler, false);
-
+ // We will scan for these later to deregister them
+ @com.google.gwt.user.client.impl.DOMImpl::addDisposableEvent(*)(input,
+ 'focus', focusHandler, false);
div.appendChild(input);
return div;
}-*/;
diff --git a/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java b/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
index 19a1d68..8783b08 100644
--- a/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
+++ b/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
@@ -161,7 +161,7 @@
*/
public final native void clearOnReadyStateChange() /*-{
var self = this;
- $wnd.setTimeout(function() {
+ @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
// Using a function literal here leaks memory on ie6
// Using the same function object kills HtmlUnit
self.onreadystatechange = new Function();