Skip to content

[DRAFT] Experimental PHP WebView interface

License

Notifications You must be signed in to change notification settings

SerafimArts/Boson

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Boson

PHP 8.4+ Latest Stable Version Latest Unstable Version License MIT MetaStorm

Why Boson? Because it's not an Electron! And much easier than that =)

Also, this repository contains included high level PHP bindings for Saucer v6.0.

Simple Example

use Serafim\Boson\Application;

$app = new Application();

$app->webview->html = <<<'HTML'
    <button onclick="foo('HELLO');">Hello</button>
    HTML;

$app->webview->bind('foo', function (string $message): void {
    var_dump($message);
});

$app->run();

Requirements

  • PHP ^8.4
    • ext-ffi
Platform X86 AMD64 ARM64 Technologies
Windows âś“ âś“ âś– Windows API, WebView2
Linux âś“ âś“ âś“ GTK, WebKitGTK, Qt5/Qt6
macOS âś“ âś“ âś“ Cocoa, WebKit

At the moment, all binaries are supplied together with the library. In future versions, a separate platform-dependent installation of assemblies from the GitHub Actions CI is planned.

Windows

Requires Windows 10 or higher.

End-users must have the WebView2 runtime installed on their system for any version of Windows before Windows 11.

MacOS

It appears that no additional dependencies are required.

Linux and BSD

Supports both WebKitGTK and Qt5 as well as Qt6 on Linux.

The default backend is WebKitGtk:

  • Debian: apt install libgtk-4-1 libwebkitgtk-6.0-4
  • Fedora: dnf install gtk4 webkitgtk6.0
  • FreeBSD: pkg install webkit2-gtk4

You can download other assemblies separately from GitHub Actions. Automatic detection and installation of dependencies is not supported yet.

Installation

Boson package is available as Composer repository and can be installed using the following command in a root of your project:

$ composer require serafim/boson

Usage

