Allows multiple GWT apps to preview native events in IE.

Patch by: jlabanca
Review by: jgw
Issue: 3892



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6163 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/eclipse/reference/code-museum/Issue3892.launch b/eclipse/reference/code-museum/Issue3892.launch
new file mode 100644
index 0000000..3766ff0
--- /dev/null
+++ b/eclipse/reference/code-museum/Issue3892.launch
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/code-museum"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;code-museum&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/code-museum/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-dev-linux/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-dev-linux/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;code-museum&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.HostedMode"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl Issue3892.html&#10;com.google.gwt.museum.Issue3892Module1&#10;com.google.gwt.museum.Issue3892Module2&#10;com.google.gwt.museum.Issue3892Module3"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="code-museum"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Xmx256M&#10;-Dgwt.devjar=${gwt_devjar}&quot;"/>
+</launchConfiguration>
diff --git a/reference/code-museum/src/com/google/gwt/museum/Issue3892Module1.gwt.xml b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module1.gwt.xml
new file mode 100644
index 0000000..d83fadc
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module1.gwt.xml
@@ -0,0 +1,28 @@
+<!--                                                                        -->
+<!-- Copyright 2009 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 rename-to="issue3892Module1">
+
+  <!-- Inherit the core Web Toolkit stuff.                  -->
+  <inherits name='com.google.gwt.user.User' />
+  <inherits name='com.google.gwt.museum.Museum' />
+  <inherits name="com.google.gwt.user.theme.standard.StandardResources" />
+  <inherits name="com.google.gwt.user.theme.chrome.ChromeResources" />
+  <inherits name="com.google.gwt.user.theme.dark.DarkResources" />
+
+  <!-- Specify the app entry point class.                   -->
+  <entry-point class='com.google.gwt.museum.client.defaultmuseum.Issue3892EntryPoint1' />
+  <source path="client/common" />
+  <source path="client/defaultmuseum" />
+  <source path="client/viewer" />
+</module>
diff --git a/reference/code-museum/src/com/google/gwt/museum/Issue3892Module2.gwt.xml b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module2.gwt.xml
new file mode 100644
index 0000000..47fb0eb
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module2.gwt.xml
@@ -0,0 +1,28 @@
+<!--                                                                        -->
+<!-- Copyright 2009 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 rename-to="issue3892Module2">
+
+  <!-- Inherit the core Web Toolkit stuff.                  -->
+  <inherits name='com.google.gwt.user.User' />
+  <inherits name='com.google.gwt.museum.Museum' />
+  <inherits name="com.google.gwt.user.theme.standard.StandardResources" />
+  <inherits name="com.google.gwt.user.theme.chrome.ChromeResources" />
+  <inherits name="com.google.gwt.user.theme.dark.DarkResources" />
+
+  <!-- Specify the app entry point class.                   -->
+  <entry-point class="com.google.gwt.museum.client.defaultmuseum.Issue3892EntryPoint2" />
+  <source path="client/common" />
+  <source path="client/defaultmuseum" />
+  <source path="client/viewer" />
+</module>
diff --git a/reference/code-museum/src/com/google/gwt/museum/Issue3892Module3.gwt.xml b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module3.gwt.xml
new file mode 100644
index 0000000..0b42303
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/Issue3892Module3.gwt.xml
@@ -0,0 +1,28 @@
+<!--                                                                        -->
+<!-- Copyright 2009 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 rename-to="issue3892Module3">
+
+  <!-- Inherit the core Web Toolkit stuff.                  -->
+  <inherits name='com.google.gwt.user.User' />
+  <inherits name='com.google.gwt.museum.Museum' />
+  <inherits name="com.google.gwt.user.theme.standard.StandardResources" />
+  <inherits name="com.google.gwt.user.theme.chrome.ChromeResources" />
+  <inherits name="com.google.gwt.user.theme.dark.DarkResources" />
+
+  <!-- Specify the app entry point class.                   -->
+  <entry-point class="com.google.gwt.museum.client.defaultmuseum.Issue3892EntryPoint3" />
+  <source path="client/common" />
+  <source path="client/defaultmuseum" />
+  <source path="client/viewer" />
+</module>
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint1.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint1.java
new file mode 100644
index 0000000..dd3b799
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint1.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2009 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.museum.client.defaultmuseum;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Only a single GWT application can preview native events.
+ */
+public class Issue3892EntryPoint1 extends AbstractIssue {
+
+  public static final String BUTTON_1_ID = "Issue3892Button1";
+  public static final String BUTTON_2_ID = "Issue3892Button2";
+  public static final String BUTTON_3_ID = "Issue3892Button3";
+
+  /**
+   * The main grid used for layout.
+   */
+  private Grid grid = new Grid(1, 3);
+
+  @Override
+  public Widget createIssue() {
+    Window.alert("Module 1 loaded");
+
+    // Setup the grid.
+    grid.setHTML(0, 0, "<b>Test<b>");
+    grid.setHTML(0, 1, "<b>Description<b>");
+    grid.setHTML(0, 2, "<b>Expected Results<b>");
+    addTest(BUTTON_1_ID, "Event is not cancelled by any module.",
+        "The event will fire in the button.", false);
+    addTest(BUTTON_2_ID, "Module 1 cancels event.",
+        "The event will not fire in the button.", true);
+    addTest(BUTTON_3_ID, "Module 2 cancels event.",
+        "The event will not fire in the button.", true);
+
+    // Add the event preview.
+    Event.addNativePreviewHandler(new NativePreviewHandler() {
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        if (event.getTypeInt() == Event.ONCLICK) {
+          Element target = event.getNativeEvent().getEventTarget().cast();
+          if (BUTTON_2_ID.equals(target.getId())) {
+            event.cancel();
+            Window.alert("Click handled by module 1 and cancelled");
+          } else {
+            Window.alert("Click handled by module 1");
+          }
+        }
+      }
+    });
+
+    return grid;
+  }
+
+  @Override
+  public String getInstructions() {
+    return "After all three modules have loaded (indicated by alert boxes), "
+        + "click the buttons and verify that you see the expected results. "
+        + "For each test, all three modules should preview the event (even if "
+        + "one of the modules cancels the event).";
+  }
+
+  @Override
+  public String getSummary() {
+    return "Only a single GWT application can preview native events";
+  }
+
+  @Override
+  public boolean hasCSS() {
+    return false;
+  }
+
+  /**
+   * Add a test button to the grid.
+   * 
+   * @param buttonId the ID of the button
+   * @param description the test description
+   * @param results the expected result of the test
+   * @param isCancelled true if one of the modules will cancel the event
+   */
+  private void addTest(String buttonId, String description, String results,
+      final boolean isCancelled) {
+    int row = grid.getRowCount();
+    grid.resizeRows(row + 1);
+
+    // Add the test button.
+    Button button = new Button("Run Test", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        if (isCancelled) {
+          Window.alert("[Error] Event should have been cancelled");
+        } else {
+          Window.alert("[Success] Event successfully fired");
+        }
+      }
+    });
+    button.getElement().setId(buttonId);
+    grid.setWidget(row, 0, button);
+
+    // Add the description and expected results.
+    grid.setText(row, 1, description);
+    grid.setText(row, 2, results);
+  }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint2.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint2.java
new file mode 100644
index 0000000..1231698
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint2.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2009 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.museum.client.defaultmuseum;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+
+/**
+ * Only a single GWT application can preview native events.
+ */
+public class Issue3892EntryPoint2 implements EntryPoint {
+  public void onModuleLoad() {
+    Window.alert("Module 2 loaded");
+    Event.addNativePreviewHandler(new NativePreviewHandler() {
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        if (event.getTypeInt() == Event.ONCLICK) {
+          Element target = event.getNativeEvent().getEventTarget().cast();
+          if (Issue3892EntryPoint1.BUTTON_3_ID.equals(target.getId())) {
+            event.cancel();
+            Window.alert("Click handled by module 2 and cancelled");
+          } else {
+            Window.alert("Click handled by module 2");
+          }
+        }
+      }
+    });
+  }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint3.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint3.java
new file mode 100644
index 0000000..4f52d40
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue3892EntryPoint3.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 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.museum.client.defaultmuseum;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+
+/**
+ * Only a single GWT application can preview native events.
+ */
+public class Issue3892EntryPoint3 implements EntryPoint {
+  public void onModuleLoad() {
+    Window.alert("Module 3 loaded");
+    Event.addNativePreviewHandler(new NativePreviewHandler() {
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        if (event.getTypeInt() == Event.ONCLICK) {
+          Window.alert("Click handled by module 3");
+        }
+      }
+    });
+  }
+}
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 09509e6..af01b54 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplTrident.java
@@ -31,6 +31,23 @@
   @SuppressWarnings("unused")
   private static JavaScriptObject dispatchDblClickEvent;
 
