1

Using JavaFX webview, I'm changing several html attributes (background, border, and color) for all website DOM tags globally. My intent is to have a customized dark theme that performs much like a high contrast settings would. The only problem I can't resolve is the slight delay the code in the last method setWebpageTheme(Boolean succeeded) produces. It results in a bright white flicker as the html css attributes are applied and change the white to dark page backgrounds. Please see the complete code below.

Main Class:

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    public Parent createContent() {
        final WebBrowser browser = new WebBrowser();
        return browser;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setResizable(true);
        Scene scene = new Scene(createContent());
        primaryStage.setTitle("Eric's Web Demo");
        scene.getStylesheets().add("style/template.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Web-browser GUI:

import org.w3c.dom.Attr;
import org.w3c.dom.NodeList;

import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.geometry.Dimension2D;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebHistory.Entry;
import javafx.scene.web.WebView;

public class WebBrowser extends BorderPane {

    private static final Dimension2D DIM = new Dimension2D(1220, 680);
    private final WebView webView;
    private final WebEngine webEngine;
    private final ComboBox<String> addressBox;
    private final TextField searchField;
    private final Button backButton;
    private final Button forwardButton;
    private final Button proceedButton;
    private final Button searchButton;
    private final ProgressBar progressBar;
    private final Label statusLabel;

    public WebBrowser() {

        this.setMinSize(DIM.getWidth(), DIM.getHeight());
        this.setPrefSize(DIM.getWidth(), DIM.getHeight());

        backButton = new Button("\uD83E\uDC78");
        backButton.setOnAction(this::backButtonListener);

        forwardButton = new Button("\u2794");
        forwardButton.setDefaultButton(true);
        forwardButton.setOnAction(this::forwardButtonListener);

        proceedButton = new Button("Go");
        proceedButton.setOnAction(this::proceedButtonListener);

        final HBox buttonGroup = new HBox();
        buttonGroup.setSpacing(5);
        buttonGroup.setPadding(new Insets(10, 5, 10, 5));
        buttonGroup.getChildren().addAll(backButton, forwardButton, proceedButton);

        addressBox = new ComboBox<String>();
        addressBox.setItems(FXCollections.observableArrayList());
        addressBox.setValue("http://stackoverflow.com/questions/32783532/applying-css-file-to-javafx-webview");
        addressBox.setOnAction(this::proceedButtonListener);
        addressBox.setEditable(true);
        addressBox.setMaxWidth(Double.MAX_VALUE);

        searchField = new TextField();
        searchField.setPromptText("\uD83D\uDD0D Search");

        searchButton = new Button("\uD83D\uDD0D");
        searchButton.setDefaultButton(true);
        searchButton.setOnAction(this::searchButtonListener);

        statusLabel = new Label("Status: ");
        progressBar = new ProgressBar(0);   

        webView = new WebView();
        webEngine = webView.getEngine();
        webEngine.load(addressBox.getValue());
        webEngine.getLoadWorker().stateProperty().addListener(this::stateChangeListener);
        webEngine.locationProperty().addListener(this::urlChangeListener);
        progressBar.progressProperty().bind(webEngine.getLoadWorker().progressProperty());

        final WebHistory history = webEngine.getHistory();
        history.getEntries().addListener(this::historyListener);

        final GridPane root = new GridPane();
        GridPane.setConstraints(buttonGroup,  0, 0, 1, 1, HPos.LEFT,   VPos.CENTER, Priority.NEVER,  Priority.NEVER);
        GridPane.setConstraints(addressBox,   1, 0, 1, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.NEVER);
        GridPane.setConstraints(searchField,  2, 0, 1, 1, HPos.RIGHT,  VPos.CENTER, Priority.NEVER,  Priority.NEVER);
        GridPane.setConstraints(searchButton, 3, 0, 1, 1, HPos.RIGHT,  VPos.CENTER, Priority.NEVER,  Priority.NEVER);
        GridPane.setConstraints(webView,      0, 1, 4, 1, HPos.LEFT,   VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS);
        GridPane.setConstraints(statusLabel,  0, 2, 1, 1, HPos.LEFT,   VPos.CENTER, Priority.NEVER,  Priority.NEVER);
        GridPane.setConstraints(progressBar,  3, 2, 3, 1, HPos.RIGHT,  VPos.CENTER, Priority.NEVER,  Priority.NEVER);
        GridPane.setMargin(addressBox,   new Insets(5, 0, 5, 0));
        GridPane.setMargin(searchField,  new Insets(5, 5, 5, 5));
        GridPane.setMargin(searchButton, new Insets(5, 8, 5, 0));
        GridPane.setMargin(statusLabel,  new Insets(5, 0, 5, 5));
        GridPane.setMargin(progressBar,  new Insets(5, 5, 5, 5));
        root.addRow(0, buttonGroup, addressBox, searchField, searchButton);
        root.addRow(1, webView); 
        root.addRow(2,statusLabel, progressBar);

        this.setCenter(root);       
    }  

    public void historyListener(Change<? extends Entry> changeValue) {
        changeValue.next();
        for (Entry entry : changeValue.getRemoved()) {
            addressBox.getItems().remove(entry.getUrl());
            System.out.print("Removed url: ");
            System.out.println(entry.getUrl());
        }
        for (Entry entry : changeValue.getAddedSubList()) {
            System.out.print("Added url: ");
            addressBox.getItems().add(entry.getUrl());
            System.out.println(entry.getUrl());
        }
    }

    public void progressBarListener(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
        progressBar.setProgress(new_val.doubleValue());
    }

    private void stateChangeListener(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {

        setWebpageTheme(newValue == State.SUCCEEDED);
        String output = newValue.toString().toLowerCase();
        statusLabel.setText("Status: " + output);
    }

    private void urlChangeListener(ObservableValue<? extends String> observable, String oldValue, String newValue) {
        addressBox.setValue(newValue);
    }

    public void forwardButtonListener(ActionEvent event) {
        webEngine.executeScript("history.forward()");
    }

    private void backButtonListener(ActionEvent event) {
        webEngine.executeScript("history.back()");
    }

    private void searchButtonListener(ActionEvent event) {
        String google = "http://www.google.com/search?q=" + searchField.getText();
        webEngine.load(google.startsWith("http://") || google.startsWith("https://") 
                ? google : "http://" + google);
    }

    private void proceedButtonListener(ActionEvent event) {
        String url = addressBox.valueProperty().getValue();
        webEngine.load(url.startsWith("http://") || url.startsWith("https://") 
                ? url : "http://" + url);
    }

    private void setWebpageTheme(Boolean succeeded) {
        // Can safely access DOM and set styles.
        if (succeeded == true) {
            // This gives the DOM Document for the web page.
            NodeList htmlTags = webEngine.getDocument().getElementsByTagName("*");
            Attr newAttr = null;
            for (int i = 0; i < htmlTags.getLength(); i++) {
                newAttr = webEngine.getDocument().createAttribute("style");
                newAttr.setValue("background-color: #222; border-color: #333; background: #222; color: #bbb; ");
                htmlTags.item(i).getAttributes().setNamedItem(newAttr);
            }
        }
    }
}

Text file-path name: /style/template.css < -fx GUI settings (not for web-pages)

.root{
   -fx-background: rgb(44,44,44);
}

.button {
   -fx-border-radius: 5;
   -fx-background-radius: 5;
   -fx-min-height: 32;
   -fx-min-width: 40;
   -fx-background-color: radial-gradient(radius 100%, rgb(22, 33, 188), rgb(3,22,122));
   -fx-text-fill: rgb(196,188,222);
}

TextField {
   -fx-border-radius: 5;
   -fx-background-radius: 5;
   -fx-max-height: 32;
   -fx-min-width: 280;
   -fx-border-color: #555;
   -fx-border-width: 1 1 1 1;
   -fx-background-color: #333;
   -fx-text-fill: rgb(196,188,222);
}

.combo-box-base {
   -fx-border-radius: 5;
   -fx-background-radius: 5;
   -fx-min-height: 35;
   -fx-background-color: #333;
   -fx-border-color: transparent;
   -fx-border-width: 2 2 2 2;
}

.combo-box-base .arrow-button {
    -fx-background-color: radial-gradient(radius 100%, rgb(22, 33, 188), rgb(3,22,122));
}

.combo-box .combo-box-popup, .list-view, .list-cell {
    -fx-background-color:  #333;
    -fx-text-fill: rgb(155,188,166);
}

.combo-box .text-input {
    -fx-border-radius: 5;
    -fx-background-radius: 5;
    -fx-background-color: #333;
    -fx-border-color: #555;
    -fx-border-width: 1 1 1 1;
    -fx-text-fill: rgb(155,188,166);
}

.scroll-bar {
    -fx-background-color: #222;
}

.scroll-bar .thumb {
    -fx-background-color: radial-gradient(radius 100%, rgb(22, 33, 188), rgb(3,22,122));
}

.label {
    -fx-font: 14px "Arial";
    -fx-border-color: rgb(57, 58, 59);
    -fx-border-width: 2 2 2 2;
    -fx-text-fill: rgb(155,188,166);
}

.progress-bar > .track {
  -fx-text-box-border: rgb(44, 44, 44);
  -fx-control-inner-background: rgb(22, 22, 44);
}
Eric C.
  • 11
  • 4

1 Answers1

0

I've come up with a solution to minimize the impact of the dark background delay or to be specific, the inefficiency in which I'm adding attributes to all DOM html tags. I've added the below html stylesheet using the following code prior to invoking the method setWebpageTheme(boolean succeed); It's not 100% perfect but it's bearable for my purposes. If someone finds a better solution, I'm still interested.

    webEngine.setUserStyleSheetLocation(getClass().getResource("/style/style.css").toString());

style.css

*, a, abbr, acronym, address, applet, b, big, blockquote, body, br, button, caption, center, cite, code, dd, del, dfn, div,
dl, dt, element, em, em, fieldset, font, form, h1, h2, h3, h4, h5, h6, head, header, html, html, i, iframe, iframe, img, 
img, input, ins, kbd, label, label, legend, li, link, meta, nav, noscript, object, ol, ol, p, path, pre, q, s, samp, script,
small, span, strike, strong, style, sub, sup, svg, table, tbody, td, textarea, tfoot, th, thead, title, tr, tt, u, ul, var { 
    background-color: #222; border-color: #333; background: #222; color: #bbb; 
}
Eric C.
  • 11
  • 4