Skip to content

Commit

Permalink
0.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
Osiris-Team committed Sep 26, 2021
1 parent e572d9b commit 76c4a62
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 95 deletions.
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
A new, headless browser written in Java with latest JavaScript support. Powered by the GraalJS-Engine.

```java
HBrowser hBrowser = new HBrowser();
HWindow hWindow = hBrowser.openNewWindow().load("https://wikipedia.org");
HBrowser hBrowser=new HBrowser();
HWindow hWindow=hBrowser.openNewWindow().load("https://wikipedia.org");
```

## Features
- [x] Can load pages and partially execute their JavaScript code. Contributions are needed for implementing all [JS Web-APIs](https://developer.mozilla.org/en-US/docs/Web/API), to achieve full JavaScript support. [Click here to see a list of already implemented APIs and how to implement one.](how-to-implement-a-js-web-api.md)

- [x] Can load pages and partially execute their JavaScript code. Contributions are needed for implementing
all [JS Web-APIs](https://developer.mozilla.org/en-US/docs/Web/API), to achieve full JavaScript
support. [Click here to see a list of already implemented APIs and how to implement one.](how-to-implement-a-js-web-api.md)
- [x] Uses Jsoup for editing HTML directly in Java.
- [x] Uses GraalJS-Engine to execute JavaScript code.
- [ ] Access to all JS-Web APIs from within Java.
Expand All @@ -22,21 +26,20 @@ HWindow hWindow = hBrowser.openNewWindow().load("https://wikipedia.org");

#### Why contribute?

I worked with multiple different browsers like JCEF, Selenium, JWebdriver,
HtmlUnit and maybe some more I don't remember now, but all have one thing in common.
They have some kind of caveat.
I worked with multiple different browsers like JCEF, Selenium, JWebdriver, HtmlUnit and maybe some more I don't remember
now, but all have one thing in common. They have some kind of caveat.

That's why I started this project. To create a new browser, not dependent on Chromium or Waterfox etc., written in Java,
compatible with all operating systems that can run Java.
We use Jsoup to handle HTML and the GraalJS engine to handle JavaScript.
compatible with all operating systems that can run Java. We use Jsoup to handle HTML and the GraalJS engine to handle
JavaScript.

Now you may ask: Why do you need my help?
Our problem is that most of the JavaScript code out there uses so called [Web-APIs](https://developer.mozilla.org/en-US/docs/Web/API)
, which get shipped with each browser.
We will need to implement those APIs by ourselfs.
Now you may ask: Why do you need my help? Our problem is that most of the JavaScript code out there uses so
called [Web-APIs](https://developer.mozilla.org/en-US/docs/Web/API)
, which get shipped with each browser. We will need to implement those APIs by ourselfs.

There are a lot of Web-APIs available, so we need some motivated people to implement them.
If you want to help, thank you **very** much, already in advance! [Click here to see a list of already implemented APIs and how to implement one.](how-to-implement-a-js-web-api.md)
There are a lot of Web-APIs available, so we need some motivated people to implement them. If you want to help, thank
you **very** much, already in
advance! [Click here to see a list of already implemented APIs and how to implement one.](how-to-implement-a-js-web-api.md)
If you are working on an implementation open an issue to keep track of who is working on what and avoid duplicate work.

#### Beginners
Expand Down
20 changes: 14 additions & 6 deletions how-to-implement-a-js-web-api.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# How to implement a JavaScript Web-API

### 1. Choose the JS Web-API you want to implement from the TODO list below, and find its documentation. The documentation can most likely be found on these websites:
- https://www.w3.org/TR/?tag=webapi
- https://developer.mozilla.org/en-US/docs/Web/API
- https://spec.whatwg.org

### 2. Read the [JS_API_Example](https://github.com/Osiris-Team/Headless-Browser/blob/main/src/main/java/com/osiris/headlessbrowser/javascript/JS_API_Example.java) class for further details.
1. Choose the JS Web-API you want to implement from the TODO list below, and find its documentation. The documentation
can most likely be found on these websites:
- https://www.w3.org/TR/?tag=webapi
- https://developer.mozilla.org/en-US/docs/Web/API
- https://spec.whatwg.org
2. Create a new package in `com.osiris.headlessbrowser.javascript.apis` with your APIs name
3. Inside that new package create a new class named something like `JS_API_MyApiName` and implement the `JS_API`
interface.
4. Read
the [JS_API_Example](https://github.com/Osiris-Team/Headless-Browser/blob/main/src/main/java/com/osiris/headlessbrowser/javascript/JS_API_Example.java)
class for further details.
5. Register/Load your JS Web-API into
the [JSContext](https://github.com/Osiris-Team/Headless-Browser/blob/main/src/main/java/com/osiris/headlessbrowser/JSContext.java)
.

## TODO

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.osiris.headlessbrowser</groupId>
<artifactId>Headless-Browser</artifactId>
<version>ALPHA-0.0.3</version>
<version>0.0.4</version>
<packaging>jar</packaging>

<name>Headless-Browser</name>
Expand Down
26 changes: 16 additions & 10 deletions src/main/java/com/osiris/headlessbrowser/HWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,25 +98,31 @@ public Document getDocument() {
return document;
}

/**
* Returns the JavaScript context. <br>
*/
public JSContext getJsContext() {
public JSContext getJavaScriptContext() {
return jsContext;
}

public String getAuthority() {
return authority;
}

/**
* Returns the complete, current JavaScript code. <br>
* Returns the JavaScript code extracted from the pages script elements. <br>
* If no page has been loaded this will return null. <br>
*/
public String getJavaScriptCode() {
public String getLoadedJavaScriptCode() {
return javaScriptCode;
}

/**
* Executes the provided JavaScript code in the current context. <br>
* See {@link JSContext} for details. <br>
*/
public HWindow executeJS(String jsCode) {
jsContext.eval(jsCode);
return this;
}

public String getAuthority() {
return authority;
}

@Override
public void close() {
jsContext.close();
Expand Down
16 changes: 9 additions & 7 deletions src/main/java/com/osiris/headlessbrowser/JSContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ public class JSContext implements AutoCloseable {
private final HWindow window;
private final Context rawContext = Context.newBuilder("js")
.build();

private List<String> globalVarNames = new ArrayList<>();
// Currently used for debugging
private final PrintStream out = System.out;
// Web-APIs:
private final JS_API_Console console = new JS_API_Console(System.out);
private List<String> globalVarNames = new ArrayList<>();
private List<String> globalClassNames = new ArrayList<>();



public JSContext(HWindow window) {
Objects.requireNonNull(window);
out.println("Created new JavaScript context for '" + window + "'.");
out.println("Created new JavaScript context for window '" + window + "'.");
this.window = window;

// Register all JavaScript Web-APIs:
Expand All @@ -42,6 +42,7 @@ public JSContext(HWindow window) {
// Note that override should be false.
try {
out.println("Loading JS Web-APIs into context...");
long start = System.currentTimeMillis();
registerAndLoad(console, true); // If true overrides any existing variable with the same name
// DOM API:
registerAndLoad(new JS_Event_S(), false);
Expand All @@ -51,9 +52,9 @@ public JSContext(HWindow window) {
//...

globalVarNames.clear();
out.println("Loaded all JS Web-APIs into successfully.");
out.println("Loaded all JS Web-APIs successfully. Took "+(System.currentTimeMillis()-start)+"ms.");
} catch (Exception exception) {
System.err.println("Failed to load one/multiple JavaScript Web-API(s) into the current JavaScript-Context! Details:");
System.err.println("Failed to load JavaScript Web-API into the current JavaScript-Context! Details:");
throw new RuntimeException(exception);
}

Expand All @@ -67,7 +68,8 @@ public void close() {
/**
* Registers and loads the provided JS-API into the current {@link JSContext}. <br>
* A new global variable gets created for the provided JS-API with its {@link JS_API#getJSGlobalVarName()}.
* @param jsAPI the JavaScript API to add.
*
* @param jsAPI the JavaScript API to add.
* @param override if a global variable with the same name already exists, should it get overwritten?
*/
public JSContext registerAndLoad(JS_API jsAPI, boolean override) throws DuplicateFoundException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import org.graalvm.polyglot.HostAccess;

public class JS_API_Example implements JS_API {

@Override
public String getJSGlobalVarName() {
return "example";
}

@Override
public String getOptionalJSCode() {
return null;
Expand All @@ -27,7 +27,7 @@ public String returnSomething() {
// This method is only accessible from Java code, because
// it misses the @HostAccess.Export annotation
public void doAnotherThing() {
// Do something in Java code.
// Do something in Java code.
}

// If a JS Web-API has static methods or fields do not make
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
*/
public class JS_API_Console implements JS_API {

private final PrintStream out;
private final List<Sendable> onLog = new ArrayList<>();


// Only accessible from Java:
private final List<Sendable> onInfo = new ArrayList<>();
private final List<Sendable> onDebug = new ArrayList<>();
private final List<Sendable> onError = new ArrayList<>();
private final List<Sendable> onWarn = new ArrayList<>();
public JS_API_Console(OutputStream out) {
this(new PrintStream(out));
}
public JS_API_Console(PrintStream out) {
this.out = out;
}

@Override
public String getJSGlobalVarName() {
return "console";
Expand All @@ -36,27 +52,6 @@ public String getOptionalJSCode() {
"console.assert = myAssertFunc;";
}


// Only accessible from Java:


private final PrintStream out;
private final List<Sendable> onLog = new ArrayList<>();
private final List<Sendable> onInfo = new ArrayList<>();
private final List<Sendable> onDebug = new ArrayList<>();
private final List<Sendable> onError = new ArrayList<>();
private final List<Sendable> onWarn = new ArrayList<>();



public JS_API_Console(OutputStream out) {
this(new PrintStream(out));
}

public JS_API_Console(PrintStream out) {
this.out = out;
}

public void onLog(Sendable runnable) {
onLog.add(runnable);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.osiris.headlessbrowser.javascript.apis.dom;

import com.osiris.headlessbrowser.javascript.JS_API;
import org.graalvm.polyglot.HostAccess;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.osiris.headlessbrowser.javascript.apis.dom;

import com.oracle.truffle.js.runtime.builtins.JSDictionary;
import org.graalvm.polyglot.HostAccess;

import java.util.Dictionary;
import java.util.Enumeration;

public class JS_EventInit {

@HostAccess.Export
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
*/
public class JS_Event_S implements JS_API {

@HostAccess.Export
public final short NONE = 0;
@HostAccess.Export
public final short CAPTURING_PHASE = 1;
@HostAccess.Export
public final short AT_TARGET = 2;
@HostAccess.Export
public final short BUBBLING_PHASE = 3;

@Override
public String getJSGlobalVarName() {
return "Event";
Expand All @@ -19,13 +28,4 @@ public String getJSGlobalVarName() {
public String getOptionalJSCode() {
return null;
}

@HostAccess.Export
public final short NONE = 0;
@HostAccess.Export
public final short CAPTURING_PHASE = 1;
@HostAccess.Export
public final short AT_TARGET = 2;
@HostAccess.Export
public final short BUBBLING_PHASE = 3;
}
2 changes: 1 addition & 1 deletion src/test/java/com/osiris/headlessbrowser/API_1.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public String getOptionalJSCode() {
}

@HostAccess.Export
public JS_Object getJsObject(){
public JS_Object getJsObject() {
return new JS_Object();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ void test() throws IOException {
HBrowser hBrowser = new HBrowser();
HWindow hWindow = hBrowser.openNewCustomWindow().enableJavaScript(false).build()
.load("wikipedia.org");
System.out.println(hWindow.getDocument().toString());
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package com.osiris.headlessbrowser;

import com.osiris.headlessbrowser.javascript.JS_API;
import com.osiris.headlessbrowser.javascript.exceptions.DuplicateFoundException;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.junit.jupiter.api.Test;

public class GraalJSPlayground {

@Test
void testStaticAccessFromJSCode() {

Context ctx = Context.newBuilder("js").allowHostClassLookup(s->true).build();
Context ctx = Context.newBuilder("js").allowHostClassLookup(s -> true).build();

Object obj = new MyJavaClass();

ctx.getBindings("js").putMember("myJavaClass", new MyJavaClass());

ctx.eval("js", "var MyJavaClass = Java.type('"+MyJavaClass.class.getCanonicalName()+"');" +
ctx.eval("js", "var MyJavaClass = Java.type('" + MyJavaClass.class.getCanonicalName() + "');" +
"console.log(MyJavaClass.HELLO);");

ctx.eval("js", "console.log(MyJavaClass.HELLO);");
Expand All @@ -29,7 +27,7 @@ void testStaticAccessFromJSCode() {
@Test
void testDependentObjects() throws DuplicateFoundException {

JSContext context = new HBrowser().openNewWindow().getJsContext()
JSContext context = new HBrowser().openNewWindow().getJavaScriptContext()
//.registerAndLoad(new JS_Object(), false)
.registerAndLoad(new API_1(), false);

Expand Down
12 changes: 6 additions & 6 deletions src/test/java/com/osiris/headlessbrowser/JSContextTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ public static void main(String[] args) {
.build()) {
context.getBindings("js").putMember("javaObj", new MyClass());
boolean valid = context.eval("js",
" javaObj.id == 42" +
" && javaObj.text == '42'" +
" && javaObj.arr[1] == 42" +
" && javaObj.ret42() == 42")
" javaObj.id == 42" +
" && javaObj.text == '42'" +
" && javaObj.arr[1] == 42" +
" && javaObj.ret42() == 42")
.asBoolean();
context.eval("js", "javaObj.print('HELLO!!!');");
assert valid == true;
Expand All @@ -27,15 +27,15 @@ public static void main(String[] args) {
@Test
void testConsoleApi() throws IOException {
HBrowser hBrowser = new HBrowser();
JSContext jsContext = hBrowser.openNewWindow().getJsContext();
JSContext jsContext = hBrowser.openNewWindow().getJavaScriptContext();
jsContext.getConsole().onLog(msg -> System.out.println("JavaScript message received: " + msg));
jsContext.eval("console.log('john stamos');");
}

@Test
void testContextWebApis() throws IOException {
HBrowser browser = new HBrowser();
JSContext jsContext = browser.openNewWindow().getJsContext();
JSContext jsContext = browser.openNewWindow().getJavaScriptContext();
jsContext.eval("console.log('hi!');");
}

Expand Down
Loading

0 comments on commit 76c4a62

Please sign in to comment.