gwtproject / gwt

GWT Open Source Project
http://www.gwtproject.org
1.52k stars 376 forks source link

JsValueGlue throws ClassCastException when object is passed from one frame to another in hosted mode #3024

Closed dankurka closed 9 years ago

dankurka commented 9 years ago

Originally reported on Google Code with ID 3018

Found in GWT Release: 1.5.0, 1.5.2, and 1.5.3

Detailed description:

My web page has two frames and I have a RootPanel in each frame.  When a
widget in one frame instantiates a Java object of a class I've defined
myself and then passes that object to a widget in the other frame via JSNI,
the JsValueGlue throws a ClassCastException.  This only happens in hosted
mode.  In web mode everything works fine.

Workaround if you have one:

Passing java.lang objects (Integers, Doubles, etc.) from one frame to
another does not produce this error.  My workaround is to break my object
down into java.lang objects that describe my object, pass those basic
objects over to the other frame, and then reassemble my object in the
receiving frame with these values.

Links to the relevant GWT Developer Forum posts:

http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/f5816b66dec81b05

The exception thrown is very similar to the exception in Issue 2841. 
However the case that produces this exception is different than the case
described in Issue 2841, so I'm opening a separate bug report.

Reported by wyatt.bertel on 2008-10-22 21:49:42

dankurka commented 9 years ago
This is my stack trace in GWT 1.5.3:

java.lang.ClassCastException
        at java.lang.Class.cast(Class.java:2951)
        at com.google.gwt.dev.shell.JsValueGlue.get(JsValueGlue.java:122)
        at
com.google.gwt.dev.shell.ie.SwtOleGlue.convertVariantsToObjects(SwtOleGlue.java:57)
        at com.google.gwt.dev.shell.ie.IDispatchImpl.callMethod(IDispatchImpl.java:119)
        at com.google.gwt.dev.shell.ie.IDispatchProxy.invoke(IDispatchProxy.java:155)
        at com.google.gwt.dev.shell.ie.IDispatchImpl.Invoke(IDispatchImpl.java:294)
        at com.google.gwt.dev.shell.ie.IDispatchImpl.method6(IDispatchImpl.java:194)
        at org.eclipse.swt.internal.ole.win32.COMObject.callback6(COMObject.java:117)
        at org.eclipse.swt.internal.ole.win32.COM.VtblCall(Native Method)
        at org.eclipse.swt.internal.ole.win32.IDispatch.Invoke(IDispatch.java:64)
        at org.eclipse.swt.ole.win32.OleAutomation.invoke(OleAutomation.java:493)
        at org.eclipse.swt.ole.win32.OleAutomation.invoke(OleAutomation.java:417)
        at
com.google.gwt.dev.shell.ie.ModuleSpaceIE6.doInvokeOnWindow(ModuleSpaceIE6.java:67)
        at com.google.gwt.dev.shell.ie.ModuleSpaceIE6.doInvoke(ModuleSpaceIE6.java:152)
        at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:447)
        at com.google.gwt.dev.shell.ModuleSpace.invokeNativeVoid(ModuleSpace.java:248)
        at
com.google.gwt.dev.shell.JavaScriptHost.invokeNativeVoid(JavaScriptHost.java:107)
        at test.frames.client.Sender.sendNative(Sender.java)
        at test.frames.client.Sender.send(Sender.java:25)
        at test.frames.client.Sender.access$0(Sender.java:22)
        at test.frames.client.Sender$1.onClick(Sender.java:16)
        at
com.google.gwt.user.client.ui.ClickListenerCollection.fireClick(ClickListenerCollection.java:34)
        at com.google.gwt.user.client.ui.FocusWidget.onBrowserEvent(FocusWidget.java:102)
        at com.google.gwt.user.client.DOM.dispatchEventImpl(DOM.java:1308)
        at com.google.gwt.user.client.DOM.dispatchEventAndCatch(DOM.java:1287)
        at com.google.gwt.user.client.DOM.dispatchEvent(DOM.java:1255)
        at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
        at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
        at com.google.gwt.dev.shell.ie.IDispatchImpl.callMethod(IDispatchImpl.java:126)
        at com.google.gwt.dev.shell.ie.IDispatchProxy.invoke(IDispatchProxy.java:155)
        at com.google.gwt.dev.shell.ie.IDispatchImpl.Invoke(IDispatchImpl.java:294)
        at com.google.gwt.dev.shell.ie.IDispatchImpl.method6(IDispatchImpl.java:194)
        at org.eclipse.swt.internal.ole.win32.COMObject.callback6(COMObject.java:117)
        at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method)
        at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:1925)
        at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2966)
        at com.google.gwt.dev.GWTShell.pumpEventLoop(GWTShell.java:720)
        at com.google.gwt.dev.GWTShell.run(GWTShell.java:593)
        at com.google.gwt.dev.GWTShell.main(GWTShell.java:357)

Reported by wyatt.bertel on 2008-10-22 21:51:18

dankurka commented 9 years ago
This example produces the exception:

-------------------------------------
My EntryPoint class creates the appropriate Widget depending on which frame it's in:

package test.frames.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RootPanel;

public class FramesEntryPoint implements EntryPoint {