This documentation corresponds to the latest release (see https://github.com/SerafimArts/Boson/tags). Behavior and code in the master branch may differ from what is shown below.

Application Configuration

An application configuration is built on the basis of the DTO hierarchy, which reflects the application hierarchy.

ApplicationCreateInfo
  // ... application configs

  window: WindowCreateInfo
    // ... window configs

    webview: WebViewCreateInfo
    // ... webview configs

In the PHP code it might look like this:

use Serafim\Boson\ApplicationCreateInfo;
use Serafim\Boson\WebView\WebViewCreateInfo;
use Serafim\Boson\Window\WindowCreateInfo;

$config = new ApplicationCreateInfo(
    debug: false,
    window: new WindowCreateInfo(
        title: 'Hello World!',
        width: 640,
        height: 480,
        webview: new WebViewCreateInfo(
            html: '<h1>Hello!</h1>',
            contextMenu: false,
        ),
    ),
);

The configuration is passed to the application itself and can be optional.

use Serafim\Boson\Application;
use Serafim\Boson\ApplicationCreateInfo;

// Create an application
$app = new Application();

// Create an application with configuration
$appWithConfig = new Application(new ApplicationCreateInfo(
    debug: false,
));

Application Launching

To start the application, you must call the run() method.

$app = new Serafim\Boson\Application();

$app->run();

Launching an application blocks the execution thread, meaning that no further code will be executed until the application is stopped.

$app = new Serafim\Boson\Application();

$app->run();

echo 'This code DOES NOT execute until the application is stopped!';

Application Quit

To stop the application, you should call the quit() method.

$app = new Serafim\Boson\Application();

/// ... do smth

$app->quit();

Since exit (according to the logic of work) occurs after the application is launched, and launching blocks the thread, it makes sense for the application to terminate after any event is fired.

use Serafim\Boson\Application;
use Serafim\Boson\Window\Event\WindowMinimized;

$app = new Application();

// The application will exit after minimizing.
$app->events->addEventListener(WindowMinimized::class, function () use ($app) {
    $app->quit();
});

$app->run();

It is also worth keeping in mind that the application may close automatically after ALL windows are closed. To disable this behavior, you should set the quitOnClose application config.

use Serafim\Boson\Application;
use Serafim\Boson\ApplicationCreateInfo;

$appWithConfig = new Application(new ApplicationCreateInfo(
    // Disables app quit when all windows are has been closed
    quitOnClose: false,
));

Window Title

To get or update the title, you should change the $title property

$app = new Serafim\Boson\Application();

$app->webview->title = 'New Title';

echo 'Current Title: ' . $app->webview->title;

$app->run();

Or set title from configuration

$app = new Serafim\Boson\Application(
    info: new \Serafim\Boson\ApplicationCreateInfo(
        window: new Serafim\Boson\Window\WindowCreateInfo(
            title: 'New Title',
        ),
    ),
);

$app->run();

Window Size

To obtain window size, use the $size property.

$app = new Serafim\Boson\Application();

echo $app->window->size; // Size(640 Ă— 480)

echo $app->window->size->width; // int(640)
echo $app->window->size->height; // int(480)

$app->run();

You also may set default window size via configuration.

$app = new Serafim\Boson\Application(
    info: new \Serafim\Boson\ApplicationCreateInfo(
        window: new Serafim\Boson\Window\WindowCreateInfo(
            width: 640,
            height: 480,
        ),
    ),
);

$app->run();

Window Resizing

To change the size, use the $size property.

$app = new Serafim\Boson\Application();

// Update window size to 640Ă—480
$app->window->size->update(640, 480);

// Or set the dimensions explicitly using new Size object
$app->window->size = new Serafim\Boson\Window\Size\Size(640, 480);

$app->run();

In addition, each window size can also be changed separately.

$app = new Serafim\Boson\Application();

$app->window->size->width = 640;
$app->window->size->height = 480;

$app->run();

To disable the ability to resize a window, pass the appropriate resizable window configuration option.

$app = new Serafim\Boson\Application(
    info: new \Serafim\Boson\ApplicationCreateInfo(
        window: new Serafim\Boson\Window\WindowCreateInfo(
            resizable: false,
        ),
    ),
);

$app->run();

Window Max Size

To specify the maximum window size, use the $max property. The behavior of the property is identical to the $size property described above.

$app = new Serafim\Boson\Application();

echo $app->window->max; // Will display the maximum allowed window size

$app->window->max->update(1024, 768);

$app->run();

Window Min Size

To specify the maximum window size, use the $min property. The behavior of the property is identical to the $size property described above.

$app = new Serafim\Boson\Application();

echo $app->window->min; // Will display the minimum allowed window size

$app->window->min->update(1024, 768);

$app->run();

Window Dark Mode

To set the dark mode (dark theme), use the $isDarkModeEnabled window's property.

$app = new Serafim\Boson\Application();

$app->window->isDarkModeEnabled = true;

if ($app->window->isDarkModeEnabled) {
    echo 'Dark mode is enabled!';
}

$app->run();

Or set dark mode from configuration

$app = new Serafim\Boson\Application(
    info: new \Serafim\Boson\ApplicationCreateInfo(
        window: new Serafim\Boson\Window\WindowCreateInfo(
            darkMode: true,
        ),
    ),
);

$app->run();

Window Decorations

You can DISABLE the standard window`s title bar, minimize, maximize and close buttons. To do this, you should set the false for $isDecorated property.

$app = new Serafim\Boson\Application();

$app->window->isDecorated = false;

$app->run();

Or via configuration options.

$app = new Serafim\Boson\Application(
    info: new \Serafim\Boson\ApplicationCreateInfo(
        window: new Serafim\Boson\Window\WindowCreateInfo(
            decorated: false,
        ),
    ),
);

$app->run();

Please note that by disabling the standard title bar and buttons you will not be able to control the window (moving and resizing), but you can implement this behavior yourself using HTML attributes.

$app = new Serafim\Boson\Application(
    info: new \Serafim\Boson\ApplicationCreateInfo(
        window: new Serafim\Boson\Window\WindowCreateInfo(
            decorated: false,
            webview: new \Serafim\Boson\WebView\WebViewCreateInfo(
                html: <<<'HTML'
                    <div data-webview-drag>
                        By dragging this element you can move the window
                    </div>
                    
                    <hr />
                    
                    <div data-webview-resize="br">
                        By dragging this element you can resize the window
                    </div>
                    HTML
            )
        ),
    ),
);

$app->run();
  • The data-webview-drag attribute defines the area by dragging which you can move the window.
  • The data-webview-resize attribute defines the area by dragging which you can resize the window.
    • An t attribute's value means the ability to resize vertically, top of the window.
    • An b attribute's value means the ability to resize vertically, bottom of the window.
    • An r attribute's value means the ability to resize horizontally, right of the window.
    • An l attribute's value means the ability to resize horizontally, left of the window.
    • Using combinations such as tl, tr, bl and br allows you to specify simultaneous resizing of the window vertically and horizontally.

WebView HTML Content

To set the content, you should use the $html property

$app = new Serafim\Boson\Application();

$app->webview->html = '<button>Do Not Click Me!</button>';

$app->run();

Or set html content from configuration

$app = new Serafim\Boson\Application(
    window: new Serafim\Boson\Window\WindowCreateInfo(
        webview: new Serafim\Boson\WebView\WebViewCreateInfo(
            html: '<button>Do Not Click Me!</button>',
        ),
    ),
);

$app->run();

Please note that reading this property is NOT possible. If you need to read the contents, use the data retrieval method.

use Serafim\Boson\Application;
use Serafim\Boson\WebView\Event\WebViewDomReady;

$app = new Application();

$app->webview->html = '<button>Do Not Click Me!</button>';

$app->events->addEventListener(WebViewDomReady::class, function () use ($app) {
    $html = $app->webview->request('document.body.innerHTML');
    
    var_dump($html);
});

$app->run();

WebView URL Navigation

To load content from the URL, you should use the $url property

$app = new Serafim\Boson\Application();

$app->webview->url = 'https://github.com/SerafimArts/Boson';

$app->run();

Or set URL from configuration

$app = new Serafim\Boson\Application(
    window: new Serafim\Boson\Window\WindowCreateInfo(
        webview: new Serafim\Boson\WebView\URLWebViewCreateInfo(
            url: 'https://github.com/SerafimArts/Boson',
        ),
    ),
);

$app->run();

WebView URL Info

In addition to navigation to the specified URL address, it is also possible to obtain information about the URL.

$app = new Serafim\Boson\Application();

$app->webview->url = 'https://github.com/SerafimArts/Boson';

echo 'URL: ' . $app->webview->url . "\n"; // string("about:blank")

$app->events->addEventListener(WebViewNavigated::class, function () use ($app) {
    echo 'URL: ' . $app->webview->url . "\n"; // string("https://github.com/SerafimArts/Boson")
});

$app->run();

In addition, you can get separate information about the URL parts.

$app = new Serafim\Boson\Application();

$app->webview->url = 'https://github.com/SerafimArts/Boson';

$app->events->addEventListener(WebViewNavigated::class, function () use ($app) {
    echo 'Scheme: ' . $app->webview->url->scheme . "\n"; // string("https")
    echo 'Host:   ' . $app->webview->url->host . "\n";   // string("github.com")
    echo 'Path:   ' . $app->webview->url->path . "\n";   // string("/SerafimArts/Boson")
});

$app->run();

WebView Code Evaluation

You can execute arbitrary code directly on current WebView

$app = new Serafim\Boson\Application();

// Load empty page
$app->webview->html = '';
// Evaluate JS code on this page
$app->webview->eval('document.write("Hello World!")');

$app->run();

WebView Scripts

You can register a JavaScript code that will be applied to any page.

$app = new Serafim\Boson\Application();

$app->webview->scripts->add(<<<'JS'
    alert('hello');
    JS);

$app->run();

Or set scripts from configuration

$app = new Serafim\Boson\Application(
    window: new Serafim\Boson\Window\WindowCreateInfo(
        webview: new Serafim\Boson\WebView\WebViewCreateInfo(
            scripts: [<<<'JS'
                alert('hello');
                JS],
        ),
    ),
);

$app->run();

It is worth noting that adding code is available in several options.

$app = new Serafim\Boson\Application();

// "This code will be executed BEFORE the page loads: undefined"
$app->webview->scripts->preload(<<<'JS'
    console.log('This code will be executed BEFORE the page loads: ' + document?.body?.innerHTML);
    JS);

// "This code will be executed AFTER the page loads: <b>hello</b>"
$app->webview->scripts->add(<<<'JS'
    console.log('This code will be executed AFTER the page loads: ' + document?.body?.innerHTML);
    JS);
    
$app->webview->html = '<b>hello</b>';

$app->run();

WebView Function Bindings

You can create a function that can be called directly from WebView

$app = new Serafim\Boson\Application();

$app->webview->bind('foo', function () { 
    var_dump('Executed!');
});

$app->run();

Or set functions list from configuration

$app = new Serafim\Boson\Application(
    window: new Serafim\Boson\Window\WindowCreateInfo(
        webview: new Serafim\Boson\WebView\WebViewCreateInfo(
            functions: ['foo' => function () { 
                var_dump('Executed!');
            }],
        ),
    ),
);

$app->run();

WebView Requests

You can directly get data from WebView context.

$app = new Serafim\Boson\Application();

$app->events->addEventListener(WebViewDomReady::class, function () use ($app) {
    var_dump($app->webview->request('document.location')); 
    // array:10 [
    //   "ancestorOrigins" => []
    //   "href" => "https://nesk.me/"
    //   "origin" => "https://nesk.me"
    //   "protocol" => "https:"
    //   "host" => "nesk.me"
    //   "hostname" => "nesk.me"
    //   "port" => ""
    //   "pathname" => "/"
    //   "search" => ""
    //   "hash" => ""
    // ]
});

$app->webview->url = 'https://nesk.me';

$app->run();

Please note that the request CAN NOT be processed if the application is not running.

$app = new Serafim\Boson\Application();

var_dump($app->webview->request('document.location'));
//
// Uncaught Serafim\Boson\WebView\Requests\Exception\UnprocessableRequestException:
//      Request "document.location" could not be processed
//      because application is not running
//

$app->run();

Events and Intentions

Any event is a fact. Events can not be changed or rejected. Unlike events, an intent is an attempt by the application or any of its components to do something. Any intent can be rejected or modified.

To subscribe to events and intents, the $events field is used.

use Serafim\Boson\WebView\Event\WebViewNavigating;
use Serafim\Boson\WebView\Event\WebViewNavigated;

$app = new Serafim\Boson\Application();

$app->events->addEventListener(WebViewNavigating::class, function (WebViewNavigating $intention): void {
    echo 'Try to move on URL: ' . $intention->url . "\n";

    // Randomly block navigating to another URL
    if (\random_int(0, 1)) {
        $intention->cancel();
    }
});

$app->events->addEventListener(WebViewNavigated::class, function (WebViewNavigated $event): void {
    echo 'Navigated to URL: ' . $event->url . "\n";
});

$app->webview->url = 'https://nesk.me';

$app->run();

Debug Mode

To enable debug mode, you should define the debug: ?bool argument of the Application instance.

$app = new Serafim\Boson\Application(
    // true  - enable debug mode
    // false - disable debug mode
    // null  - autodetect debug mode
    debug: true, 
);

$app->run();

Custom Library

To define binary, you should define the library: ?non-empty-string argument of the Application instance.

$app = new Serafim\Boson\Application(
    // string - defines pathname to the library
    // null   - autodetect library
    library: __DIR__ . '/path/to/custom-webview.so',
);

$app->run();