Javaで簡易Webブラウザを作る（JavaFX WebView）

# 簡易Webブラウザを作る
昔の記事だけど、Javaでかなり本格的なWebブラウザを作れる[らしい](https://nompor.com/2018/06/09/post-3893/)ので、私も作ってみる。

# ブラウザを表示させる
参考元のソースで、「[戻る、進むを実装してみる(前のページ、次のページ)](https://nompor.com/2018/06/09/post-3893/#p5)」をまずは写経する。
（その時に、ファイル名/パッケージ名は任意に変えている。）

この時に、Mavenを使ってJavaFXを読み込もうとして少し苦労した。（わかってしまえば難しいことではなかった。。。）

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>webbrowser</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

    <repositories>
        <repository>
            <id>central</id>
            <url>https://repo1.maven.org/maven2/</url>
        </repository>
    </repositories>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>15</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-web -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-web</artifactId>
            <version>15.0.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <configuration>
                    <jlinkImageName>hellobrowser</jlinkImageName>
                    <launcher>launcher</launcher>
                    <mainClass>xyz.osamusasa.browser.MainApp</mainClass>
                </configuration>
            </plugin>

        </plugins>
    </build>
</project>
```

### 実行結果

![webbrowser_v1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1777490/89fdfd45-26d5-a2b7-961f-df68fe621f6e.png)



# タブ機能を追加する

## タブを新規に追加する機能

タブ機能は[javafx.scene.control.TabPane](https://openjfx.io/javadoc/11/javafx.controls/javafx/scene/control/TabPane.html#getSelectionModel())を使うことで簡単に実装できる。

```xml:mainPane.fxml(抜粋・変更前)
<center>
    <!-- 画面中央のWeb画面表示 -->
    <WebView fx:id="webView"></WebView>
</center>
```

```xml:mainPane.fxml(抜粋・変更後)
<center>
    <TabPane>
        <Tab>
            <!-- 画面中央のWeb画面表示 -->
            <WebView fx:id="webView"></WebView>
        </Tab>
    </TabPane>
</center>
```

### 実行結果

タブが表示されている（タブを消す×ボタンが表示されているだけだが。。）

![webbrowser_v2a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1777490/095cae1f-07f9-4fb3-1eed-f2c7ead38778.png)

## 他の必要な機能を実装する

これをベースに以下の機能を追加した。

- 新規タブボタン
- 戻るボタン・進むボタンを現在開いているタブに対して実行する
- URLがロードされるとそのページのタイトルをタブの名称に設定する。

```java:Controller.java
package xyz.osamusasa.browser;

import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebView;

import java.util.Optional;

public class Controller {
    @FXML private TextField addressBar;
    @FXML private TabPane tabPane;

    /**
     * 初期化処理(自動呼出し)
     *
     * initialize()メソッドは自動呼出しの対象となる
     */
    public void initialize() {
        loadNewTab("https://www.google.co.jp/");
    }

    /**
     * アドレスバーでEnter
     *
     * @param e ActionEvent
     */
    public void onAddressBarEvent(KeyEvent e) {
        switch(e.getCode()) {
            case ENTER:load(addressBar.getText());break;
            default:
                break;
        }
    }

    /**
     * 戻るボタン
     *
     * @param e ActionEvent
     */
    public void onBack(ActionEvent e) {
        currentWebView().ifPresent(webView -> {
            WebEngine engine = webView.getEngine();
            WebHistory history = engine.getHistory();
            if ( history.getCurrentIndex() > 0 ) history.go(-1);
        });
    }

    /**
     * 進むボタン
     *
     * @param e ActionEvent
     */
    public void onNext(ActionEvent e) {
        currentWebView().ifPresent(webView -> {
            WebEngine engine = webView.getEngine();
            WebHistory history = engine.getHistory();
            if (history.getCurrentIndex() < history.getEntries().size() - 1) history.go(1);
        });
    }

    /**
     * 新規タブボタン
     *
     * @param e ActionEvent
     */
    public void onNewTab(ActionEvent e) {
        loadNewTab("https://www.google.co.jp/");
    }

    /**
     * URLロード
     *
     * @param search URL
     */
    public void load(String search) {
        final String searchStr;
        if ( !search.matches("^https{0,1}://.+") ) {
            //httpじゃないならgoogle検索を実行
            searchStr = "https://www.google.co.jp/search?q="+search;
        } else {
            searchStr = search;
        }

        currentWebView().ifPresentOrElse(
                webView -> webView.getEngine().load(searchStr),
                () -> loadNewTab(searchStr)
        );
    }

    /**
     * 新規タブでURLロード
     *
     * @param search URL
     */
    private void loadNewTab(String search) {

        // 新規タブ作成
        Tab tab = new Tab(search);
        WebView view = new WebView();
        WebEngine engine = view.getEngine();

        //ページロードイベントでアドレスバーの更新
        engine.getLoadWorker().stateProperty().addListener(
                (ov, oldState, newState) -> {
                    if (newState == State.SUCCEEDED) {
                        addressBar.setText(engine.getLocation());
                        tab.setText(engine.getTitle());
                    }
                }
        );

        // URLロード
        engine.load(search);

        // 作成したタブを追加
        tab.setContent(view);
        tabPane.getTabs().add(tab);

        // 新規タブを表示
        tabPane.getSelectionModel().select(tab);
    }

    /**
     * 現在選択されているタブのWebViewを返す
     *
     * @return 選択されているWebView
     */
    private Optional<WebView> currentWebView() {
        return Optional.ofNullable(tabPane.getSelectionModel().getSelectedItem())
                .map(Tab::getContent)
                .filter(n -> n instanceof WebView)
                .map(n -> (WebView)n);
    }
}

```

```xml:mainPane.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TabPane?>

<!-- fx:controllerで関連付けるコントローラクラスを指定 -->
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.osamusasa.browser.Controller">
    <top>
        <!-- 画面ヘッダーの表示 -->
        <BorderPane styleClass="topView">
            <left>
                <FlowPane prefWidth="100">
                    <Button onAction="#onBack" text="戻る"></Button>
                    <Button onAction="#onNext" text="進む"></Button>
                </FlowPane>
            </left>
            <center>
                <TextField fx:id="addressBar" text="" onKeyPressed="#onAddressBarEvent" ></TextField>
            </center>
            <right>
                <FlowPane prefWidth="100">
                    <Button onAction="#onNewTab" text="+"></Button>
                </FlowPane>
            </right>
        </BorderPane>
    </top>
    <center>
        <TabPane fx:id="tabPane">
        </TabPane>
    </center>
</BorderPane>
```

### 実行結果

（実行した日付が[バレて](https://www.youtube.com/watch?v=hy7tHQUR3TM)しまう。。。）

![webbrowser_v2b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1777490/73b59321-68be-aff3-b68a-9ff0fe512cda.png)

# ブックマーク機能

ブックマークの表示方法はツールバーやサイドバー、別タブ、メニューなど様々あるが、今回はfirefox流に別ウィンドウで表示させるものを実装する。

### 実行結果

- ブックマークを追加する

![webbrowser_v3a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1777490/c9ff4edd-d3a6-c88b-d066-12d5ef8eac82.gif)

- ブックマークからWebページを開く

![webbrowser_v3a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1777490/2faac3ba-fec9-a9a5-a6b5-b764a452dc25.gif)

## WIP...

思い出したら続き書く