    public void onModuleLoad() {
        if (RootPanel.get("LeftFrame") != null) {
            // This executes on the left side of Frames.html:
            Sender sender = new Sender();
            RootPanel.get().add(sender);
        } else if (RootPanel.get("RightFrame") != null) {
            // This executes on the right side of Frames.html:
            Receiver receiver = new Receiver();
            RootPanel.get().add(receiver);
        } else if (RootPanel.get("Both") != null) {
            // This only executes if you hit Both.html 
            // instead of Frames.html:
            Sender sender = new Sender();
            Receiver receiver = new Receiver();
            HorizontalPanel hp = new HorizontalPanel();
            hp.add(sender);
            hp.add(receiver);
            RootPanel.get().add(hp);
        }
    }
-------------------------------------
This is the object that is passed from one frame to another:

package test.frames.client;

public class MyObject {
    private int number;

    public MyObject(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }
}

-------------------------------------
The Sender creates a MyObject and sends it to the other frame:

package test.frames.client;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class Sender extends VerticalPanel {

    private int messageNumber = 0;

    public Sender() {
        Button sendButton = new Button("Send");
        sendButton.addClickListener(new ClickListener() {
            public void onClick(Widget arg0) {
                send();
            }
        });
        add(sendButton);
    }

    private void send() {
        MyObject myObject = new MyObject(messageNumber++);
        sendNative(myObject);
    }

    private native void sendNative(MyObject myObject) /*-{
        if (top.receive != null) {
            top.receive(myObject);
        }
    }-*/;
}

-------------------------------------
The Receiver lives in the second frame and receives the MyObject:

package test.frames.client;

import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;

class Receiver extends SimplePanel {

    public Receiver() {
        registerNativeFunction(this);
    }

    private native void registerNativeFunction(Receiver receiver) /*-{
        top.receive = function (myObject) {

receiver.@test.frames.client.Receiver::display(Ltest/frames/client/MyObject;)(myObject);
        };
    }-*/;

    private void display(MyObject myObject) {
        setWidget(new Label("Message: "+myObject.getNumber()));
    }
}

-------------------------------------
Frames.html: this is what you point the GWT shell at:

<html>
  <head>
    <title>GwtFrames</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
<frameset cols="50%,50%">
    <frame id="TheLeftFrame" src="Left.html"></frame>
    <frame id="TheRightFrame" src="Right.html"></frame>
</frameset>
</html>
-------------------------------------
Left.html:

<html>
    <head>
        <meta name='gwt:module' content='test.frames.FramesTest=test.frames.FramesTest'>
    </head>
    <body>
        <script language="javascript"
src="test.frames.FramesTest/test.frames.FramesTest.nocache.js"></script>
        <div id="LeftFrame"/>
    </body>
</html>
-------------------------------------
Right.html:

<html>
    <head>
        <meta name='gwt:module' content='test.frames.FramesTest=test.frames.FramesTest'>
    </head>
    <body>
        <script language="javascript"
src="test.frames.FramesTest/test.frames.FramesTest.nocache.js"></script>
        <div id="RightFrame"/>
    </body>
</html>
-------------------------------------
Both.html: if you point the GWT shell at this (instead of at
Frames.html), both Sender and Receiver live in the same html body and
you don't see the error:

<html>
    <head>
        <meta name='gwt:module'
content='org.gwtsandbox.frames.Frames=org.gwtsandbox.frames.Frames'>
    </head>
    <body>
        <script language="javascript"
src="org.gwtsandbox.frames.Frames/org.gwtsandbox.frames.Frames.nocache.js"></script>
        <div id="Both"/>
    </body>
</html>
------------------------------------- 

Reported by wyatt.bertel on 2008-10-22 21:58:38

dankurka commented 9 years ago
Additional notes:
1. If the Sender and Receiver live in the same RootPanel (i.e. in the same frame) the
ClassCastException is not thrown in hosted mode.
2. Passing java.util.Dates between frames is safe, so I'd expand my workaround to
advise passing java.lang and java.util objects between frames.

Reported by wyatt.bertel on 2008-10-22 22:03:56

dankurka commented 9 years ago
This use case is explicitly not supported.  Two different GWT applications running in

different frames cannot be considered the same app with the same types.  The fact 
that it works in web mode is purely accidental, and works only because you are using

identical compilations in each frame.

Were you to compile two different apps and run them in different frames, it would be

unlikely to work.

Reported by scottb+legacy@google.com on 2008-10-22 22:56:38

dankurka commented 9 years ago
Roger that.  

Is my workaround supported though?  I.e. is it safe to pass java.lang.Integers,
java.util.Dates, etc. from one RootPanel to another?  Or is it accidental that that
works as well?

Reported by wyatt.bertel on 2008-10-23 07:24:33

dankurka commented 9 years ago
No, that's accidental as well.  It happens to work because in Java, the only way 
those classes can be loaded are in the bootstrap JVM classloader, where they are 
shared.

The right way to do what you're trying to do is to create plain old JS objects to 
share.  Check out 1.5's Overlay Types for a way to easily manipulate "POJSOs". :)

Reported by scottb+legacy@google.com on 2008-10-23 18:22:17

dankurka commented 9 years ago
Thank you very much! The JavaScript overlay types did the trick!  For posterity's
sake, I updated my original post on the developer forum
(http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/f5816b66dec81b05)
with a working test case.

Reported by wyatt.bertel on 2008-10-24 10:14:03