import { ImageArea } from './image-area.js';
import { InputForm } from './input-form.js';
import { VRSPACEUI } from '../vrspace-ui.js';
/**
* Experimental remote web browser. Communicates with server-side component that controls a headless web browser with Selenium.
* Requires firefox on the server, and org.vrspace.server.selenium-enabled=true property.
* Forwards click to the server, and loads resulting screenshots.
* Severe limitation is that there's no such thing as 'web page is ready' signal in web browser.
*/
export class RemoteBrowser extends ImageArea {
constructor(scene) {
super(scene);
this.size = .3;
this.position = new BABYLON.Vector3(0, .15, -0.05);
this.depth = 0;
this.maxDepth = 0;
this.inputForm = new InputForm("Enter");
}
endpoint() {
return VRSPACEUI.contentBase+"/vrspace/api/webbrowser"
}
async available() {
let response = await fetch(this.endpoint()+"/available")
let result = await response.json();
return "true" === result;
}
async get(url) {
this.url = url;
let response = await fetch(this.endpoint()+"/get?url="+url);
let bytes = await response.blob();
this.loadData(bytes,url);
this.processHeaders(response.headers);
}
async click(x,y) {
let response = await fetch(this.endpoint()+"/click?x="+x+"&y="+y);
let bytes = await response.blob();
let activeElement = response.headers.get('active-element');
if ( "input" === activeElement || "textarea" === activeElement) {
console.log("TODO input required: "+activeElement);
this.inputForm.setEnabled(true);
}
this.loadData(bytes);
this.processHeaders(response.headers);
}
async enter(text) {
this.inputForm.setEnabled(false);
let response = await fetch(this.endpoint()+"/enter?text="+text);
let bytes = await response.blob();
this.loadData(bytes);
}
async scroll(pixels) {
let response = await fetch(this.endpoint()+"/scroll?pixels="+pixels);
let bytes = await response.blob();
this.loadData(bytes);
}
async close() {
let response = await fetch(this.endpoint()+"/close");
if ( response.status == 204 ) {
this.dispose();
} else {
let bytes = await response.blob();
this.loadData(bytes);
this.processHeaders(response.headers);
}
}
async forward() {
let response = await fetch(this.endpoint()+"/forward");
let bytes = await response.blob();
this.loadData(bytes);
this.processHeaders(response.headers);
}
async back() {
let response = await fetch(this.endpoint()+"/back");
if ( response.status == 204 ) {
this.dispose();
} else {
let bytes = await response.blob();
this.loadData(bytes);
this.processHeaders(response.headers);
}
}
async quit() {
await fetch(this.endpoint()+"/quit");
this.dispose();
}
processHeaders(headers) {
let depth = headers.get("history-position");
let maxDepth = headers.get("history-length");
let windows = headers.get("browser-windows");
if ( depth && maxDepth ) {
console.log("Depth: "+depth+" max "+maxDepth+" clicked "+headers.get("clicked-element")+" active "+headers.get("active-element")+" windows "+windows);
this.depth = depth;
this.maxDepth = maxDepth;
this.buttonForward.isVisible = depth < maxDepth;
}
this.buttonBack.isVisible = (depth != 0);
this.buttonClose.text = "Close:"+windows;
}
show() {
super.show();
this.buttonClose = new BABYLON.GUI.HolographicButton("close");
this.buttonClose.imageUrl = VRSPACEUI.contentBase+"/content/icons/close.png";
VRSPACEUI.guiManager.addControl(this.buttonClose);
this.buttonClose.linkToTransformNode(this.handles.box);
this.buttonClose.position = new BABYLON.Vector3(5,0,0);
this.buttonClose.scaling = new BABYLON.Vector3(2,2,2);
this.buttonClose.text = "Close";
this.buttonClose.onPointerDownObservable.add( ()=>this.close() );
this.buttonClose.isVisible = true;
this.buttonBack = new BABYLON.GUI.HolographicButton("back");
this.buttonBack.imageUrl = VRSPACEUI.contentBase+"/content/icons/back.png";
VRSPACEUI.guiManager.addControl(this.buttonBack);
this.buttonBack.linkToTransformNode(this.handles.box);
this.buttonBack.position = new BABYLON.Vector3(8,0,0);
this.buttonBack.scaling = new BABYLON.Vector3(2,2,2);
this.buttonBack.text = "Back";
this.buttonBack.onPointerDownObservable.add( ()=>this.back() );
this.buttonBack.isVisible = false;
this.buttonForward = new BABYLON.GUI.HolographicButton("forward");
this.buttonForward.imageUrl = VRSPACEUI.contentBase+"/content/icons/forward.png";
VRSPACEUI.guiManager.addControl(this.buttonForward);
this.buttonForward.linkToTransformNode(this.handles.box);
this.buttonForward.position = new BABYLON.Vector3(46,0,0);
this.buttonForward.scaling = new BABYLON.Vector3(2,2,2);
this.buttonForward.text = "Forward";
this.buttonForward.onPointerDownObservable.add( ()=>this.forward() );
this.buttonForward.isVisible = false;
this.buttonQuit = new BABYLON.GUI.HolographicButton("quit");
this.buttonQuit.imageUrl = VRSPACEUI.contentBase+"/content/icons/delete.png";
VRSPACEUI.guiManager.addControl(this.buttonQuit);
this.buttonQuit.linkToTransformNode(this.handles.box);
this.buttonQuit.position = new BABYLON.Vector3(55,0,0);
//this.buttonQuit.scaling = new BABYLON.Vector3(2,2,2);
this.buttonQuit.text = "Quit";
this.buttonQuit.onPointerDownObservable.add( ()=>this.quit() );
this.inputForm.size = .1;
let formPlane = this.inputForm.init();
formPlane.parent = this.group;
formPlane.position = new BABYLON.Vector3(0,-0.12,-.05);
formPlane.setEnabled(false);
this.inputForm.addListener(text=>this.enter(text));
}
dispose() {
super.dispose();
this.inputForm.dispose();
if ( this.buttonBack ) {
this.buttonBack.dispose();
this.buttonForward.dispose();
this.buttonQuit.dispose();
}
}
}