Skip to content

Commit 749ddb9

Browse files
committed
basic js model
1 parent d720baf commit 749ddb9

File tree

9 files changed

+84
-59
lines changed

9 files changed

+84
-59
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ A headless browser for Java.
44
## IN-DEV | CONTRIBUTIONS NEEDED!
55
To simulate a browser nowadays we need to provide all of these web apis: https://developer.mozilla.org/en-US/docs/Web/API
66
See package `com.osiris.headlessbrowser.javascript` for already implemented apis.
7-
If you are working on a implementation open an issue to keep track of who is working on what and avoid duplicate work.
7+
If you are working on an implementation open an issue to keep track of who is working on what and avoid duplicate work.
8+
[Read this to get started.](how-to-implement-a-js-web-api.md)
89

910
## Motivation
1011
There are only two types of headless Java browsers currently available:

RESEARCH.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Research
2+
Organized list containing easy to forget things.
3+
4+
## GraalJS - JavaScript Engine
5+
Problem: `eval()` fails to detect correct binding when nested classes are used (at least in tests).
6+
7+
```java
8+
import com.osiris.headlessbrowser.javascript.JavaScriptAPI;
9+
10+
public class JS_API_Test extends JavaScriptAPI {
11+
// stuff...
12+
class NestedObject {
13+
}
14+
15+
@Override
16+
public Object getObject() {
17+
return new NestedObject();
18+
}
19+
}
20+
```
21+
Fix: Simply don't use nested classes/objects.

how-to-implement-a-js-web-api.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# How to implement a JavaScript Web-API
2+
3+
1. Simply copy the `JS_API_Console` class from the `com.osiris.headlessbrowser.javascript` package
4+
and paste it with another name into the same package.
5+
2. Register your new class in the `JSContext` class, so it gets loaded into it.
6+
3. The `JS_API_Console` is the Java-Implementation of the JavaScript console api and
7+
thus provides all the information you need to start implementing a new web-api.

src/main/java/com/osiris/headlessbrowser/JSContext.java

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.osiris.headlessbrowser;
22

