diff --git a/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java b/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java
index a877b3955..0e0243eb1 100644
--- a/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java
+++ b/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java
@@ -16,6 +16,7 @@
package org.teavm.dom.browser;
import org.teavm.dom.ajax.XMLHttpRequest;
+import org.teavm.dom.events.EventTarget;
import org.teavm.dom.html.HTMLDocument;
import org.teavm.dom.json.JSON;
import org.teavm.dom.typedarrays.*;
@@ -28,7 +29,7 @@ import org.teavm.jso.JSProperty;
*
* @author Alexey Andreev
*/
-public interface Window extends JSGlobal {
+public interface Window extends JSGlobal, EventTarget {
@JSProperty
HTMLDocument getDocument();
diff --git a/teavm-jso/src/main/java/org/teavm/jso/JS.java b/teavm-jso/src/main/java/org/teavm/jso/JS.java
index 6de56e908..d1096a78b 100644
--- a/teavm-jso/src/main/java/org/teavm/jso/JS.java
+++ b/teavm-jso/src/main/java/org/teavm/jso/JS.java
@@ -18,8 +18,10 @@ package org.teavm.jso;
import java.util.Iterator;
import org.teavm.dependency.PluggableDependency;
import org.teavm.javascript.ni.InjectedBy;
+import org.teavm.jso.plugin.JSNativeGenerator;
/**
+ *
Container of static methods to manipulate over {@link JSObject}s.
*
* @author Alexey Andreev
*/
@@ -56,6 +58,10 @@ public final class JS {
@InjectedBy(JSNativeGenerator.class)
public static native JSObject getTypeName(JSObject obj);
+ /**
+ * Gets global JavaScript object, that is similar to the window
object in the browser.
+ * @return global object.
+ */
@InjectedBy(JSNativeGenerator.class)
public static native JSObject getGlobal();
diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSBody.java b/teavm-jso/src/main/java/org/teavm/jso/JSBody.java
new file mode 100644
index 000000000..83ab3b79c
--- /dev/null
+++ b/teavm-jso/src/main/java/org/teavm/jso/JSBody.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * 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 org.teavm.jso;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that method is to have native JavaScript implementation.
+ * Method only can take and return primitive values and {@link JSObject}s.
+ * JSBody script can't call Java methods, but you can pass callbacks wrapped into {@link JSFunctor}.
+ * Note that unless method is static, it must belong to class that implements {@link JSObject}.
+ * If applied to non-native method, original Java body will be overwritten by JavaScript.
+ *
+ * @author Alexey Andreev
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface JSBody {
+ /**
+ * How method parameters are named inside JavaScript implementation.
+ */
+ String[] params();
+
+ /**
+ * JavaScript implementation.
+ */
+ String script();
+}
diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java b/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java
index 9f0a228e8..5374025a1 100644
--- a/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java
+++ b/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java
@@ -37,17 +37,15 @@ public interface JSFunction extends JSObject {
JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c,
JSObject d);
- JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c,
- JSObject d, JSObject e);
+ JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e);
- JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c,
- JSObject d, JSObject e, JSObject f);
+ JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f);
- JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c,
- JSObject d, JSObject e, JSObject f, JSObject g);
+ JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
+ JSObject g);
- JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c,
- JSObject d, JSObject e, JSObject f, JSObject g, JSObject h);
+ JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
+ JSObject g, JSObject h);
JSObject apply(JSObject thisArg, JSObject[] arguments);
}
diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java
new file mode 100644
index 000000000..a671c6ca6
--- /dev/null
+++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * 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 org.teavm.jso.plugin;
+
+import java.io.IOException;
+import java.util.List;
+import org.teavm.codegen.SourceWriter;
+import org.teavm.javascript.ni.Generator;
+import org.teavm.javascript.ni.GeneratorContext;
+import org.teavm.model.*;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+public class JSBodyGenerator implements Generator {
+ @Override
+ public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
+ ClassReader cls = context.getClassSource().get(methodRef.getClassName());
+ MethodReader method = cls.getMethod(methodRef.getDescriptor());
+ AnnotationReader annot = method.getAnnotations().get(JSBodyImpl.class.getName());
+ boolean isStatic = annot.getValue("isStatic").getBoolean();
+ List paramNames = annot.getValue("params").getList();
+
+ int bodyParamCount = method.parameterCount();
+ int offset = isStatic ? 1 : 0;
+
+ writer.append("if (!").appendMethodBody(methodRef).append(".$native)").ws().append('{').indent().newLine();
+ writer.appendMethodBody(methodRef).append(".$native").ws().append('=').ws().append("function(");
+ int count = method.parameterCount() + (isStatic ? 0 : 1);
+ for (int i = 0; i < count; ++i) {
+ if (i > 0) {
+ writer.append(',').ws();
+ }
+ writer.append('_').append(context.getParameterName(i + offset));
+ }
+ writer.append(')').ws().append('{').softNewLine().indent();
+
+ writer.append("return (function(");
+ for (int i = 0; i < bodyParamCount; ++i) {
+ if (i > 0) {
+ writer.append(',').ws();
+ }
+ String name = paramNames.get(i).getString();
+ writer.append(name);
+ }
+ writer.append(')').ws().append('{').softNewLine().indent();
+ writer.append(annot.getValue("script").getString()).softNewLine();
+ writer.outdent().append("})");
+ if (!isStatic) {
+ writer.append(".call");
+ }
+ writer.append('(');
+ for (int i = 0; i < count; ++i) {
+ if (i > 0) {
+ writer.append(',').ws();
+ }
+ writer.append('_').append(context.getParameterName(i + offset));
+ }
+ writer.append(");").softNewLine();
+ writer.outdent().append("};").softNewLine();
+ writer.appendMethodBody(methodRef).ws().append('=').ws().appendMethodBody(methodRef).append(".$native;")
+ .softNewLine();
+ writer.outdent().append("}").softNewLine();
+
+ writer.appendMethodBody(methodRef).append('(');
+ for (int i = 0; i < count; ++i) {
+ if (i > 0) {
+ writer.append(',').ws();
+ }
+ writer.append(context.getParameterName(i + offset));
+ }
+ writer.append(");").softNewLine();
+ }
+}
diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java
new file mode 100644
index 000000000..c2fa25419
--- /dev/null
+++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 Alexey Andreev.
+ *
+ * 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 org.teavm.jso.plugin;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@interface JSBodyImpl {
+ String[] params();
+
+ String script();
+
+ boolean isStatic();
+}
diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java
similarity index 99%
rename from teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java
rename to teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java
index ef4f9fb16..856aa0dc9 100644
--- a/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java
+++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.teavm.jso;
+package org.teavm.jso.plugin;
import java.io.IOException;
import org.teavm.codegen.SourceWriter;
@@ -23,6 +23,7 @@ import org.teavm.javascript.ast.Expr;
import org.teavm.javascript.ast.InvocationExpr;
import org.teavm.javascript.ni.Injector;
import org.teavm.javascript.ni.InjectorContext;
+import org.teavm.jso.JS;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReader;
diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java
index 083491b45..738fa639d 100644
--- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java
+++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java
@@ -16,31 +16,28 @@
package org.teavm.jso.plugin;
import org.teavm.diagnostics.Diagnostics;
+import org.teavm.jso.JSBody;
import org.teavm.model.*;
/**
*
* @author Alexey Andreev
*/
-class JSObjectClassTransformer implements ClassHolderTransformer {
- private ThreadLocal processor = new ThreadLocal<>();
+public class JSObjectClassTransformer implements ClassHolderTransformer {
+ private JavascriptNativeProcessor processor;
@Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
- JavascriptNativeProcessor processor = getProcessor(innerSource);
+ processor = new JavascriptNativeProcessor(innerSource);
processor.setDiagnostics(diagnostics);
processor.processClass(cls);
- for (MethodHolder method : cls.getMethods()) {
- if (method.getProgram() != null) {
+ for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
+ if (method.getAnnotations().get(JSBody.class.getName()) != null) {
+ processor.processJSBody(cls, method);
+ } else if (method.getProgram() != null &&
+ method.getAnnotations().get(JSBodyImpl.class.getName()) == null) {
processor.processProgram(method);
}
}
}
-
- private JavascriptNativeProcessor getProcessor(ClassReaderSource innerSource) {
- if (processor.get() == null) {
- processor.set(new JavascriptNativeProcessor(innerSource));
- }
- return processor.get();
- }
}
diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java
index 6c43ad759..8bb924573 100644
--- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java
+++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java
@@ -17,6 +17,7 @@ package org.teavm.jso.plugin;
import java.util.*;
import org.teavm.diagnostics.Diagnostics;
+import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.PreserveOriginalName;
import org.teavm.jso.*;
import org.teavm.model.*;
@@ -32,6 +33,7 @@ class JavascriptNativeProcessor {
private List replacement = new ArrayList<>();
private NativeJavascriptClassRepository nativeRepos;
private Diagnostics diagnostics;
+ private int methodIndexGenerator;
public JavascriptNativeProcessor(ClassReaderSource classSource) {
this.classSource = classSource;
@@ -209,6 +211,101 @@ class JavascriptNativeProcessor {
}
}
+ public void processJSBody(ClassHolder cls, MethodHolder methodToProcess) {
+ CallLocation location = new CallLocation(methodToProcess.getReference());
+ boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);
+
+ // validate parameter names
+ AnnotationHolder bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
+ int jsParamCount = bodyAnnot.getValue("params").getList().size();
+ if (methodToProcess.parameterCount() != jsParamCount) {
+ diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount() +
+ " parameters, but annotation specifies " + jsParamCount, methodToProcess);
+ return;
+ }
+
+ // remove annotation and make non-native
+ methodToProcess.getAnnotations().remove(JSBody.class.getName());
+ methodToProcess.getModifiers().remove(ElementModifier.NATIVE);
+
+ // generate parameter types for original method and validate
+ int paramCount = methodToProcess.parameterCount();
+ if (!isStatic) {
+ ++paramCount;
+ }
+ ValueType[] paramTypes = new ValueType[paramCount];
+ int offset = 0;
+ if (!isStatic) {
+ ValueType paramType = ValueType.object(cls.getName());
+ paramTypes[offset++] = paramType;
+ if (!isSupportedType(paramType)) {
+ diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
+ methodToProcess.getReference(), cls.getName());
+ }
+ }
+ if (methodToProcess.getResultType() != ValueType.VOID && !isSupportedType(methodToProcess.getResultType())) {
+ diagnostics.error(location, "JSBody method {{m0}} returns unsupported type {{t1}}",
+ methodToProcess.getReference(), methodToProcess.getResultType());
+ }
+
+ // generate parameter types for proxy method
+ for (int i = 0; i < methodToProcess.parameterCount(); ++i) {
+ paramTypes[offset++] = methodToProcess.parameterType(i);
+ }
+ ValueType[] proxyParamTypes = new ValueType[paramCount + 1];
+ for (int i = 0; i < paramCount; ++i) {
+ proxyParamTypes[i] = ValueType.parse(JSObject.class);
+ }
+ proxyParamTypes[paramCount] = methodToProcess.getResultType() == ValueType.VOID ? ValueType.VOID :
+ ValueType.parse(JSObject.class);
+
+ // create proxy method
+ MethodHolder proxyMethod = new MethodHolder("$js_body$_" + methodIndexGenerator++, proxyParamTypes);
+ proxyMethod.getModifiers().add(ElementModifier.NATIVE);
+ proxyMethod.getModifiers().add(ElementModifier.STATIC);
+ AnnotationHolder genBodyAnnot = new AnnotationHolder(JSBodyImpl.class.getName());
+ genBodyAnnot.getValues().put("script", bodyAnnot.getValue("script"));
+ genBodyAnnot.getValues().put("params", bodyAnnot.getValue("params"));
+ genBodyAnnot.getValues().put("isStatic", new AnnotationValue(isStatic));
+ AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
+ generatorAnnot.getValues().put("value", new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
+ proxyMethod.getAnnotations().add(genBodyAnnot);
+ proxyMethod.getAnnotations().add(generatorAnnot);
+ cls.addMethod(proxyMethod);
+
+ // create program that invokes proxy method
+ program = new Program();
+ BasicBlock block = program.createBasicBlock();
+ List params = new ArrayList<>();
+ for (int i = 0; i < paramCount; ++i) {
+ params.add(program.createVariable());
+ }
+ methodToProcess.setProgram(program);
+
+ // Generate invoke instruction
+ replacement.clear();
+ InvokeInstruction invoke = new InvokeInstruction();
+ invoke.setType(InvocationType.SPECIAL);
+ invoke.setMethod(proxyMethod.getReference());
+ for (int i = 0; i < paramCount; ++i) {
+ Variable var = program.createVariable();
+ invoke.getArguments().add(wrapArgument(location, var, paramTypes[i]));
+ }
+ block.getInstructions().addAll(replacement);
+ block.getInstructions().add(invoke);
+
+ // Generate return
+ ExitInstruction exit = new ExitInstruction();
+ if (methodToProcess.getResultType() != ValueType.VOID) {
+ replacement.clear();
+ Variable result = program.createVariable();
+ invoke.setReceiver(result);
+ exit.setValueToReturn(unwrap(location, result, methodToProcess.getResultType()));
+ block.getInstructions().addAll(replacement);
+ }
+ block.getInstructions().add(exit);
+ }
+
private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
InstructionLocation location) {
Variable nameVar = addStringWrap(addString(propertyName, location), location);
@@ -373,8 +470,7 @@ class JavascriptNativeProcessor {
}
Variable result = program.createVariable();
InvokeInstruction insn = new InvokeInstruction();
- insn.setMethod(new MethodReference(JS.class.getName(), "wrap", type,
- ValueType.object(JSObject.class.getName())));
+ insn.setMethod(new MethodReference(JS.class.getName(), "wrap", type, ValueType.parse(JSObject.class)));
insn.getArguments().add(var);
insn.setReceiver(result);
insn.setType(InvocationType.SPECIAL);