Update the OOPHM wire protocol for the following:
- support future changes such as clients supporting multiple versions of
the wire protocol simultaneously, switching to alternate transport
mechanisms such as shared memory, and being able to detect an out-of-date
hosted.html file for developers running an external server (-noserver)
- collect additional data for upcoming OOPHM UI changes
(in particular, the top-level URL and a session key)
- split plugin initialization from connection to differentiate between
failures in those steps
- add tests for wire protocol messages
- refactor for upcoming HTMLUnit hosted mode integration
Backwards compatibility with v1 plugins is retained by keeping thd old
LoadModule message, defining a new tag for the new LoadModule message,
and checking for v1 clients during the initialization of the protocol.
Patch by: jat
Review by: amitmanjhi
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5911 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
index 3037377..975ca75 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/hosted.html
@@ -11,6 +11,7 @@
var moduleName = moduleFunc ? moduleFunc.moduleName : "unknown";
$stats({moduleName:moduleName,subSystem:'startup',evtGroup:'moduleStartup',millis:(new Date()).getTime(),type:'moduleEvalStart'});
}
+var $hostedHtmlVersion="2.0";
var gwtOnLoad;
var $hosted = "localhost:9997";
@@ -23,11 +24,30 @@
}
} catch(e) {
}
+
+function getTopWindow() {
+ var topWin = window.top;
+ while (topWin.opener) {
+ topWin = topWin.opener.top;
+ }
+ return topWin;
+}
+
+function loadIframe(url) {
+ var iframe = $doc.createElement('iframe');
+ iframe.src = "javascript:''";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+ iframe.style.borderWidth = "0px";
+ $doc.body.insertBefore(iframe, $doc.body.firstChild);
+ iframe.contentWindow.location.replace(url);
+}
+
if ($legacyHosted) {
gwtOnLoad = function(errFn, modName, modBase) {
$moduleName = modName;
$moduleBase = modBase;
- if (!external.gwtOnLoad(window, modName, "1.6")) {
+ if (!external.gwtOnLoad(window, modName, $hostedHtmlVersion)) {
if (errFn) {
errFn(modName);
}
@@ -35,7 +55,7 @@
}
window.onunload = function() {
- external.gwtOnLoad(window, null, "1.6");
+ external.gwtOnLoad(window, null, $hostedHtmlVersion);
};
} else {
// install eval wrapper on FF to avoid EvalError problem
@@ -175,29 +195,51 @@
findPluginObject,
findPluginEmbed,
];
- var found = false;
+ var topWin = getTopWindow();
+ var url = topWin.location.href;
+ if (!topWin.__gwt_SessionID) {
+ var ASCII_EXCLAMATION = 33;
+ var ASCII_TILDE = 126;
+ var chars = [];
+ for (var i = 0; i < 16; ++i) {
+ chars.push(Math.floor(ASCII_EXCLAMATION
+ + Math.random() * (ASCII_TILDE - ASCII_EXCLAMATION + 1)));
+ }
+ topWin.__gwt_SessionID = String.fromCharCode.apply(null, chars);
+ }
+ var plugin = null;
for (var i = 0; i < pluginFinders.length; ++i) {
try {
- var plugin = pluginFinders[i]();
- if (plugin != null) {
- // TODO: split connect into init/connect so we can tell plugin
- // failures from connection failures.
- if (plugin.connect($hosted, $moduleName, window)) {
- found = true;
- break;
- } else {
- if (errFn) {
- errFn(modName);
- } else {
- alert("failed to connect to hosted mode server at " + $hosted);
- }
- }
+ plugin = pluginFinders[i]();
+ if (plugin != null && plugin.init(window)) {
+ break;
}
} catch (e) {
}
}
- if (!found) {
- alert("No GWT plugin found or hosted-mode connection failed");
+ if (!plugin) {
+ // try searching for a v1 plugin for backwards compatibility
+ var found = false;
+ for (var i = 0; i < pluginFinders.length; ++i) {
+ try {
+ plugin = pluginFinders[i]();
+ if (plugin != null && plugin.connect($hosted, $moduleName, window)) {
+ return;
+ }
+ } catch (e) {
+ }
+ }
+ loadIframe("http://google-web-toolkit.googlecode.com/svn/trunk/plugins/MissingBrowserPlugin.html");
+ } else {
+ if (!plugin.connect(url, topWin.__gwt_SessionID, $hosted, $moduleName,
+ $hostedHtmlVersion)) {
+ if (errFn) {
+ errFn(modName);
+ } else {
+ alert("Plugin failed to connect to hosted mode server at " + $hosted);
+ loadIframe("http://code.google.com/p/google-web-toolkit/wiki/TroubleshootingOOPHM");
+ }
+ }
}
}
diff --git a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
index ea06563..7542206 100644
--- a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
@@ -76,8 +76,8 @@
}
public ModuleSpaceHost createModuleSpaceHost(TreeLogger logger,
- String moduleName, String userAgent, String remoteEndpoint)
- throws UnableToCompleteException {
+ String moduleName, String userAgent, String url, String sessionKey,
+ String remoteEndpoint) throws UnableToCompleteException {
throw new UnsupportedOperationException();
}
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java b/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
index 9253b34..019f268 100644
--- a/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
@@ -49,9 +49,17 @@
/**
* For OOPHM.
+ *
+ * @param logger
+ * @param moduleName
+ * @param userAgent
+ * @param url URL of top-level window (may be null for old browser plugins)
+ * @param sessionKey unique session key (may be null for old browser plugins)
+ * @param remoteEndpoint
*/
ModuleSpaceHost createModuleSpaceHost(TreeLogger logger, String moduleName,
- String userAgent, String remoteEndpoint) throws UnableToCompleteException;
+ String userAgent, String url, String sessionKey,
+ String remoteEndpoint) throws UnableToCompleteException;
TreeLogger getLogger();
diff --git a/dev/core/src/com/google/gwt/dev/util/TemporaryBufferStream.java b/dev/core/src/com/google/gwt/dev/util/TemporaryBufferStream.java
new file mode 100644
index 0000000..608cb55
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/TemporaryBufferStream.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dev.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+/**
+ * A utility for tests that allow writing to a temporary buffer and reading
+ * from the same buffer to verify that serialization/deserialization works.
+ */
+public class TemporaryBufferStream {
+
+ private ArrayList<Byte> buf = new ArrayList<Byte>();
+
+ private InputStream inputStream = new InputStream() {
+ @Override
+ public int read() throws IOException {
+ try {
+ return buf.remove(0) & 255;
+ } catch (IndexOutOfBoundsException e) {
+ return -1;
+ }
+ }
+ };
+
+ private OutputStream outputStream = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ buf.add(Byte.valueOf((byte) b));
+ }
+ };
+
+ public InputStream getInputStream() {
+ return inputStream;
+ }
+
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+}
diff --git a/dev/oophm/build.xml b/dev/oophm/build.xml
index 947f655..2d4f957 100755
--- a/dev/oophm/build.xml
+++ b/dev/oophm/build.xml
@@ -6,6 +6,23 @@
<property.ensure name="gwt.core.root" location="../core" />
<property.ensure name="gwt.core.build" location="${project.build}/../core" />
+ <fileset id="default.tests" dir="${javac.junit.out}">
+ <include name="**/com/google/**/*Test.class" />
+ </fileset>
+
+ <target name="compile.tests" depends="build" description="Compiles the test code for this project">
+ <mkdir dir="${javac.junit.out}" />
+ <gwt.javac srcdir="test" destdir="${javac.junit.out}">
+ <classpath>
+ <pathelement location="${javac.out}" />
+ <pathelement location="${gwt.core.build}/bin" />
+ <pathelement location="${gwt.core.build}/bin-tests" />
+ <pathelement location="${alldeps.jar}" />
+ <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ </classpath>
+ </gwt.javac>
+ </target>
+
<target name="compile" description="Compile all java files">
<mkdir dir="${javac.out}" />
<gwt.javac>
@@ -35,4 +52,41 @@
<delete file="${project.lib}" failonerror="false" />
</target>
+ <target name="test" depends="build, compile.tests" description="Run unit tests for this project.">
+ <!-- TODO: refactor gwt.junit so it can be reused here -->
+ <taskdef name="junit" classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask">
+ <classpath>
+ <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ <pathelement location="${gwt.tools.antlib}/ant-junit-1.6.5.jar" />
+ </classpath>
+ </taskdef>
+
+ <echo message="Writing test results to ${junit.out}/reports for ${test.cases}" />
+ <mkdir dir="${junit.out}/reports" />
+
+ <echo message="${javac.out} ${javac.junit.out}" />
+ <junit dir="${junit.out}" fork="yes" printsummary="yes" haltonfailure="true">
+ <classpath>
+ <pathelement location="test" />
+ <pathelement location="${javac.junit.out}" />
+ <pathelement location="${javac.out}" />
+ <pathelement location="${gwt.core.build}/bin" />
+ <pathelement location="${gwt.core.build}/bin-tests" />
+ <pathelement location="${alldeps.jar}" />
+ <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ <!-- Pull in gwt-dev and gwt-user sources for .gwt.xml files -->
+ <pathelement location="${gwt.root}/user/src/" />
+ <pathelement location="${gwt.root}/user/super/" />
+ <pathelement location="${gwt.root}/dev/core/super" />
+ </classpath>
+
+ <formatter type="plain" />
+ <formatter type="xml" />
+
+ <batchtest todir="${junit.out}/reports">
+ <fileset refid="default.tests" />
+ </batchtest>
+ </junit>
+ </target>
+
</project>
diff --git a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
index 9d1705c..b8fe956 100644
--- a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
+++ b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
@@ -142,14 +142,15 @@
}
public ModuleSpaceHost createModuleSpaceHost(TreeLogger mainLogger,
- String moduleName, String userAgent, String remoteSocket)
- throws UnableToCompleteException {
+ String moduleName, String userAgent, String url, String sessionKey,
+ String remoteSocket) throws UnableToCompleteException {
TreeLogger logger = mainLogger;
TreeLogger.Type maxLevel = TreeLogger.INFO;
if (mainLogger instanceof AbstractTreeLogger) {
maxLevel = ((AbstractTreeLogger) mainLogger).getMaxDetail();
}
-
+ // TODO(jat): collect different sessions into the same tab, with a
+ // dropdown to select individual module's logs
ModulePanel tab;
if (!isHeadless()) {
tab = new ModulePanel(maxLevel, moduleName, userAgent, remoteSocket,
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
index bdf36f9..85bafa4 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannel.java
@@ -26,6 +26,8 @@
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
@@ -102,9 +104,14 @@
/**
* Enumeration of message type ids.
+ *
+ * NOTE: order is important as this defines the ordinals used in the wire
+ * protocol.
*/
public enum MessageType {
- Invoke, Return, LoadModule, Quit, LoadJsni, InvokeSpecial, FreeValue;
+ INVOKE, RETURN, OLD_LOAD_MODULE, QUIT, LOAD_JSNI, INVOKE_SPECIAL, FREE_VALUE,
+ FATAL_ERROR, CHECK_VERSIONS, PROTOCOL_VERSION, CHOOSE_TRANSPORT,
+ SWITCH_TRANSPORT, LOAD_MODULE;
}
/**
@@ -152,7 +159,8 @@
Value thisObj, int dispId, Value[] args);
public abstract TreeLogger loadModule(TreeLogger logger,
- BrowserChannel channel, String moduleName, String userAgent);
+ BrowserChannel channel, String moduleName, String userAgent, String url,
+ String sessionKey);
public abstract ExceptionOrReturnValue setProperty(BrowserChannel channel,
int refId, int dispId, Value newValue);
@@ -514,6 +522,132 @@
}
/**
+ * The initial request from the client, supplies a range of supported versions
+ * and the version from hosted.html (so stale copies on an external server
+ * can be detected).
+ */
+ protected static class CheckVersionsMessage extends Message {
+
+ public static CheckVersionsMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int minVersion = stream.readInt();
+ int maxVersion = stream.readInt();
+ String hostedHtmlVersion = readUtf8String(stream);
+ return new CheckVersionsMessage(channel, minVersion, maxVersion,
+ hostedHtmlVersion);
+ }
+
+ private final int minVersion;
+
+ private final int maxVersion;
+
+ private final String hostedHtmlVersion;
+
+ public CheckVersionsMessage(BrowserChannel channel, int minVersion,
+ int maxVersion, String hostedHtmlVersion) {
+ super(channel);
+ this.minVersion = minVersion;
+ this.maxVersion = maxVersion;
+ this.hostedHtmlVersion = hostedHtmlVersion;
+ }
+
+ public String getHostedHtmlVersion() {
+ return hostedHtmlVersion;
+ }
+
+ public int getMaxVersion() {
+ return maxVersion;
+ }
+
+ public int getMinVersion() {
+ return minVersion;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.CHECK_VERSIONS.ordinal());
+ stream.writeInt(minVersion);
+ stream.writeInt(maxVersion);
+ writeUntaggedString(stream, hostedHtmlVersion);
+ stream.flush();
+ }
+ }
+
+ /**
+ * A message from the client giving a list of supported connection methods
+ * and requesting the server choose one of them to switch protocol traffic to.
+ */
+ protected static class ChooseTransportMessage extends Message {
+
+ public static ChooseTransportMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int n = stream.readInt();
+ String[] transports = new String[n];
+ for (int i = 0; i < n; ++i) {
+ transports[i] = readUtf8String(stream);
+ }
+ return new ChooseTransportMessage(channel, transports);
+ }
+
+ private final String[] transports;
+
+ public ChooseTransportMessage(BrowserChannel channel,
+ String[] transports) {
+ super(channel);
+ this.transports = transports;
+ }
+
+ public String[] getTransports() {
+ return transports;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.CHOOSE_TRANSPORT.ordinal());
+ stream.writeInt(transports.length);
+ for (String transport : transports) {
+ writeUntaggedString(stream, transport);
+ }
+ }
+ }
+
+ /**
+ * A message reporting a connection error to the client.
+ */
+ protected static class FatalErrorMessage extends Message {
+
+ public static FatalErrorMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ // NOTE: Tag has already been read.
+ String error = readUtf8String(stream);
+ return new FatalErrorMessage(channel, error);
+ }
+
+ private final String error;
+
+ public FatalErrorMessage(BrowserChannel channel, String error) {
+ super(channel);
+ this.error = error;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.FATAL_ERROR.ordinal());
+ writeUntaggedString(stream, error);
+ }
+ }
+
+ /**
* A message asking the other side to free object references. Note that there
* is no response to this message, and this must only be sent immediately
* before an Invoke or Return message.
@@ -534,7 +668,7 @@
public static void send(BrowserChannel channel, int[] ids)
throws IOException {
DataOutputStream stream = channel.getStreamToOtherSide();
- stream.writeByte(MessageType.FreeValue.ordinal());
+ stream.writeByte(MessageType.FREE_VALUE.ordinal());
stream.writeInt(ids.length);
for (int id : ids) {
stream.writeInt(id);
@@ -565,44 +699,38 @@
}
/**
- * A request from the to invoke a function on the other side.
+ * A request from the server to invoke a function on the client.
+ *
+ * Note that MessageType.INVOKE can refer to either this class
+ * or {@link InvokeOnServerMessage} depending on the direction, as the
+ * protocol is asymmetric (Java needs a dispatch ID, Javascript needs a
+ * name).
*/
- protected static class InvokeMessage extends Message {
- public static InvokeMessage receive(BrowserChannel channel)
+ protected static class InvokeOnClientMessage extends Message {
+ public static InvokeOnClientMessage receive(BrowserChannel channel)
throws IOException {
- final DataInputStream stream = channel.getStreamFromOtherSide();
+ DataInputStream stream = channel.getStreamFromOtherSide();
// NOTE: Tag has already been read.
- final int methodDispatchId = stream.readInt();
- final Value thisRef = readValue(stream);
- final int argLen = stream.readInt();
- final Value[] args = new Value[argLen];
+ String methodName = readUtf8String(stream);
+ Value thisRef = readValue(stream);
+ int argLen = stream.readInt();
+ Value[] args = new Value[argLen];
for (int i = 0; i < argLen; i++) {
args[i] = readValue(stream);
}
- return new InvokeMessage(channel, methodDispatchId, thisRef, args);
+ return new InvokeOnClientMessage(channel, methodName, thisRef, args);
}
- private final int methodDispatchId;
private final String methodName;
private final Value thisRef;
private final Value[] args;
- public InvokeMessage(BrowserChannel channel, int methodDispatchId,
- Value thisRef, Value[] args) {
- super(channel);
- this.thisRef = thisRef;
- this.methodName = null;
- this.methodDispatchId = methodDispatchId;
- this.args = args;
- }
-
- public InvokeMessage(BrowserChannel channel, String methodName,
+ public InvokeOnClientMessage(BrowserChannel channel, String methodName,
Value thisRef, Value[] args) {
super(channel);
this.thisRef = thisRef;
this.methodName = methodName;
- this.methodDispatchId = -1;
this.args = args;
}
@@ -610,10 +738,6 @@
return args;
}
- public int getMethodDispatchId() {
- return methodDispatchId;
- }
-
public String getMethodName() {
return methodName;
}
@@ -626,7 +750,7 @@
public void send() throws IOException {
final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.Invoke.ordinal());
+ stream.writeByte(MessageType.INVOKE.ordinal());
writeUntaggedString(stream, methodName);
writeValue(stream, thisRef);
stream.writeInt(args.length);
@@ -638,6 +762,69 @@
}
/**
+ * A request from the client to invoke a function on the server.
+ *
+ * Note that MessageType.INVOKE can refer to either this class
+ * or {@link InvokeOnClientMessage} depending on the direction, as the
+ * protocol is asymmetric (Java needs a dispatch ID, Javascript needs a
+ * name).
+ */
+ protected static class InvokeOnServerMessage extends Message {
+ public static InvokeOnServerMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ // NOTE: Tag has already been read.
+ int methodDispatchId = stream.readInt();
+ Value thisRef = readValue(stream);
+ int argLen = stream.readInt();
+ Value[] args = new Value[argLen];
+ for (int i = 0; i < argLen; i++) {
+ args[i] = readValue(stream);
+ }
+ return new InvokeOnServerMessage(channel, methodDispatchId, thisRef,
+ args);
+ }
+
+ private final int methodDispatchId;
+ private final Value thisRef;
+ private final Value[] args;
+
+ public InvokeOnServerMessage(BrowserChannel channel, int methodDispatchId,
+ Value thisRef, Value[] args) {
+ super(channel);
+ this.thisRef = thisRef;
+ this.methodDispatchId = methodDispatchId;
+ this.args = args;
+ }
+
+ public Value[] getArgs() {
+ return args;
+ }
+
+ public int getMethodDispatchId() {
+ return methodDispatchId;
+ }
+
+ public Value getThis() {
+ return thisRef;
+ }
+
+ @Override
+ public void send() throws IOException {
+ final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+
+ stream.writeByte(MessageType.INVOKE.ordinal());
+ stream.writeInt(methodDispatchId);
+ writeValue(stream, thisRef);
+ stream.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ writeValue(stream, args[i]);
+ }
+ stream.flush();
+ }
+ }
+
+ /**
* A request from the to invoke a function on the other side.
*/
protected static class InvokeSpecialMessage extends Message {
@@ -682,7 +869,7 @@
public void send() throws IOException {
final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.InvokeSpecial.ordinal());
+ stream.writeByte(MessageType.INVOKE_SPECIAL.ordinal());
stream.writeByte(dispatchId.ordinal());
stream.writeInt(args.length);
for (int i = 0; i < args.length; i++) {
@@ -701,14 +888,14 @@
public static LoadJsniMessage receive(BrowserChannel channel)
throws IOException {
DataInputStream stream = channel.getStreamFromOtherSide();
- String js = stream.readUTF();
+ String js = readUtf8String(stream);
return new LoadJsniMessage(channel, js);
}
public static void send(BrowserChannel channel, String js)
throws IOException {
DataOutputStream stream = channel.getStreamToOtherSide();
- stream.write(MessageType.LoadJsni.ordinal());
+ stream.write(MessageType.LOAD_JSNI.ordinal());
writeUntaggedString(stream, js);
stream.flush();
}
@@ -720,6 +907,10 @@
this.js = js;
}
+ public String getJsni() {
+ return js;
+ }
+
@Override
public boolean isAsynchronous() {
return true;
@@ -737,44 +928,43 @@
*/
protected static class LoadModuleMessage extends Message {
public static LoadModuleMessage receive(BrowserChannel channel)
- throws IOException, BrowserChannelException {
- final DataInputStream stream = channel.getStreamFromOtherSide();
- final int version = stream.readInt();
- checkProtocolVersion(version);
- final String moduleName = readUtf8String(stream);
- final String userAgent = readUtf8String(stream);
- return new LoadModuleMessage(channel, version, moduleName, userAgent);
- }
-
- private static void checkProtocolVersion(int version)
- throws BrowserChannelException {
- if (version != BROWSERCHANNEL_PROTOCOL_VERSION) {
- throw new BrowserChannelException(
- "Incompatible client version: server="
- + BROWSERCHANNEL_PROTOCOL_VERSION + ", client=" + version);
- }
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ String url = readUtf8String(stream);
+ String sessionKey = readUtf8String(stream);
+ String moduleName = readUtf8String(stream);
+ String userAgent = readUtf8String(stream);
+ return new LoadModuleMessage(channel, url, sessionKey, moduleName,
+ userAgent);
}
private final String moduleName;
private final String userAgent;
- private final int protocolVersion;
+ private final String url;
+
+ private final String sessionKey;
- public LoadModuleMessage(BrowserChannel channel, int protocolVersion,
- String moduleName, String userAgent) {
+ public LoadModuleMessage(BrowserChannel channel, String url,
+ String sessionKey, String moduleName, String userAgent) {
super(channel);
+ this.url = url;
+ this.sessionKey = sessionKey;
this.moduleName = moduleName;
this.userAgent = userAgent;
- this.protocolVersion = protocolVersion;
}
public String getModuleName() {
return moduleName;
}
- public int getProtocolVersion() {
- return protocolVersion;
+ public String getSessionKey() {
+ return sessionKey;
+ }
+
+ public String getUrl() {
+ return url;
}
public String getUserAgent() {
@@ -783,9 +973,10 @@
@Override
public void send() throws IOException {
- final DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
- stream.writeByte(MessageType.LoadModule.ordinal());
- stream.writeInt(protocolVersion);
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.LOAD_MODULE.ordinal());
+ writeUntaggedString(stream, url);
+ writeUntaggedString(stream, sessionKey);
writeUntaggedString(stream, moduleName);
writeUntaggedString(stream, userAgent);
stream.flush();
@@ -834,6 +1025,90 @@
}
/**
+ * A request from the client that the server load and initialize a given
+ * module (original v1 version).
+ */
+ protected static class OldLoadModuleMessage extends Message {
+ public static OldLoadModuleMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int protoVersion = stream.readInt();
+ String moduleName = readUtf8String(stream);
+ String userAgent = readUtf8String(stream);
+ return new OldLoadModuleMessage(channel, protoVersion, moduleName,
+ userAgent);
+ }
+
+ private final String moduleName;
+
+ private final String userAgent;
+
+ private final int protoVersion;
+
+ public OldLoadModuleMessage(BrowserChannel channel, int protoVersion,
+ String moduleName, String userAgent) {
+ super(channel);
+ this.protoVersion = protoVersion;
+ this.moduleName = moduleName;
+ this.userAgent = userAgent;
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public int getProtoVersion() {
+ return protoVersion;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.OLD_LOAD_MODULE.ordinal());
+ stream.writeInt(protoVersion);
+ writeUntaggedString(stream, moduleName);
+ writeUntaggedString(stream, userAgent);
+ stream.flush();
+ }
+ }
+
+ /**
+ * Reports the selected protocol version.
+ */
+ protected static class ProtocolVersionMessage extends Message {
+
+ public static ProtocolVersionMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ int protocolVersion = stream.readInt();
+ return new ProtocolVersionMessage(channel, protocolVersion);
+ }
+
+ private final int protocolVersion;
+
+ public ProtocolVersionMessage(BrowserChannel channel, int protocolVersion) {
+ super(channel);
+ this.protocolVersion = protocolVersion;
+ }
+
+ public int getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.PROTOCOL_VERSION.ordinal());
+ stream.writeInt(protocolVersion);
+ stream.flush();
+ }
+ }
+
+ /**
* A message signifying a soft close of the communications channel.
*/
protected static class QuitMessage extends Message {
@@ -843,7 +1118,7 @@
public static void send(BrowserChannel channel) throws IOException {
final DataOutputStream stream = channel.getStreamToOtherSide();
- stream.writeByte(MessageType.Quit.ordinal());
+ stream.writeByte(MessageType.QUIT.ordinal());
stream.flush();
}
@@ -872,7 +1147,7 @@
public static void send(BrowserChannel channel, boolean isException,
Value returnValue) throws IOException {
final DataOutputStream stream = channel.getStreamToOtherSide();
- stream.writeByte(MessageType.Return.ordinal());
+ stream.writeByte(MessageType.RETURN.ordinal());
stream.writeBoolean(isException);
writeValue(stream, returnValue);
stream.flush();
@@ -908,7 +1183,56 @@
}
}
- public static final int BROWSERCHANNEL_PROTOCOL_VERSION = 1;
+ /**
+ * A response to ChooseTransport telling the client which transport should
+ * be used for the remainder of the protocol.
+ */
+ protected static class SwitchTransportMessage extends Message {
+
+ public static SwitchTransportMessage receive(BrowserChannel channel)
+ throws IOException {
+ DataInputStream stream = channel.getStreamFromOtherSide();
+ String transport = readUtf8String(stream);
+ String transportArgs = readUtf8String(stream);
+ return new SwitchTransportMessage(channel, transport, transportArgs);
+ }
+
+ private final String transport;
+
+ private final String transportArgs;
+
+ public SwitchTransportMessage(BrowserChannel channel,
+ String transport, String transportArgs) {
+ super(channel);
+ // Change nulls to empty strings
+ if (transport == null) {
+ transport = "";
+ }
+ if (transportArgs == null) {
+ transportArgs = "";
+ }
+ this.transport = transport;
+ this.transportArgs = transportArgs;
+ }
+
+ public String getTransport() {
+ return transport;
+ }
+
+ public String getTransportArgs() {
+ return transportArgs;
+ }
+
+ @Override
+ public void send() throws IOException {
+ DataOutputStream stream = getBrowserChannel().getStreamToOtherSide();
+ stream.writeByte(MessageType.SWITCH_TRANSPORT.ordinal());
+ writeUntaggedString(stream, transport);
+ writeUntaggedString(stream, transportArgs);
+ }
+ }
+
+ public static final int BROWSERCHANNEL_PROTOCOL_VERSION = 2;
public static final int SPECIAL_CLIENTMETHODS_OBJECT = 0;
@@ -1161,13 +1485,18 @@
private Socket socket;
public BrowserChannel(Socket socket) throws IOException {
- streamFromOtherSide = new DataInputStream(new BufferedInputStream(
- socket.getInputStream()));
- streamToOtherSide = new DataOutputStream(new BufferedOutputStream(
- socket.getOutputStream()));
+ this(new BufferedInputStream(socket.getInputStream()),
+ new BufferedOutputStream(socket.getOutputStream()));
this.socket = socket;
}
+ protected BrowserChannel(InputStream inputStream, OutputStream outputStream)
+ throws IOException {
+ streamFromOtherSide = new DataInputStream(inputStream);
+ streamToOtherSide = new DataOutputStream(outputStream);
+ socket = null;
+ }
+
public void endSession() {
Utility.close(streamFromOtherSide);
Utility.close(streamToOtherSide);
@@ -1209,13 +1538,16 @@
}
public String getRemoteEndpoint() {
+ if (socket == null) {
+ return "";
+ }
return socket.getInetAddress().getCanonicalHostName() + ":"
+ socket.getPort();
}
public Value invoke(String methodName, Value vthis, Value[] vargs,
SessionHandler handler) throws IOException, BrowserChannelException {
- new InvokeMessage(this, methodName, vthis, vargs).send();
+ new InvokeOnClientMessage(this, methodName, vthis, vargs).send();
final ReturnMessage msg = reactToMessagesWhileWaitingForReturn(handler);
return msg.returnValue;
}
@@ -1226,19 +1558,19 @@
getStreamToOtherSide().flush();
MessageType messageType = Message.readMessageType(getStreamFromOtherSide());
switch (messageType) {
- case FreeValue:
+ case FREE_VALUE:
final FreeMessage freeMsg = FreeMessage.receive(this);
handler.freeValue(this, freeMsg.getIds());
break;
- case Invoke:
- final InvokeMessage imsg = InvokeMessage.receive(this);
+ case INVOKE:
+ final InvokeOnServerMessage imsg = InvokeOnServerMessage.receive(this);
ReturnMessage.send(this, handler.invoke(this, imsg.getThis(),
imsg.getMethodDispatchId(), imsg.getArgs()));
break;
- case InvokeSpecial:
+ case INVOKE_SPECIAL:
handleInvokeSpecial(handler);
break;
- case Quit:
+ case QUIT:
return;
default:
throw new BrowserChannelException("Invalid message type "
@@ -1253,18 +1585,18 @@
getStreamToOtherSide().flush();
MessageType messageType = Message.readMessageType(getStreamFromOtherSide());
switch (messageType) {
- case FreeValue:
+ case FREE_VALUE:
final FreeMessage freeMsg = FreeMessage.receive(this);
handler.freeValue(this, freeMsg.getIds());
break;
- case Return:
+ case RETURN:
return ReturnMessage.receive(this);
- case Invoke:
- final InvokeMessage imsg = InvokeMessage.receive(this);
+ case INVOKE:
+ final InvokeOnServerMessage imsg = InvokeOnServerMessage.receive(this);
ReturnMessage.send(this, handler.invoke(this, imsg.getThis(),
imsg.getMethodDispatchId(), imsg.getArgs()));
break;
- case InvokeSpecial:
+ case INVOKE_SPECIAL:
handleInvokeSpecial(handler);
break;
default:
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
index ab285e0..4b98529 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -82,8 +82,8 @@
vargs[i] = convertFromJsValue(remoteObjects, args[i]);
}
try {
- InvokeMessage invokeMessage = new InvokeMessage(this, methodName, vthis,
- vargs);
+ InvokeOnClientMessage invokeMessage = new InvokeOnClientMessage(this,
+ methodName, vthis, vargs);
invokeMessage.send();
final ReturnMessage msg = reactToMessagesWhileWaitingForReturn(handler);
Value returnValue = msg.getReturnValue();
@@ -139,21 +139,7 @@
public void run() {
try {
- MessageType type = Message.readMessageType(getStreamFromOtherSide());
- assert type == MessageType.LoadModule;
- LoadModuleMessage message = LoadModuleMessage.receive(this);
- moduleName = message.getModuleName();
- userAgent = message.getUserAgent();
- Thread.currentThread().setName(
- "Hosting " + moduleName + " for " + userAgent);
- logger = handler.loadModule(logger, this, moduleName, userAgent);
- try {
- // send LoadModule response
- ReturnMessage.send(this, false, new Value());
- reactToMessages(handler);
- } finally {
- handler.unloadModule(this, moduleName);
- }
+ processConnection();
} catch (IOException e) {
logger.log(TreeLogger.WARN, "Client connection lost", e);
} catch (BrowserChannelException e) {
@@ -265,4 +251,113 @@
break;
}
}
+
+ /**
+ * Create the requested transport and return the appropriate information so
+ * the client can connect to the same transport.
+ *
+ * @param transport transport name to create
+ * @return transport-specific arguments for the client to use in attaching
+ * to this transport
+ */
+ private String createTransport(String transport) {
+ // TODO(jat): implement support for additional transports
+ throw new UnsupportedOperationException(
+ "No alternate transports supported");
+ }
+
+ private void processConnection() throws IOException, BrowserChannelException {
+ MessageType type = Message.readMessageType(getStreamFromOtherSide());
+ // TODO(jat): add support for getting the a shim plugin downloading the
+ // real plugin via a GetRealPlugin message before CheckVersions
+ String url = null;
+ String sessionKey = null;
+ switch (type) {
+ case OLD_LOAD_MODULE:
+ // v1 client
+ OldLoadModuleMessage oldLoadModule = OldLoadModuleMessage.receive(this);
+ if (oldLoadModule.getProtoVersion() != 1) {
+ // This message type was only used in v1, so something is really
+ // broken here.
+ throw new BrowserChannelException(
+ "Old LoadModule message used, but not v1 protocol");
+ }
+ moduleName = oldLoadModule.getModuleName();
+ userAgent = oldLoadModule.getUserAgent();
+ break;
+ case CHECK_VERSIONS:
+ String connectError = null;
+ CheckVersionsMessage hello = CheckVersionsMessage.receive(this);
+ int minVersion = hello.getMinVersion();
+ int maxVersion = hello.getMaxVersion();
+ String hostedHtmlVersion = hello.getHostedHtmlVersion();
+ if (minVersion > BROWSERCHANNEL_PROTOCOL_VERSION
+ || maxVersion < BROWSERCHANNEL_PROTOCOL_VERSION) {
+ connectError = "No supported protocol version in range " + minVersion
+ + " - " + maxVersion;
+ }
+ // TODO(jat): verify hosted.html version
+ if (connectError != null) {
+ logger.log(TreeLogger.ERROR, "Connection error " + connectError, null);
+ new FatalErrorMessage(this, connectError).send();
+ return;
+ }
+ new ProtocolVersionMessage(this, BROWSERCHANNEL_PROTOCOL_VERSION).send();
+ type = Message.readMessageType(getStreamFromOtherSide());
+
+ // Optionally allow client to request switch of transports. Inband is
+ // always supported, so a return of an empty transport string requires
+ // the client to stay in this channel.
+ if (type == MessageType.CHOOSE_TRANSPORT) {
+ ChooseTransportMessage chooseTransport = ChooseTransportMessage.receive(this);
+ String transport = selectTransport(chooseTransport.getTransports());
+ String transportArgs = null;
+ if (transport != null) {
+ transportArgs = createTransport(transport);
+ }
+ new SwitchTransportMessage(this, transport, transportArgs).send();
+ type = Message.readMessageType(getStreamFromOtherSide());
+ }
+
+ // Now we expect a LoadModule message to load a GWT module.
+ if (type != MessageType.LOAD_MODULE) {
+ logger.log(TreeLogger.ERROR, "Unexpected message type " + type
+ + "; expecting LoadModule");
+ return;
+ }
+ LoadModuleMessage loadModule = LoadModuleMessage.receive(this);
+ url = loadModule.getUrl();
+ sessionKey = loadModule.getSessionKey();
+ moduleName = loadModule.getModuleName();
+ userAgent = loadModule.getUserAgent();
+ break;
+ default:
+ logger.log(TreeLogger.ERROR, "Unexpected message type " + type
+ + "; expecting CheckVersions");
+ return;
+ }
+ Thread.currentThread().setName(
+ "Hosting " + moduleName + " for " + userAgent + " on " + url + " @ "
+ + sessionKey);
+ logger = handler.loadModule(logger, this, moduleName, userAgent, url,
+ sessionKey);
+ try {
+ // send LoadModule response
+ ReturnMessage.send(this, false, new Value());
+ reactToMessages(handler);
+ } finally {
+ handler.unloadModule(this, moduleName);
+ }
+ }
+
+ /**
+ * Select a transport from those provided by the client.
+ *
+ * @param transports array of supported transports
+ * @return null to continue in-band, or a transport type
+ */
+ private String selectTransport(String[] transports) {
+ // TODO(jat): add support for shared memory, others
+ return null;
+ }
}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
index 45ef3bc..1eff0dc 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
@@ -155,13 +155,13 @@
@Override
public TreeLogger loadModule(TreeLogger logger, BrowserChannel channel,
- String moduleName, String userAgent) {
+ String moduleName, String userAgent, String url, String sessionKey) {
try {
// Attach a new ModuleSpace to make it programmable.
//
BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
ModuleSpaceHost msh = host.createModuleSpaceHost(logger, moduleName,
- userAgent, channel.getRemoteEndpoint());
+ userAgent, url, sessionKey, channel.getRemoteEndpoint());
this.logger = logger = msh.getLogger();
ModuleSpace moduleSpace = new ModuleSpaceOOPHM(msh, moduleName,
serverChannel);
diff --git a/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java b/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
new file mode 100644
index 0000000..9f472f2
--- /dev/null
+++ b/dev/oophm/test/com/google/gwt/dev/shell/BrowserChannelTest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.dev.shell;
+
+import com.google.gwt.dev.util.TemporaryBufferStream;
+import com.google.gwt.dev.shell.BrowserChannel.MessageType;
+import com.google.gwt.dev.shell.BrowserChannel.Value;
+import com.google.gwt.dev.shell.BrowserChannel.SessionHandler.SpecialDispatchId;
+import com.google.gwt.dev.shell.BrowserChannel.Value.ValueType;
+import com.google.gwt.dev.shell.BrowserChannel.CheckVersionsMessage;
+import com.google.gwt.dev.shell.BrowserChannel.ChooseTransportMessage;
+import com.google.gwt.dev.shell.BrowserChannel.FatalErrorMessage;
+import com.google.gwt.dev.shell.BrowserChannel.FreeMessage;
+import com.google.gwt.dev.shell.BrowserChannel.InvokeOnClientMessage;
+import com.google.gwt.dev.shell.BrowserChannel.InvokeOnServerMessage;
+import com.google.gwt.dev.shell.BrowserChannel.InvokeSpecialMessage;
+import com.google.gwt.dev.shell.BrowserChannel.JavaObjectRef;
+import com.google.gwt.dev.shell.BrowserChannel.LoadJsniMessage;
+import com.google.gwt.dev.shell.BrowserChannel.LoadModuleMessage;
+import com.google.gwt.dev.shell.BrowserChannel.OldLoadModuleMessage;
+import com.google.gwt.dev.shell.BrowserChannel.ProtocolVersionMessage;
+import com.google.gwt.dev.shell.BrowserChannel.QuitMessage;
+import com.google.gwt.dev.shell.BrowserChannel.ReturnMessage;
+import com.google.gwt.dev.shell.BrowserChannel.SwitchTransportMessage;
+
+import junit.framework.TestCase;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+/**
+ * Test for {@link BrowserChannel}.
+ */
+public class BrowserChannelTest extends TestCase {
+
+ private TemporaryBufferStream bufferStream = new TemporaryBufferStream();
+
+ private class TestBrowserChannel extends BrowserChannel {
+ public TestBrowserChannel(InputStream inputStream,
+ OutputStream outputStream) throws IOException {
+ super(inputStream, outputStream);
+ }
+
+ public MessageType readMessageType() throws IOException,
+ BrowserChannelException {
+ getStreamToOtherSide().flush();
+ return Message.readMessageType(getStreamFromOtherSide());
+ }
+ }
+
+ private DataInputStream iStr = new DataInputStream(
+ bufferStream.getInputStream());
+ private DataOutputStream oStr = new DataOutputStream(
+ bufferStream.getOutputStream());
+ private TestBrowserChannel channel;
+
+ @Override
+ protected void setUp() throws Exception {
+ channel = new TestBrowserChannel(bufferStream.getInputStream(),
+ bufferStream.getOutputStream());
+ }
+
+ public void testBooleanValue() throws IOException {
+ Value val = new Value();
+ val.setBoolean(true);
+ BrowserChannel.writeValue(oStr, val);
+ val = BrowserChannel.readValue(iStr);
+ assertEquals(ValueType.BOOLEAN, val.getType());
+ assertEquals(true, val.getBoolean());
+ val.setBoolean(false);
+ BrowserChannel.writeValue(oStr, val);
+ val = BrowserChannel.readValue(iStr);
+ assertEquals(ValueType.BOOLEAN, val.getType());
+ assertEquals(false, val.getBoolean());
+ }
+
+ // TODO(jat): add more tests for Value types
+
+ public void testCheckVersions() throws IOException, BrowserChannelException {
+ int minVersion = 1;
+ int maxVersion = 2;
+ String hostedHtmlVersion = "2.0";
+ new CheckVersionsMessage(channel, minVersion, maxVersion,
+ hostedHtmlVersion).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.CHECK_VERSIONS, type);
+ CheckVersionsMessage message = CheckVersionsMessage.receive(channel);
+ assertEquals(minVersion, message.getMinVersion());
+ assertEquals(maxVersion, message.getMaxVersion());
+ assertEquals(hostedHtmlVersion, message.getHostedHtmlVersion());
+ }
+
+ public void testChooseTransport() throws IOException,
+ BrowserChannelException {
+ String[] transports = new String[] { "shm" };
+ new ChooseTransportMessage(channel, transports).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.CHOOSE_TRANSPORT, type);
+ ChooseTransportMessage message = ChooseTransportMessage.receive(channel);
+ String[] transportsRecv = message.getTransports();
+ assertTrue(Arrays.equals(transports, transportsRecv));
+ }
+
+ public void testFatalErrorMessage() throws IOException,
+ BrowserChannelException {
+ String error = "Fatal error";
+ new FatalErrorMessage(channel, error).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.FATAL_ERROR, type);
+ FatalErrorMessage message = FatalErrorMessage.receive(channel);
+ assertEquals(error, message.getError());
+ }
+
+ public void testFreeMessage() throws IOException, BrowserChannelException {
+ int[] ids = new int[] { 42, 1024 };
+ new FreeMessage(channel, ids).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.FREE_VALUE, type);
+ FreeMessage message = FreeMessage.receive(channel);
+ int[] idsRecv = message.getIds();
+ assertTrue(Arrays.equals(ids, idsRecv));
+ }
+
+ public void testInvokeOnClientMessage() throws IOException,
+ BrowserChannelException {
+ String methodName = "fooMethod";
+ Value thisRef = new Value();
+ thisRef.setJavaObject(new JavaObjectRef(42));
+ Value[] args = new Value[] {
+ new Value(), new Value(),
+ };
+ args[0].setInt(0);
+ args[1].setInt(1);
+ new InvokeOnClientMessage(channel, methodName, thisRef, args).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.INVOKE, type);
+ InvokeOnClientMessage message = InvokeOnClientMessage.receive(channel);
+ assertEquals(methodName, message.getMethodName());
+ Value thisRefRecv = message.getThis();
+ assertEquals(ValueType.JAVA_OBJECT, thisRefRecv.getType());
+ assertEquals(42, thisRefRecv.getJavaObject().getRefid());
+ Value[] argsRecv = message.getArgs();
+ assertEquals(2, argsRecv.length);
+ for (int i = 0; i < 2; ++i) {
+ assertEquals(ValueType.INT, argsRecv[i].getType());
+ assertEquals(i, argsRecv[i].getInt());
+ }
+ }
+
+ public void testInvokeOnServerMessage() throws IOException,
+ BrowserChannelException {
+ int methodId = -1;
+ Value thisRef = new Value();
+ thisRef.setJavaObject(new JavaObjectRef(42));
+ Value[] args = new Value[] {
+ new Value(), new Value(),
+ };
+ args[0].setInt(0);
+ args[1].setInt(1);
+ new InvokeOnServerMessage(channel, methodId, thisRef, args).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.INVOKE, type);
+ InvokeOnServerMessage message = InvokeOnServerMessage.receive(channel);
+ assertEquals(methodId, message.getMethodDispatchId());
+ Value thisRefRecv = message.getThis();
+ assertEquals(ValueType.JAVA_OBJECT, thisRefRecv.getType());
+ assertEquals(42, thisRefRecv.getJavaObject().getRefid());
+ Value[] argsRecv = message.getArgs();
+ assertEquals(2, argsRecv.length);
+ for (int i = 0; i < 2; ++i) {
+ assertEquals(ValueType.INT, argsRecv[i].getType());
+ assertEquals(i, argsRecv[i].getInt());
+ }
+ }
+
+ public void testInvokeSpecialMessage() throws IOException,
+ BrowserChannelException {
+ Value[] args = new Value[] {
+ new Value(), new Value(),
+ };
+ args[0].setInt(0);
+ args[1].setInt(1);
+ new InvokeSpecialMessage(channel, SpecialDispatchId.HasMethod, args).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.INVOKE_SPECIAL, type);
+ InvokeSpecialMessage message = InvokeSpecialMessage.receive(channel);
+ assertEquals(SpecialDispatchId.HasMethod, message.getDispatchId());
+ Value[] argsRecv = message.getArgs();
+ assertEquals(2, argsRecv.length);
+ for (int i = 0; i < 2; ++i) {
+ assertEquals(ValueType.INT, argsRecv[i].getType());
+ assertEquals(i, argsRecv[i].getInt());
+ }
+ }
+
+ public void testLoadJsniMessage() throws IOException,
+ BrowserChannelException {
+ String jsni = "function foo() { }";
+ new LoadJsniMessage(channel, jsni).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.LOAD_JSNI, type);
+ LoadJsniMessage message = LoadJsniMessage.receive(channel);
+ assertEquals(jsni, message.getJsni());
+ }
+
+ public void testLoadModuleMessage() throws IOException,
+ BrowserChannelException {
+ String url = "http://www.google.com";
+ String sessionKey = "asdkfjklAI*23ja";
+ String moduleName = "org.example.Hello";
+ String userAgent = "Firefox";
+ new LoadModuleMessage(channel, url, sessionKey, moduleName,
+ userAgent).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.LOAD_MODULE, type);
+ LoadModuleMessage message = LoadModuleMessage.receive(channel);
+ assertEquals(url, message.getUrl());
+ assertEquals(sessionKey, message.getSessionKey());
+ assertEquals(moduleName, message.getModuleName());
+ assertEquals(userAgent, message.getUserAgent());
+ }
+
+ public void testOldLoadModuleMessage() throws IOException,
+ BrowserChannelException {
+ int protoVersion = 42;
+ String moduleName = "org.example.Hello";
+ String userAgent = "Firefox";
+ new OldLoadModuleMessage(channel, protoVersion, moduleName,
+ userAgent).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.OLD_LOAD_MODULE, type);
+ OldLoadModuleMessage message = OldLoadModuleMessage.receive(channel);
+ assertEquals(protoVersion, message.getProtoVersion());
+ assertEquals(moduleName, message.getModuleName());
+ assertEquals(userAgent, message.getUserAgent());
+ }
+
+ public void testProtocolVersionMessage() throws IOException,
+ BrowserChannelException {
+ int protoVersion = 42;
+ new ProtocolVersionMessage(channel, protoVersion).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.PROTOCOL_VERSION, type);
+ ProtocolVersionMessage message = ProtocolVersionMessage.receive(channel);
+ assertEquals(protoVersion, message.getProtocolVersion());
+ }
+
+ public void testQuitMessage() throws IOException,
+ BrowserChannelException {
+ new QuitMessage(channel).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.QUIT, type);
+ QuitMessage message = QuitMessage.receive(channel);
+ }
+
+ public void testReturnMessage() throws IOException,
+ BrowserChannelException {
+ Value val = new Value();
+ val.setInt(42);
+ new ReturnMessage(channel, false, val).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.RETURN, type);
+ ReturnMessage message = ReturnMessage.receive(channel);
+ assertFalse(message.isException());
+ Value valRecv = message.getReturnValue();
+ assertEquals(ValueType.INT, valRecv.getType());
+ assertEquals(42, valRecv.getInt());
+ }
+
+ public void testSwitchTransportMessage() throws IOException,
+ BrowserChannelException {
+ String transport = "shm";
+ String transportArgs = "17021";
+ new SwitchTransportMessage(channel, transport, transportArgs).send();
+ MessageType type = channel.readMessageType();
+ assertEquals(MessageType.SWITCH_TRANSPORT, type);
+ SwitchTransportMessage message = SwitchTransportMessage.receive(channel);
+ assertEquals(transport, message.getTransport());
+ assertEquals(transportArgs, message.getTransportArgs());
+ }
+}