Adds event dispatch extension methods to DOMImplStandard Fixes issue 8379 Change-Id: I7df2678ab26e703b3db437c81ccc8d768b44917e
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 6bd383e..f4fb29a 100644 --- a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java +++ b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
@@ -15,6 +15,7 @@ */ 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.dom.client.BrowserEvents; import com.google.gwt.dom.client.Element; @@ -26,13 +27,79 @@ * by those browsers that come a bit closer to supporting a common standard (ie, * not legacy IEs). */ -abstract class DOMImplStandard extends DOMImpl { +public abstract class DOMImplStandard extends DOMImpl { + + /** + * Adds custom bitless event dispatchers to GWT. If no specific event dispatcher supplied for an + * event, the default dispatcher is used. + * <p> Example usage: + * <pre> + * static { + * DOMImplStandard.addBitlessEventDispatchers(getMyCustomDispatchers()); + * } + * + * private static native JavaScriptObject getMyCustomDispatchers() /*-{ + * return { + * click: @com.xxx.YYY::myCustomDispatcher(*), + * ... + * }; + * }-* /; + * </pre> + * + * <p> Note that although this method is public for extensions, it is subject to change in + * different releases. + * + * @param eventMap an object that provides dispatching methods keyed with the name of the event + */ + public static void addBitlessEventDispatchers(JavaScriptObject eventMap) { + ensureInit(); + bitlessEventDispatchers.merge(eventMap); + } + + /** + * Adds custom capture event dispatchers to GWT. + * <p> Example usage: + * <pre> + * static { + * if (isIE10Plus())) { + * DOMImplStandard.addCaptureEventDispatchers(getMsPointerCaptureDispatchers()); + * } + * } + * + * private static native JavaScriptObject getMsPointerCaptureDispatchers() /*-{ + * return { + * MSPointerDown: @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent(*), + * MSPointerUp: @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent(*), + * ... + * }; + * }-* /; + * </pre> + * + * <p> Note that although this method is public for extensions, it is subject to change in + * different releases. + * + * @param eventMap an object that provides dispatching methods keyed with the name of the event + */ + public static void addCaptureEventDispatchers(JavaScriptObject eventMap) { + ensureInit(); + captureEventDispatchers.merge(eventMap); + } + + private static void ensureInit() { + if (eventSystemIsInitialized) { + throw new IllegalStateException("Event system already initialized"); + } + + // Ensure that any default extensions for the browser is registered via + // static initializers in deferred binding of DOMImpl: + GWT.create(DOMImpl.class); + } private static Element captureElem; - private static JavaScriptObject bitlessEventDispatchers = getBitlessEventDispatchers(); + private static EventMap bitlessEventDispatchers = getBitlessEventDispatchers(); - private static JavaScriptObject captureEventDispatchers = getCaptureEventDispatchers(); + private static EventMap captureEventDispatchers = getCaptureEventDispatchers(); @Deprecated // We no longer want any external JSNI dependencies private static JavaScriptObject dispatchEvent; @@ -159,7 +226,7 @@ @com.google.gwt.user.client.impl.DOMImplStandard::dispatchUnhandledEvent = $entry(@com.google.gwt.user.client.impl.DOMImplStandard::dispatchUnhandledEvent(*)); - var foreach = @com.google.gwt.user.client.impl.DOMImplStandard::foreach(*); + var foreach = @com.google.gwt.user.client.impl.EventMap::foreach(*); // Ensure $entry for bitless event dispatchers var bitlessEvents = @com.google.gwt.user.client.impl.DOMImplStandard::bitlessEventDispatchers; @@ -175,7 +242,7 @@ @Override protected native void disposeEventSystem() /*-{ - var foreach = @com.google.gwt.user.client.impl.DOMImplStandard::foreach(*); + var foreach = @com.google.gwt.user.client.impl.EventMap::foreach(*); // Remove capture event listeners foreach(captureEvents, function(e, fn) { $wnd.removeEventListener(e, fn, true); }); @@ -290,7 +357,7 @@ } } - private static native JavaScriptObject getBitlessEventDispatchers() /*-{ + private static native EventMap getBitlessEventDispatchers() /*-{ return { _default_: @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent(*), dragenter: @com.google.gwt.user.client.impl.DOMImplStandard::dispatchDragEvent(*), @@ -298,7 +365,7 @@ }; }-*/; - private static native JavaScriptObject getCaptureEventDispatchers() /*-{ + private static native EventMap getCaptureEventDispatchers() /*-{ return { // Mouse events click: @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent(*), @@ -325,12 +392,4 @@ gesturechange:@com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent(*), }; }-*/; - - private static native void foreach(JavaScriptObject map, JavaScriptObject fn) /*-{ - for (var e in map) { - if (map.hasOwnProperty(e)) { - fn(e, map[e]); - } - } - }-*/; }
diff --git a/user/src/com/google/gwt/user/client/impl/EventMap.java b/user/src/com/google/gwt/user/client/impl/EventMap.java new file mode 100644 index 0000000..a32ae65 --- /dev/null +++ b/user/src/com/google/gwt/user/client/impl/EventMap.java
@@ -0,0 +1,52 @@ +/* + * Copyright 2013 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.user.client.impl; + +import com.google.gwt.core.client.JavaScriptObject; + +/** + * A simple helper class to abstract event maps. + */ +class EventMap extends JavaScriptObject { + + protected EventMap() { /* Mandatory constructor for JSO */} + + /** + * Merges the key-value pairs from the parameter into this object. If a key already exists, then + * it will be overridden by the new value. + */ + public final void merge(JavaScriptObject eventMap) { + foreach(eventMap, copyTo(this)); + } + + /** + * Returns a function that copies the passed key-value to target object. + */ + private static native JavaScriptObject copyTo(EventMap target) /*-{ + return function(key, value) { target[key] = value; }; + }-*/; + + /** + * Executes a provided function over the key-value pairs of the map object. + */ + static native void foreach(JavaScriptObject map, JavaScriptObject fn) /*-{ + for (var e in map) { + if (map.hasOwnProperty(e)) { + fn(e, map[e]); + } + } + }-*/; +}
diff --git a/user/test/com/google/gwt/user/CustomEventsTest.gwt.xml b/user/test/com/google/gwt/user/CustomEventsTest.gwt.xml new file mode 100644 index 0000000..7ed3833 --- /dev/null +++ b/user/test/com/google/gwt/user/CustomEventsTest.gwt.xml
@@ -0,0 +1,17 @@ +<!-- --> +<!-- Copyright 2013 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 --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. --> + +<module type="fileset"> + <inherits name="com.google.gwt.user.User"/> +</module>
diff --git a/user/test/com/google/gwt/user/UiPart1Suite.java b/user/test/com/google/gwt/user/UiPart1Suite.java index dba4009..9052013 100644 --- a/user/test/com/google/gwt/user/UiPart1Suite.java +++ b/user/test/com/google/gwt/user/UiPart1Suite.java
@@ -16,6 +16,7 @@ package com.google.gwt.user; import com.google.gwt.junit.tools.GWTTestSuite; +import com.google.gwt.user.client.CustomEventsTest; import com.google.gwt.user.client.ui.AbsolutePanelTest; import com.google.gwt.user.client.ui.AnchorTest; import com.google.gwt.user.client.ui.ButtonTest; @@ -74,6 +75,7 @@ suite.addTestSuite(CompositeTest.class); suite.addTestSuite(CreateEventTest.class); suite.addTestSuite(CustomButtonTest.class); + suite.addTestSuite(CustomEventsTest.class); suite.addTestSuite(CustomScrollPanelTest.class); suite.addTestSuite(DateBoxTest.class); suite.addTestSuite(DatePickerTest.class);
diff --git a/user/test/com/google/gwt/user/client/CustomEventsTest.java b/user/test/com/google/gwt/user/client/CustomEventsTest.java new file mode 100644 index 0000000..9e6f26b --- /dev/null +++ b/user/test/com/google/gwt/user/client/CustomEventsTest.java
@@ -0,0 +1,147 @@ +/* + * Copyright 2013 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.user.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.junit.client.GWTTestCase; +import com.google.gwt.user.client.impl.DOMImpl; +import com.google.gwt.user.client.impl.DOMImplStandard; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.RootPanel; + +/** + * Tests standard DOM operations in the {@link DOM} class. + * <p> + * The test has its own module so that it will always be the first test executed in the page. By + * this way, we can make sure that the custom event registration always takes place before the event + * system gets initialized. + */ +public class CustomEventsTest extends GWTTestCase { + + private static class CustomHandler implements EventHandler { + void on(DomEvent<?> customEvent) { + Element el = customEvent.getRelativeElement(); + el.setTitle(el.getTitle() + "-dispatched"); + } + } + + private static class CustomEvent1 extends DomEvent<CustomHandler> { + static final Type<CustomHandler> TYPE = new Type<CustomHandler>("c1", new CustomEvent1()); + + @Override + public Type<CustomHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(CustomHandler handler) { + handler.on(this); + } + } + + private static class CustomEvent2 extends DomEvent<CustomHandler> { + static final Type<CustomHandler> TYPE = new Type<CustomHandler>("c2", new CustomEvent2()); + + @Override + public Type<CustomHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(CustomHandler handler) { + handler.on(this); + } + } + + private Button button; + + @Override + public String getModuleName() { + return "com.google.gwt.user.CustomEventsTest"; + } + + @Override + protected void gwtSetUp() throws Exception { + ensureCustomEventDispatch(); + + button = new Button(); + button.setTitle("event"); + button.addBitlessDomHandler(new CustomHandler(), CustomEvent1.TYPE); + button.addBitlessDomHandler(new CustomHandler(), CustomEvent2.TYPE); + + RootPanel.get().add(button); + } + + @Override + protected void gwtTearDown() throws Exception { + RootPanel.get().remove(button); + } + + public void testCustomEvent() { + if (!isStandard()) { + return; // Custom event support is for standard browsers (i.e. no IE 6/7/8) + } + + dispatchEvent("c1"); + assertEquals("event-dispatched", button.getTitle()); + } + + public void testCustomDispatchEvent() { + if (!isStandard()) { + return; // Custom event support is for standard browsers (i.e. no IE 6/7/8) + } + + dispatchEvent("c2"); + assertEquals("event-dispatched-custom_method", button.getTitle()); + } + + private void dispatchEvent(String evt) { + button.getElement().dispatchEvent(createCustomEvent(evt)); + } + + private static native NativeEvent createCustomEvent(String evt) /*-{ + var e = $doc.createEvent("Event"); + e.initEvent(evt, true, false); + return e; + }-*/; + + private static native JavaScriptObject getBitlessCustomDisptachers() /*-{ + return { + c2: function(evt) { + @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent(*)(evt); + evt.target.title += "-custom_method"; + } + }; + }-*/; + + private static boolean initCustomEventDispatch = true; + + private static void ensureCustomEventDispatch() { + if (initCustomEventDispatch) { + DOMImplStandard.addBitlessEventDispatchers(getBitlessCustomDisptachers()); + initCustomEventDispatch = false; + } + } + + private static boolean isStandard() { + return GWT.create(DOMImpl.class) instanceof DOMImplStandard; + } +}