Skip to content

Commit

Permalink
Adding graphical.browser
Browse files Browse the repository at this point in the history
  • Loading branch information
marigostra committed Apr 29, 2021
1 parent 5a16f5d commit ddb9f07
Show file tree
Hide file tree
Showing 10 changed files with 1,275 additions and 0 deletions.
117 changes: 117 additions & 0 deletions src/main/java/org/luwrain/graphical/FxThread.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
Copyright 2012-2021 Michael Pozhidaev <[email protected]>
Copyright 2015-2016 Roman Volovodov <[email protected]>
This file is part of LUWRAIN.
LUWRAIN is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
LUWRAIN is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
*/

package org.luwrain.graphical;

import javafx.application.Platform;
import javafx.scene.paint.Color;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

import org.luwrain.core.*;
import org.luwrain.base.InteractionParamColor;

public final class FxThread
{
static private final String LOG_COMPONENT = "fx";

static public void ensure()
{
if(!Platform.isFxApplicationThread())
throw new IllegalStateException("Execution in non-jfx thread");
}

static public Object callInFxThreadSync(Callable callable)
{
NullCheck.notNull(callable, "callable");
if(Platform.isFxApplicationThread())
try {
return callable.call();
}
catch(Throwable e)
{
Log.error(LOG_COMPONENT, "callable object thrown an exception:" + e.getClass().getName() + ":" + e.getMessage());
throw new RuntimeException(e);
}
final FutureTask<Object> query=new FutureTask<Object>(callable);
Platform.runLater(query);
try {
return query.get();
}
catch(InterruptedException e)
{
Thread.currentThread().interrupt();
return null;
}
catch(ExecutionException e)
{
Log.error(LOG_COMPONENT, "execution exception in callable object processing:" + e.getClass().getName() + ":" + e.getMessage());
throw new RuntimeException(e);
}
}

static public void runInFxThreadSync(Runnable runnable)
{
NullCheck.notNull(runnable, "runnable");
if(Platform.isFxApplicationThread())
try {
runnable.run();
return;
}
catch(Throwable e)
{
Log.error(LOG_COMPONENT, "runnable object thrown an exception:" + e.getClass().getName() + ":" + e.getMessage());
return;
}
final FutureTask<Object> query=new FutureTask<Object>(runnable, null);
Platform.runLater(query);
try {
query.get();
return;
}
catch(InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
catch(ExecutionException e)
{
Log.error(LOG_COMPONENT, "execution exception in runnable object processing:" + e.getClass().getName() + ":" + e.getMessage());
throw new RuntimeException(e);
}
}

static public void runInFxThreadAsync(Runnable runnable)
{
NullCheck.notNull(runnable, "runnable");
if(Platform.isFxApplicationThread())
try {
runnable.run();
return;
}
catch(Throwable e)
{
Log.error(LOG_COMPONENT, "runnable object has thrown an exception:" + e.getClass().getName() + ":" + e.getMessage());
throw new RuntimeException(e);
}
final FutureTask<Object> query=new FutureTask<Object>(runnable, null);
Platform.runLater(query);
return;
}
}
264 changes: 264 additions & 0 deletions src/main/java/org/luwrain/graphical/browser/Base.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
/*
Copyright 2012-2020 Michael Pozhidaev <[email protected]>
Copyright 2015-2016 Roman Volovodov <[email protected]>
This file is part of LUWRAIN.
LUWRAIN is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
LUWRAIN is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
*/

package org.luwrain.graphical.browser;

import java.io.*;
import java.util.*;

import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.scene.input.KeyEvent;
import netscape.javascript.JSObject;

import org.w3c.dom.Node;
import org.w3c.dom.html.*;
import org.w3c.dom.views.DocumentView;
import com.sun.webkit.dom.DOMWindowImpl;

import org.luwrain.core.*;
//import org.luwrain.interaction.javafx.*;
import org.luwrain.browser.BrowserEvents;
import org.luwrain.browser.BrowserParams;
import org.luwrain.graphical.*;

abstract class Base
{
static private final String RESCAN_RESOURCE_PATH = "org/luwrain/interaction/javafx/injection.js";
static final String LOG_COMPONENT = "web";

/*
static final class DomScanResult
{
final DOMWindowImpl window;
final List<NodeInfo> dom = new Vector();
final Map<Node, Integer> domMap = new HashMap();
DomScanResult(DOMWindowImpl window)
{
NullCheck.notNull(window, "window");
this.window = window;
}
}
*/

protected final String injectedScript;
public final WebView webView;
protected final WebEngine webEngine;
protected DomScanResult domScanRes = null;

protected JSObject injectionRes = null;
protected JSObject jsWindow = null;

protected Base(BrowserParams params) throws IOException
{
NullCheck.notNull(params, "params");
NullCheck.notNull(params.events, "params.events");
NullCheck.notNull(params.userAgent, "params.userAgent");
NullCheck.notNull(params.userDataDir, "params.userDataDir");
FxThread.ensure();
final BrowserEvents events = params.events;
this.injectedScript = readInjectedScript();
this.webView = new WebView();
this.webEngine = webView.getEngine();
this.webEngine.setUserDataDirectory(params.userDataDir);
this.webEngine.setUserAgent(params.userAgent);
this.webEngine.setJavaScriptEnabled(params.javaScriptEnabled);
this.webEngine.getLoadWorker().stateProperty().addListener((ov,oldState,newState)->onStateChanged(events, ov, oldState, newState));
this.webEngine.getLoadWorker().progressProperty().addListener((ov,o,n)->events.onProgress(n));
this.webEngine.setOnAlert((event)->events.onAlert(event.getData()));
this.webEngine.setPromptHandler((event)->events.onPrompt(event.getMessage(),event.getDefaultValue()));
this.webEngine.setConfirmHandler((param)->events.onConfirm(param));
this.webEngine.setOnError((event)->events.onError(event.getMessage()));
this.webView.setOnKeyReleased((event)->onKeyReleased(event));
this.webView.setVisible(false);
}

abstract void setVisibility(boolean enabled);


Object executeScript(String script)
{
NullCheck.notNull(script, "script");
if(script.trim().isEmpty() || webEngine == null)
return null;
return webEngine.executeScript(script);
}

private void resetInjectionRes()
{
FxThread.ensure();
this.injectionRes = null;
}

private void runInjection()
{
try {
final JSObject window = (JSObject)webEngine.executeScript("window");
window.setMember("console",new MyConsole());
this.injectionRes = (JSObject)webEngine.executeScript(injectedScript);
if (injectionRes == null)
Log.warning(LOG_COMPONENT, "the injection result is null after running the injection script");
}
catch(Throwable e)
{
Log.error(LOG_COMPONENT, "unable to run a browser injection:" + e.getClass().getName() + ":" + e.getMessage());
e.printStackTrace();
}
}

protected void update()
{
FxThread.ensure();
try {
if(injectionRes == null || injectionRes.getMember("name").equals("_luwrain_"))
return;
final HTMLDocument webDoc = (HTMLDocument)webEngine.getDocument();
if(webDoc == null)
{
Log.warning(LOG_COMPONENT, "no web document");
return;
}
final DOMWindowImpl window = (DOMWindowImpl)((DocumentView)webDoc).getDefaultView();
this.domScanRes = new DomScanResult(window);
final JSObject js = (JSObject)injectionRes.getMember("dom");
Object o = null;
for(int i=0;!(o=js.getSlot(i)).getClass().equals(String.class);i++)
{
final JSObject rect=(JSObject)((JSObject)o).getMember("r");
final Node n=(Node)((JSObject)o).getMember("n");
int x = 0;
int y = 0;
int width = 0;
int height = 0;
if(rect != null)
{
x = (int)jsLong(rect.getMember("left"));
y = (int)jsLong(rect.getMember("top"));
width=(int)jsLong(rect.getMember("width"));
height=(int)jsLong(rect.getMember("height"));
}
final NodeInfo info = new NodeInfo(n, x, y, width, height);
domScanRes.domMap.put(n, i);
domScanRes.dom.add(info);
}
for(NodeInfo info: domScanRes.dom)
{
final Node parent = info.getNode().getParentNode();
if(domScanRes.domMap.containsKey(parent))
info.setParentIndex(domScanRes.domMap.get(parent).intValue());
}
this.jsWindow = (JSObject)webEngine.executeScript("window");
Log.debug(LOG_COMPONENT, "DOM rescanning completed");
}
catch(Throwable e)
{
Log.error(LOG_COMPONENT, "unable to rescan DOM:" + e.getClass().getName() + ":" + e.getMessage());
e.printStackTrace();
}
}

private void onStateChanged(BrowserEvents events, ObservableValue<? extends State> ov, State oldState, State newState)
{
NullCheck.notNull(events, "events");
if (oldState == null || newState == null)
{
Log.warning(LOG_COMPONENT, "oldState or newState is null in BrowserBase.onStateChanged()");
return;
}
final BrowserEvents.State state;
switch(newState)
{
case CANCELLED:
//It could be a start of downloading, if cancelling was initiated not by a user
//FIXME:if(events.onDownloadStart(webEngine.getLocation()))
state = BrowserEvents.State.CANCELLED;
resetInjectionRes();
break;
case FAILED:
state = BrowserEvents.State.FAILED;
resetInjectionRes();
break;
case READY:
return;
case RUNNING:
state = BrowserEvents.State.RUNNING;
break;
case SCHEDULED:
state = BrowserEvents.State.SCHEDULED;
break;
case SUCCEEDED:
runInjection();
state = BrowserEvents.State.SUCCEEDED;
break;
default:
state = BrowserEvents.State.CANCELLED;
}
events.onChangeState(state);
}

private void onKeyReleased(KeyEvent event)
{
NullCheck.notNull(event, "event");
switch(event.getCode())
{
case ESCAPE:
setVisibility(false);
break;
default:break;
}
}

private String readInjectedScript() throws IOException
{
final InputStream is = getClass().getClassLoader().getResourceAsStream(RESCAN_RESOURCE_PATH);
if (is == null)
throw new IOException("No resource with the path 'RESCAN_RESOURCE_PATH'");
final BufferedReader r = new BufferedReader(new InputStreamReader(is));
try {
final StringBuilder b = new StringBuilder();
String line = null;
while((line = r.readLine()) != null)
b.append(line + "\n");
return new String(b);
}
finally {
r.close();
is.close();
}
}

static long jsLong(Object o)
{
if(o == null)
return 0;
if(o instanceof Double)
return (long)(double)o;
if(o instanceof Integer)
return (long)(int)o;
//throw new Exception("js have unknown number type: "+o.getClass().getName());
// FIXME: it can be happened or not?
return (long)Double.parseDouble(o.toString());
}

DomScanResult getDomScanResult()
{
return this.domScanRes;
}
}
Loading

0 comments on commit ddb9f07

Please sign in to comment.