+  /**
+   * Let every GWT app on the page preview the current event. If any app cancels
+   * the event, the event will be canceled for all apps.
+   * 
+   * @return <code>false</code> to cancel the event
+   */
+  @SuppressWarnings("unused")
+  private static native boolean previewEventImpl() /*-{
+    var isCancelled = false; 
+    for (var i = 0; i < $wnd.__gwt_globalEventArray.length; i++) {
+      if (!$wnd.__gwt_globalEventArray[i]()) {
+        isCancelled = true;
+      }
+    }
+    return !isCancelled;
+  }-*/;
+
   @Override
   public native Element eventGetFromElement(Event evt) /*-{
     // Prefer 'relatedTarget' if it's set (see createMouseEvent(), which
@@ -68,6 +85,24 @@
 
   @Override
   public native void initEventSystem() /*-{
+    // All GWT apps on the page register themselves with the globelEventArray
+    // so that the first app to handle an event can allow all apps on the page
+    // to preview it. See issue 3892 for more details.
+    //
+    // Apps cannot just mark the event as they visit it for a few reasons.
+    // First, window level event handlers fire last in IE, so the first app to
+    // cancel the event will be the last to see it. Second, window events do
+    // not support arbitrary attributes, and the only writable field is the
+    // returnValue, which has another use. Finally, window events are not
+    // comparable (ex. a=event; b=event; a!=b), so we cannot keep a list of
+    // events that have already been previewed by the current app.
+    if ($wnd.__gwt_globalEventArray == null) {
+      $wnd.__gwt_globalEventArray = new Array();
+    }
+    $wnd.__gwt_globalEventArray[$wnd.__gwt_globalEventArray.length] = function() {
+      return @com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)($wnd.event);
+    }
+
     @com.google.gwt.user.client.impl.DOMImplTrident::dispatchEvent = function() {
       // IE doesn't define event.currentTarget, so we squirrel it away here. It
       // also seems that IE won't allow you to add expandos to the event object,
@@ -76,9 +111,12 @@
       var oldEventTarget = @com.google.gwt.dom.client.DOMImplTrident::currentEventTarget;
       @com.google.gwt.dom.client.DOMImplTrident::currentEventTarget = this;
 
+      // The first GWT app on the page to handle the event allows all apps to
+      // preview it before continuing or cancelling, which is consistent with
+      // other browsers.
       if ($wnd.event.returnValue == null) {
         $wnd.event.returnValue = true;
-        if (!@com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)($wnd.event)) {
+        if (!@com.google.gwt.user.client.impl.DOMImplTrident::previewEventImpl()()) {
           @com.google.gwt.dom.client.DOMImplTrident::currentEventTarget = oldEventTarget;
           return;
         }
@@ -109,7 +147,7 @@
       } else if ($wnd.event.returnValue == null) {
         // Ensure that we preview the event even if we aren't handling it.
         $wnd.event.returnValue = true;
-        @com.google.gwt.user.client.DOM::previewEvent(Lcom/google/gwt/user/client/Event;)($wnd.event);
+        @com.google.gwt.user.client.impl.DOMImplTrident::previewEventImpl()();
       }
     };