33
import com.osiris.headlessbrowser.javascript.JS_API_Console;
4-
import com.osiris.headlessbrowser.javascript.defaults.JavaScriptAPI;
4+
import com.osiris.headlessbrowser.javascript.JavaScriptAPI;
55
import com.osiris.headlessbrowser.javascript.exceptions.DuplicateRegisteredId;
66
import de.undercouch.citeproc.VariableWrapper;
77
import de.undercouch.citeproc.VariableWrapperParams;
@@ -20,33 +20,34 @@
2020
public class JSContext extends AbstractScriptRunner {
2121
private HeadlessWindow window;
2222
private Context rawContext = Context.newBuilder("js").allowAllAccess(true).build();
23-
private List<JavaScriptAPI> loadedJSWebAPIs = new ArrayList<>();
23+
private Map<JavaScriptAPI, Boolean> loadedJSWebAPIs = new HashMap<>();
2424

2525
public JSContext(HeadlessWindow window) {
2626
this.window = window;
2727

2828
// APIs in this list get loaded into this JSContext in the order they were added to this list.
2929
// If you want to add an api that depends on another one make sure to add it after that one.
3030
//loadedJSWebAPIs.add(new JS_API_Console(System.out));
31-
rawContext.getBindings("js").getMemberKeys().forEach(key -> System.out.println(key));
31+
loadedJSWebAPIs.put(new JS_API_Console(System.out), true); // If true overrides any existing variable with the same name
3232

3333
// Register all JavaScript Web-APIs:
34-
try{
35-
for (JavaScriptAPI api :
36-
loadedJSWebAPIs) {
37-
registerAndLoad(api.getJSVarName(), api.getObject());
34+
loadedJSWebAPIs.forEach((api, override) -> {
35+
try{
36+
registerAndLoad(api.getJSVarName(), api.getObject(), override);
37+
} catch (Exception exception) {
38+
System.err.println("Failed to load one/multiple JavaScript Web-API(s) into the current JavaScript-Context! Details:");
39+
throw new RuntimeException(exception);
3840
}
39-
} catch (Exception exception) {
40-
System.err.println("Failed to load one/multiple JavaScript Web-API(s) into the current JavaScript-Context! Details:");
41-
throw new RuntimeException(exception);
42-
}
41+
});
42+
4343
}
4444

4545
/**
4646
* Registers and loads this API into the provided {@link JSContext}. <br>
47+
* @param id
4748
*/
48-
public void registerAndLoad(String id, Object object) throws DuplicateRegisteredId, IOException {
49-
if(rawContext.getBindings("js").getMember(id) != null)
49+
public void registerAndLoad(String id, Object object, boolean override) throws DuplicateRegisteredId, IOException {
50+
if(!override && rawContext.getBindings("js").getMember(id) != null)
5051
throw new DuplicateRegisteredId("Failed to register because of already existing/registered id '"+id+"'.");
5152
rawContext.getBindings("js").putMember(id, object);
5253
}
@@ -60,11 +61,11 @@ public void registerAndLoad(String id, Object object) throws DuplicateRegistered
6061
* @throws IOException
6162
*/
6263
public void eval(String jsCode) throws IOException {
63-
eval(new BufferedReader(new StringReader(jsCode)));
64+
rawContext.eval("js", jsCode);
6465
}
6566

6667
public void eval(InputStream jsCodesInputStream) throws IOException {
67-
eval(new BufferedReader(new InputStreamReader(jsCodesInputStream)));
68+
eval(new InputStreamReader(jsCodesInputStream));
6869
}
6970

7071
public HeadlessWindow getWindow() {
@@ -95,6 +96,14 @@ public String getVersion() {
9596

9697
@Override
9798
public void eval(Reader reader) throws IOException {
99+
String jsCode = null;
100+
try (BufferedReader bufferedReader = new BufferedReader(reader)) {
101+
while ((jsCode = bufferedReader.readLine()) != null) {
102+
jsCode = jsCode+jsCode;
103+
}
104+
} catch (IOException e) {
105+
throw e;
106+
}
98107
rawContext.eval(Source.newBuilder("js", reader, null).cached(false).build());
99108
}
100109

src/main/java/com/osiris/headlessbrowser/javascript/JS_API_Console.java

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package com.osiris.headlessbrowser.javascript;
22

3-
import com.osiris.headlessbrowser.javascript.defaults.Default_JS_API;
4-
import com.osiris.headlessbrowser.javascript.defaults.JavaScriptAPI;
5-
63
import java.io.OutputStream;
74
import java.io.PrintStream;
85

@@ -11,12 +8,13 @@
118
* WORK IN PROGRESS <br>
129
* @author Osiris-Team
1310
*/
14-
public class JS_API_Console extends Default_JS_API implements JavaScriptAPI {
11+
public class JS_API_Console implements JavaScriptAPI {
1512
private PrintStream out;
1613

1714
public JS_API_Console(OutputStream out) {
1815
this(new PrintStream(out));
1916
}
17+
2018
public JS_API_Console(PrintStream out) {
2119
this.out = out;
2220
}
@@ -31,10 +29,12 @@ public String getJSVarName() {
3129

3230
@Override
3331
public Object getObject() {
34-
return new Console();
32+
return this;
3533
}
3634

37-
class Console{
35+
public void log(String msg){
36+
out.println(msg);
37+
}
3838
/*
3939
TODO IMPLEMENT THESE
4040
TODO GENERATE TEST FOR EACH METHOD.
@@ -69,10 +69,4 @@ class Console{
6969
};
7070
*/
7171

72-
public void log(String msg){
73-
out.println(msg);
74-
}
75-
}
76-
77-
7872
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.osiris.headlessbrowser.javascript;
2+
3+
import com.osiris.headlessbrowser.JSContext;
4+
5+
import java.io.InputStream;
6+
7+
public interface JavaScriptAPI {
8+
9+
/**
10+
* The global variable name used in the JavaScript code to access the APIs methods/functions. <br>
11+
* Example: console.log('Hello'); <br>
12+
* JSVarName: console <br>
13+
* Should be unique. <br>
14+
*/
15+
String getJSVarName();
16+
17+
/**
18+
* The object containing all methods/functions of the API. <br>
19+
*/
20+
Object getObject();
21+
22+
}

src/main/java/com/osiris/headlessbrowser/javascript/defaults/Default_JS_API.java

-16
This file was deleted.

src/main/java/com/osiris/headlessbrowser/javascript/defaults/JavaScriptAPI.java

-13
This file was deleted.

src/test/java/com/osiris/headlessbrowser/JSContextTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ void testCallMethods() throws ScriptRunnerException, IOException {
2222
}
2323

2424
@Test
25-
void testContextWebApis() {
25+
void testContextWebApis() throws IOException {
2626
HeadlessBrowser browser = new HeadlessBrowser();
2727
JSContext jsContext = browser.openNewWindow().getJsContext();
28-
jsContext.getRawContext().getBindings("js").getMemberKeys().forEach(System.out::println);
28+
jsContext.eval("console.log('hi!');");
2929
}
3030

3131
public static class MyClass {

0 commit comments

Comments
 (0)