diff --git a/update-11ty/.stamp b/update-11ty/.stamp new file mode 100644 index 000000000..ddab627a6 --- /dev/null +++ b/update-11ty/.stamp @@ -0,0 +1 @@ +Tue Nov 26 23:42:25 UTC 2024 diff --git a/update-11ty/README/index.html b/update-11ty/README/index.html new file mode 100644 index 000000000..0a3cc271f --- /dev/null +++ b/update-11ty/README/index.html @@ -0,0 +1,46 @@ +

wpewebkit.org

+

wepwebkit.org is a statically generated site for WPE. The website aims to be simple to maintain and with little complexity and dependencies. It is built with 11ty and Liquid templates - and that’s pretty much it.

+

The actual site is hosted by Igalia who are the primary maintainers of the project.

+

Development

+

In order to setup, you just just have to check it out, switch to the checked out directory and then npm install.

+

Building wpewebkit.org site locally

+

In order to test it all you need to do is

+
npm install && npm run serve
+

This will build the project, start a server, and your terminal will provide you useful links to actually get to it.

+

Structure

+ +

In the root directory you will also find some top level files - index.htmlwhich is the template for the main page, release.md which is the template for creating the release pages, a package.json which is, well, what you’d expect and .eleventy.js which does some very understandable work in creating a date filter for outputting dates (because of how some of the old content exists), and creates some ‘recent’ collections for simple templating of things like release notes and security advisories.

+

Updating wpewebkit.org site

+ +

Updating content

+

In order to write a new release or security advisory piece in the website +you just have to create a new file inside _posts folder using +Markdown syntax.

+

Should you need anything else, you will find 11ty’s documentation pretty helpful.

diff --git a/update-11ty/_authors/aperez/index.html b/update-11ty/_authors/aperez/index.html new file mode 100644 index 000000000..920177190 --- /dev/null +++ b/update-11ty/_authors/aperez/index.html @@ -0,0 +1,17 @@ +
+
+
+
+
+
+ Head shot of Adrián Pérez + This article was written by Adrián Pérez. +
+ I have been working on WebKit since 2012, with a focus on + environment integration, embedding, and distribution. Igalia + has been a life-long project since even earlier. +
+
+
+
+
diff --git a/update-11ty/_authors/csaavedra/index.html b/update-11ty/_authors/csaavedra/index.html new file mode 100644 index 000000000..6aeca7bd5 --- /dev/null +++ b/update-11ty/_authors/csaavedra/index.html @@ -0,0 +1,14 @@ +
+
+
+
+
+
+ + This article was written by Claudio Saavedra.

Claudio is long-time WebKit contributor from Igalia, working in different areas of WebKit, WPE, and the stack around it. +
+
+
+
+
+
diff --git a/update-11ty/_authors/llepage/index.html b/update-11ty/_authors/llepage/index.html new file mode 100644 index 000000000..cc8f008b8 --- /dev/null +++ b/update-11ty/_authors/llepage/index.html @@ -0,0 +1,18 @@ +
+
+
+
+
+
+ Head shot of Loïc Le Page + This article was written by Loïc Le Page. +
+ I have worked in different industries like video games, + cinema, and multimedia—the latter being where my + focus lies at the moment. Did anyone say Web engines need + that, too? +
+
+
+
+
diff --git a/update-11ty/_authors/lmoura/index.html b/update-11ty/_authors/lmoura/index.html new file mode 100644 index 000000000..584c23cdb --- /dev/null +++ b/update-11ty/_authors/lmoura/index.html @@ -0,0 +1,15 @@ +
+
+
+
+
+
+ + This article was written by Lauro Moura.

Lauro is webkit contributor from Igalia, working mainly on QA. +
+
+
+
+
+
+ diff --git a/update-11ty/_authors/magomez/index.html b/update-11ty/_authors/magomez/index.html new file mode 100644 index 000000000..308a56b77 --- /dev/null +++ b/update-11ty/_authors/magomez/index.html @@ -0,0 +1,14 @@ +
+
+
+
+
+
+ + This article was written by Miguel Gómez.

Miguel has been contributing to WebKit and WPE for almost ten years now, focusing specially on the graphics part of the code. +
+
+
+
+
+
diff --git a/update-11ty/_authors/nzimmermann/index.html b/update-11ty/_authors/nzimmermann/index.html new file mode 100644 index 000000000..8792ec2ae --- /dev/null +++ b/update-11ty/_authors/nzimmermann/index.html @@ -0,0 +1,15 @@ +
+
+
+
+
+
+ + Picture of Nikolas Zimmermann + This article was written by Nikolas Zimmermann.

I'm a proud Igalian since 2019 and have been working on WebKits predecessors since the early 2000s, namely kjs/khtml/ksvg, and kdom/kcanvas/ksvg2/khtml2 that all found their way into WebKit. Since that time, Web technology - especially SVG - is my main area of interest. +
+
+
+
+
+
diff --git a/update-11ty/_authors/pgriffis/index.html b/update-11ty/_authors/pgriffis/index.html new file mode 100644 index 000000000..cc9bbc414 --- /dev/null +++ b/update-11ty/_authors/pgriffis/index.html @@ -0,0 +1,15 @@ +
+
+
+
+
+
+ + This article was written by Patrick Griffis.

Patrick has been contributing to WebKit since 2018 and does work around networking, security, and the platform libraries WPE uses. +
+
+
+
+
+
+ \ No newline at end of file diff --git a/update-11ty/_authors/rbuis/index.html b/update-11ty/_authors/rbuis/index.html new file mode 100644 index 000000000..a88a9a56e --- /dev/null +++ b/update-11ty/_authors/rbuis/index.html @@ -0,0 +1,14 @@ +
+
+
+
+
+
+ + This article was written by Rob Buis.

Longtime WebKit/Blink hacker with a preference for open source. +
+
+
+
+
+
diff --git a/update-11ty/about/a-good-choice.html b/update-11ty/about/a-good-choice.html new file mode 100644 index 000000000..151674812 --- /dev/null +++ b/update-11ty/about/a-good-choice.html @@ -0,0 +1,324 @@ + + + + + + + + + + + + Why Choose WPE? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+

Why Choose WPE?

+

WPE WebKit is widely adopted in many industries, including digital signage, professional audio, video and broadcasting, home appliances, set-top boxes, and automative and in-flight infotainment systems.

+
+ +
+

If you need a fast and lightweight web runtime for Linux-based embedded devices that supports most current web standards, has hardware acceleration wherever it is advantageous, and has a strong focus on multimedia applications, WPE WebKit is a great choice.

+

WPE WebKit offers great possibilities for deployment on different platforms, thanks to its underlying design which allows for integration in a variety of hardware configurations. At a minimum, only EGL and OpenGL ES 2 support and basic GStreamer integration are required.

+
+
+

Some advantages of WPE WebKit

+ +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/architecture.html b/update-11ty/about/architecture.html new file mode 100644 index 000000000..63d393e32 --- /dev/null +++ b/update-11ty/about/architecture.html @@ -0,0 +1,309 @@ + + + + + + + + + + + + WPE Architecture + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

WPE Architecture

+

WPE is the official WebKit port for embedded platforms. WPE is uniquely designed +for embedded systems in that it doesn’t depend on any user-interface toolkit +such as the traditional Cocoa, GTK, etc toolkits.

+
+
+ +
+
+

Web page rendering

+

WPE is considered a hybrid port because it defers the final web page delivery for display to a rendering backend. A traditional port would provide a widget for a given toolkit, but WPE opted for a different and more flexible approach.

+

The common interface between WPEWebKit and its rendering backends is provided by +libwpe. On one side, once +WPEWebKit has a graphical representation of the final composited Web page ready +for rendering, it invokes a callback function on libwpe. On the other side, +the WPE application has to register a view backend on the WPE WebView. This view +backend is provided by the rendering backend. The view backend receives the Web +page representation from libwpe, usually as an EGLImage, and is in charge of +presenting it in the application, on-screen.

+

The decoupling between generating the WebPage representation on WebKit side and +the actual rendering on the application side provides a very flexible design. +For instance, WPE integrators can easily develop a new rendering backend for +specific embedded platforms that might have a graphics driver with special API +requirements.

+

WPE provides a rendering backend aiming to target the most common platforms and +leverage the existing graphics stack available in the +Freedesktop umbrella eco-system. +WPEBackend-FDO is the reference +implementation of the base rendering backend design. WPEBackend-FDO provides an API +for WPE applications that aims to ease the handling of rendering either +on-screen using EGL, or off-screen using SHM.

+
+
+

Input events handling

+

In a traditional WebKit port, the provided widget usually also handles input +(keyboard, mouse, touch) events and is in charge of relaying them to the +internal WebKit input-methods components.

+

As WPE doesn’t provide a widget, it relies on libwpe APIs to relay input +events from the WPE application to the internal WebKit input-methods components. +This design again adds flexibility to the overall WPE architecture, enabling +applications to support new input devices without having to go through a UI +toolkit first.

+

In the example of the Cog WPE browser, the +application relies on Wayland protocols for user input to communicate events +coming from the Wayland compositor to WPE.

+
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/builds.html b/update-11ty/about/builds.html new file mode 100644 index 000000000..7382f644f --- /dev/null +++ b/update-11ty/about/builds.html @@ -0,0 +1,284 @@ + + + + + + + + + + + + WPE Builds + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE Builds

+ + +
+ +
+ +

While there are several simple ways for developers to experiment with and explore WPE, none are tuned for performance. Generally, shipping products for embedded systems are performance-tuned custom builds. To make this easier, there is also meta-webkit, which provides build recipes, WebKit based runtimes, and browsers for use with OpenEmbedded and/or Yocto.

+

We also have some performance tips that might be helpful.

+ + + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/explore-wpe.html b/update-11ty/about/explore-wpe.html new file mode 100644 index 000000000..533e699bd --- /dev/null +++ b/update-11ty/about/explore-wpe.html @@ -0,0 +1,14 @@ + + + + + + + + +

If you can see this, you should be redirecting this URL to /about/get-wpe.html.

+ + \ No newline at end of file diff --git a/update-11ty/about/faq.html b/update-11ty/about/faq.html new file mode 100644 index 000000000..8d9f1f00a --- /dev/null +++ b/update-11ty/about/faq.html @@ -0,0 +1,401 @@ + + + + + + + + + + + + WPE's Frequently Asked Questions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE's Frequently Asked Questions

+ + +
+ +
+ + +

WPE is an Open Source project developed upstream as part of the WebKit project, with all its code being pushed to WebKit’s upstream repository. Therefore, WPE is published under a mix of LGPLv2 and BSD licenses, which are the ones applying to the WebKit project as a whole. You can find a copy of such licenses in the webkit.org website.

+

There are also three related components developed alongside with WPEWebKit, which are externally maintained and have their own Open Source licenses:

+ +

What is the difference between WebPlatformForEmbedded’s WPE and WPEWebKit?

+

Historically, the WebPlatformForEmbedded flavor of WPE came first. It included several adaptations for the Reference Design Kit (RDK) platform, as well as different fixes for its supported devices. Quoting the RDK website:

+
+

RDK is a fully modular, portable, and customizable open source software +solution that standardizes core functions used in video, broadband and IoT +devices.

+
+

The RDK WPE page provides more +information about WPE in the RDK platform.

+

In 2017, engineers from Igalia submitted a new flavor of WPE, suitable for upstream hosting under the webkit.org umbrella. This version of WPE is released every 6 months from the code hosted on the upstream repository. This flavor of WPE is maintained upstream and provides regular security updates.

+

Is WPE supported on any specific hardware System-on-Chip?

+

WPE has been ported to a wide range of hardware platforms. The team aims to expand the list even further, so don’t hesitate to contact us if you can’t find your favorite SoC in the list.

+

Is WPE ported to non-Linux operating systems?

+

WPE currently only works on Linux-based operating systems. We are currently working on supporting Android, though. If you require WPE to run on any other OS, don’t hesitate to contact us.

+

What Web features does WPE support?

+

The WebKit project provides a features status page to which you can refer. However, that information should be taken with a grain of salt. The multi-platform nature of WebKit implies that ports have different build-time and runtime configurations.

+

The WPE project currently does not have an official list of the Web features it supports. It might provide one in the future, but for the time being, we recommend users check for specific features by testing WPE through the Cog browser.

+

What’s the status regarding WebRTC?

+

As of March 2022 the facts are:

+
    +
  1. WPEWebKit upstream has support for WebRTC, by relying on LibWebRTC.
  2. +
  3. LibWebRTC is bundled as third-party library in WebKit’s upstream repository.
  4. +
  5. The LibWebRTC backend supports hardware-acceleration only for decoding. Encoding is supported only via software encoders.
  6. +
  7. LibWebRTC bundles BoringSSL, which is a fork of OpenSSL started while OpenSSL +was still under the dual OpenSSL and SSLeay licences.
  8. +
+

Taking these facts into account, the WPEWebKit maintainers have decided to leave WebRTC support disabled in the default build configuration of the official release tarballs because:

+
    +
  • Bundling LibWebRTC in tarballs significantly increases the archive size.
  • +
  • The dependency on BoringSSL prevents LibWebRTC usage in GPL applications.
  • +
  • The lack of hardware-accelerated support in LibWebRTC would incur a bad +performance impact on the embedded platforms that WPE targets.
  • +
+

In order to solve these issues, an alternative WebRTC backend based on GstWebRTC will be enabled by default in the WPE upstream CMake build, hopefully soon; bug #235885 is being used to track progress. This new backend will seamlessly integrate with hardware-accelerated encoders and decoders on most embedded platforms. GstWebRTC depends on OpenSSL, which is released under an Apache-style license, so it doesn’t have limitations regarding redistribution in binary form.

+

What’s up with EME? How can I support this feature in my WPE-based product?

+

There is code in WebKit to support Encrypted Media Extensions (EME), but in any case, you will need a license agreement with DRM CDM providers to access it, since this part is not open source. There are three ways you can get this working:

+
    +
  • Obtain a license and use the Thunder OCDM plugin.
  • +
  • Write a Thunder-compatible API complement that will work with your DRM system.
  • +
  • Write a new CDM backend for WebKit using your DRM system.
  • +
+

What is (and isn’t) Cog?

+

From Cog’s README:

+
+

Cog is a small single “window” launcher for the WebKit WPE port. It is small, +provides no user interface, and is suitable to be used as a Web application +container. The “window” may be fullscreen depending on the WPE backend being +used.

+
+

Cog’s usage scenarios span from a MiniBrowser application to a full web-app container application meant to run HTML-based user interfaces on embedded platforms and products.

+

Although it can run on Linux-based desktop environments, Cog is not a full-blown Web Browser to be compared with Google Chrome or Safari. Cog’s primary environment is on embedded platforms, and it can run within a Wayland compositor such as Weston. Additionally, if the platform supports KMS/DRM, Cog can run as a full-screen standalone browser, this use-case is very common on kiosk products for instance.

+

If you are a developer aiming to enable WPE on a certain embedded platform, Cog combined with WPEBackend-FDO provides the most flexible solution for agile tinkering and to test WPE’s features.

+

Is Wayland required to run WPE?

+

As we say in Galicia, “it depends”.

+

WPE’s architecture was designed in order to +decouple rendering out of the Web engine and delegate this task to rendering +backends and to the application running the Web engine—it does not strictly +require usage of Wayland.

+

Typically when talking about Wayland we tend to conflate many things:

+
    +
  • +

    Wayland itself is an IPC +protocol which happens to be designed to move buffers containing pixel +data and input events from one process to another.

    +
  • +
  • +

    The Wayland package typically contains the reference implementation +of the protocol, libwayland. Other implementations are theoretically +possible.

    +
  • +
  • +

    By extension we may refer to a compositor, which is a program that +implements the server–side of the Wayland +protocol—possibly with the aid of libwayland.

    +
  • +
+

If you use WPEBackend-fdo, it internally uses the Wayland +protocol (via libwayland) to pass rendered frames from the WPEWebProcess +program to the application that embeds the web view—that we call “the UI +process”. As this is an implementation detail of the backend, the fact that +Wayland is used as IPC protocol does not need to be known by the application. +A compositor may be required or not depending on how the UI process displays +the web content.

+

For example, Cog can act as a Wayland client using its FDO platform plug-in, and in that case a +Wayland compositor is required. On the other hand, using Cog’s DRM platform plug-in it will display +rendered web content directly on screen (without a running Wayland +compositor). Note that in both cases WPEBackend-fdo is used as backend, +which means that the Wayland protocol is still in use.

+

Some WPE backends may not require Wayland at all. Such is the case +of WPEBackend-rdk in some configurations +(USE_BACKEND_BCM_RPI, USE_BACKEND_BCM_NEXUS, etc.)

+

Are open dialogs/popups menus supported?

+

The application embedding WPE is responsible for rendering popups and dialogs. The reference WPE Browser, Cog, has limited support for these features (as of 2021, it supports option menus).

+

What is the wayland-protocols build dependency about in Cog?

+

Depending on which platform rendering plugin is enabled at build time, the Cog browser might depend on the wayland-protocols project to generate source files needed in order to act as a Wayland client to the compositor (server) implementing those protocols.

+

So for instance, if you enable the FDO platform plugin and want to use it at runtime to have Cog running as a Wayland application, then the plugin will try to consume some Wayland protocols from the server, such as xdg-shell, fullscreen-shell-unstable-v1, presentation-time and linux-dmabuf-unstable-v1. Those protocols can’t be used without first generating source files derived from each protocol XML spec definition. This is all part of the Wayland design.

+

Why does the browser/launcher (e.g. Cog) crash at startup?

+

If you are building an embedded system image yourself, make sure there is at least one font installed that can be used as fallback by Fontconfig. You can use the fc-list program to print the list of known fonts.

+

Why does the browser/launcher (e.g. Cog) crash when trying to play audio?

+

If you are building an embedded system image yourself, make sure that the +GStreamer elements autoaudiosink and alsasink are installed. Even if your +system uses some other audio output by default (PulseAudio, PipeWire, etc.) +ALSA is always tried as the last fallback if all the other available sinks +fail.

+

Why does the browser/launcher (e.g. Cog) not load local files?

+

If you are building an embedded system image yourself, make sure to install +the shared MIME database is installed—in most distributions +this is part of a package named shared-mime-info. WebKit uses it to +determine which kind of data within file before loading it. Note that this is +not needed if you plan to loading remote resources because HTTP servers +provide the needed information in the Content-Type HTTP header.

+ + + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/get-started.html b/update-11ty/about/get-started.html new file mode 100644 index 000000000..533e699bd --- /dev/null +++ b/update-11ty/about/get-started.html @@ -0,0 +1,14 @@ + + + + + + + + +

If you can see this, you should be redirecting this URL to /about/get-wpe.html.

+ + \ No newline at end of file diff --git a/update-11ty/about/get-wpe.html b/update-11ty/about/get-wpe.html new file mode 100644 index 000000000..c8b7a9932 --- /dev/null +++ b/update-11ty/about/get-wpe.html @@ -0,0 +1,425 @@ + + + + + + + + + + + + Get WPE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Get WPE

+
+

WPE is an open source/free software project. That means that you can +get the source code directly and modify it to serve your needs. While +this can sometimes be an involved process, there are different ways to +get your hands on WPE, depending on what you need.

+ +

Before starting

+

Before getting the code, it’s a good idea to be familiar with what you +will need. The different components that are needed to run WPE are:

+ +

Install it from your Linux distribution

+

These packages are not just a quick and simple way to test WPE but +they also come with all the development files and documentation +necessary to build and test software that uses this web engine. Some +of the distributions that already have built packages for WPE are:

+ +

This list is not exhaustive, so if you use a different distributions, +there might be packages for it already. Refer to the official +documentation of your distribution for information on how to install packages.

+

Build an image for supported reference hardware

+

A simple way to cross-compile WPE and its dependencies for a target +architecture is to use an existing build framework. We provide recipes +for a OpenEmbedded/Yocto layer for WPE. +There are specific instructions in the +project wiki.

+

Download the official tarball releases

+

We periodically package WPE and its associated libraries, following a +predictable release schedule, to make sure that +both stable and development versions are available to users, +deployers, and developers. Following the release schedule is the best +way to follow the progress of WPE, get a sense of what’s coming, and +properly prepare for updates in your production deployments.

+

Unstable releases are development versions, that give a preview of +what’s coming in the next stable release. These are useful for +beta-testing, and to prepare to upgrade your deployment to an upcoming +stable release when this is out. Unstable releases should never be +used in production.

+

Below is a summary of the latest stable and unstable releases for WPE +and its components:

+

Releases

+
+
+
+

Stable

+
+ wpewebkit: + + Download v2.46.4 + - + Release notes for v2.46.4 + +
+ wpebackend-fdo: + + Download v1.14.3 + - + Release notes for v1.14.3 + +
+
+
+

Unstable

+
+ wpewebkit: + + Download v2.47.1 + - + Release notes for v2.47.1 + +
+ wpebackend-fdo: + + Download v1.15.90 + - + Release notes for v1.15.90 + +
+
+

+ + See all released tarballs… +

+
+
+

Get the source code directly from git

+

This is the most involved way to get the source code, and it’s only +recommended for developers who are interested in getting involved in +the development of the project, or who need to implement any feature +that is not yet available in WPE. Additionally, this can be also be +a good way to track down any bug you might find and to fix it.

+ +

Instead of downloading each of these components on their own, the recommended +procedure is cloning Git repositories for WebKit and the WebKit Container +SDK. Follow the SDK +instructions to prepare the container and open a shell inside it; then +running Tools/Scripts/build-webkit --wpe in the WebKit source directory +will produce a developer build—all the other development +tools can +be run directly in the container as well.

+

If you find any problem or want to know more about optimizing WPE for your hardware or use cases, please contact us.

+
+
+

Resources

+ +
+
+ +
+
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/index.html b/update-11ty/about/index.html new file mode 100644 index 000000000..899df22c6 --- /dev/null +++ b/update-11ty/about/index.html @@ -0,0 +1,494 @@ + + + + + + + + + + + + Learn & Discover + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

Learn & Discover

+

You might be wondering how a web browser may be used in an embedded device, or +why WPE could be a good choice for your product. If you are an engineer, you +might want to know if your target architecture is supported. Read below to +answer these and more questions.

+
+
+ + +
+
+

Supported Hardware

+

WPE runs on a wide range of hardware, including devices made by Broadcom, Nvidia, NXP, Qualcomm, or Rockchip.

+ +
+
+

Why Choose WPE?

+

WPE WebKit is widely adopted by many industries, including digital signage, +professional audio, home appliances, set-top-boxes, automotive, and inflight +infotainment. Countless devices deployed around the globe are already using +WPE WebKit as their web runtime platform, and use is growing rapidly. Read +more about why you should choose WPE.

+ +
+
+

WPE In Action

+
+
+ +
+ Video: WPE WebGL performance demos +
WPE WebGL performance demos
+
+
+
+
+ +
+ Video: WPE SVG Transformations and Hardware Acceleration +
WPE SVG Transformations and Hardware Acceleration
+
+
+
+
+ +
+ Video: WPE 2d canvas and video performance on low end-hardware +
WPE 2d canvas and video performance on low end-hardware
+
+
+
+
+ +
+ Video: Web-augmented video overlays with WPEWebKit and GStreamer +
Web-augmented video overlays with WPEWebKit and GStreamer
+
+
+
+
+ +
+ Video: WPE CSS Transforms and Performance +
WPE CSS Transforms and Performance
+
+
+
+
+
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/supported-hardware.html b/update-11ty/about/supported-hardware.html new file mode 100644 index 000000000..e6ae5a0b0 --- /dev/null +++ b/update-11ty/about/supported-hardware.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + Supported Hardware + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+

Supported Hardware

+

WPE is currently running on a wide range of hardware. This page lists configurations which are known to work, sorted by manufacturer.

+
+
+

Note that this list is not exhaustive. Reports of unlisted configurations are welcome.

+

NXP

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SeriesGPUDriverWPE BackendCog Platforms
i.MX 51Imageon Z460freedreno (reverse-engineered)fdowl, drm
i.MX 53Imageon Z460freedreno (reverse-engineered)fdowl, drm
i.MX 6Vivante GC880Vivante (Proprietary)fdowl, drm
i.MX 6Vivante GC2000etnaviv (reverse-engineered)fdowl, drm
i.MX 6Vivante GC2000Vivante (Proprietary)fdowl
i.MX 6Vivante GC2000Vivante (Proprietary)rdk, VIV_IMX6_EGLn/a
i.MX 8MVivante GC7000etnaviv (reverse-engineered)fdowl, drm
i.MX 8MVivante GC7000Vivante (Proprietary)fdowl
+

Broadcom

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DeviceGPUDriverWPE BackendCog Platforms
Arris VIP5202WVideoCore IVProprietaryrdk, BCM_NEXUS or USE_BACKEND_BCM_NEXUS_WAYLANDn/a
Raspberry Pi 3VideoCore IVProprietaryrdk, BCM_RPIn/a
Raspberry Pi 3VideoCore IVMesa vc4fdowl, drm, headless
Raspberry Pi 4VideoCore VMesa v3dfdowl
+

Qualcomm

+ + + + + + + + + + + + + + + + + + + +
DeviceGPUDriverWPE BackendCog Platforms
APQ8017Adreno 306ProprietaryCustomn/a
+

Nvidia

+ + + + + + + + + + + + + + + + + + + +
DeviceGPUDriverWPE BackendCog Platforms
Jetson TK1Tegra K1
+

RockChip

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
DeviceGPUDriverWPE BackendCog Platforms
RK3399Mali T860MP4panfrost (reverse-engineered)fdowl
RK3399Mali T860MP4Mali (Proprietary)
+

PC-style Hardware

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DeviceGPUDriverWPE BackendCog Platforms
AnyAMDMesa amdgpufdowl, x11, gtk4, headless
AnyIntelMesa i965fdowl, x11, gtk4, headless, drm
AnyIntelMesa irisfdowl, x11, gtk4, headless, drm
+

Other

+ + + + + + + + + + + + + + + + + + + +
DeviceGPUDriverWPE BackendCog Platforms
BeaglebonePowerVR SGX530Proprietary
+
+
+
+

Resources

+ +
+
+ +
+
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/about/what-is-embedded.html b/update-11ty/about/what-is-embedded.html new file mode 100644 index 000000000..944d32d61 --- /dev/null +++ b/update-11ty/about/what-is-embedded.html @@ -0,0 +1,270 @@ + + + + + + + + + + + + What is an Embedded Browser? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

What is an Embedded Browser?

+

Long ago, it might have been reasonable to imagine browsers only in the realm of desktop computers. However, embedded browsers are in everything these days: cars, signs, tablets, gaming systems, televisions and appliances. They’re even in space!

+
+

The Web Platform is a frequently chosen foundational technology for many reasons, including:

+ +

The fact that over the past few years, billions of new devices have come online, and the implications of this are still under-discussed. We believe developers need to be able explore and understand this space, and that lessons learned here are potentially helpful to the larger web ecosystem as well. However, this can be challenging, because embedded browsers are a little different…

+

Why embedded browsers are different

+

For many of us, a browser is an application like the one you’re probably using now. You click an icon on your graphical operating system (OS), navigate somewhere with a URL bar, search, and so on. You have bookmarks and tabs that you can drag around, and lots of other features.

+

In contrast, an embedded browser is contained within another application or is built for a specific purpose and runs in an embedded system, and the application controlling the embedded browser does not provide all the typical features of browsers running in desktops.

+

Furthermore, for most embedded systems there isn’t a common desktop OS for the browser to run on — in fact, the OS is built specifically for that type of device, and the embedded browser is built along with it. Part of the reason for this is that, by and large, they are not general-purpose computing devices. They have slightly different security concerns, and they also run on often radically different sorts of hardware than desktop, laptop or many mobile devices.

+

So, what really is an embedded browser?

+

Browsers, engines and ports

+

A “proper” browser is, in all likelihood, the application in which you are reading this. This browser is built on one of three open source projects: WebKit, Chromium or Gecko. Each of these projects defines an engine — that is, they provide an architecture through which something else (e.g., a browser) can bind to the top layer of the engine in order to drive us from URL to URL, and use the engine’s bottom layer to do things like actually put content on the screen, render graphics, receive input from devices like keyboards, mice and pointing devices, and connect to things like the text-to-speech subsystem.

+

In terms of WebKit-based browsers, a “port” is the set of extra layers that provides facilities at those top and bottom layers of the engine. The WebKit project has a few “official” ports that are available from webkit.org/downloads/. Some, like Safari, are fully-featured desktop browsers. WebKitGTK is a port for the GTK GUI toolkit, used among others by the desktop browser Epiphany.

+

WPE (“Web Platform for Embedded”) is the official port for embedded systems, and it adds another layer to the architecture allowing for more easily swappable backends (the graphics layers, windowing and so on) and simpler bindings for programmatic top-level control in Linux based systems. Cog is another project that makes it easier to launch and drive WPE views.

+

In other words, WPE is designed to be built and optimized for your embedded device in order to deliver the best performance. What all of this means is that there isn’t really a single “WPE” that’s as easy to provide as many browsers that you’re probably familiar with.

+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/assets/SourceSans3VF-Italic.otf b/update-11ty/assets/SourceSans3VF-Italic.otf new file mode 100644 index 000000000..19ffdee31 Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Italic.otf differ diff --git a/update-11ty/assets/SourceSans3VF-Italic.ttf b/update-11ty/assets/SourceSans3VF-Italic.ttf new file mode 100644 index 000000000..814f834af Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Italic.ttf differ diff --git a/update-11ty/assets/SourceSans3VF-Italic.ttf.woff b/update-11ty/assets/SourceSans3VF-Italic.ttf.woff new file mode 100644 index 000000000..1934bb082 Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Italic.ttf.woff differ diff --git a/update-11ty/assets/SourceSans3VF-Italic.ttf.woff2 b/update-11ty/assets/SourceSans3VF-Italic.ttf.woff2 new file mode 100644 index 000000000..753755416 Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Italic.ttf.woff2 differ diff --git a/update-11ty/assets/SourceSans3VF-Roman.otf b/update-11ty/assets/SourceSans3VF-Roman.otf new file mode 100644 index 000000000..baae69659 Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Roman.otf differ diff --git a/update-11ty/assets/SourceSans3VF-Roman.ttf b/update-11ty/assets/SourceSans3VF-Roman.ttf new file mode 100644 index 000000000..093d01d02 Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Roman.ttf differ diff --git a/update-11ty/assets/SourceSans3VF-Roman.ttf.woff b/update-11ty/assets/SourceSans3VF-Roman.ttf.woff new file mode 100644 index 000000000..5e1f981c0 Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Roman.ttf.woff differ diff --git a/update-11ty/assets/SourceSans3VF-Roman.ttf.woff2 b/update-11ty/assets/SourceSans3VF-Roman.ttf.woff2 new file mode 100644 index 000000000..48d5ff89e Binary files /dev/null and b/update-11ty/assets/SourceSans3VF-Roman.ttf.woff2 differ diff --git a/update-11ty/assets/author-aperez@1x.png b/update-11ty/assets/author-aperez@1x.png new file mode 100644 index 000000000..0318523c2 Binary files /dev/null and b/update-11ty/assets/author-aperez@1x.png differ diff --git a/update-11ty/assets/author-aperez@2x.png b/update-11ty/assets/author-aperez@2x.png new file mode 100644 index 000000000..19fe6034f Binary files /dev/null and b/update-11ty/assets/author-aperez@2x.png differ diff --git a/update-11ty/assets/author-llepage@1x.png b/update-11ty/assets/author-llepage@1x.png new file mode 100644 index 000000000..d1ec5987b Binary files /dev/null and b/update-11ty/assets/author-llepage@1x.png differ diff --git a/update-11ty/assets/author-llepage@2x.png b/update-11ty/assets/author-llepage@2x.png new file mode 100644 index 000000000..001fd1171 Binary files /dev/null and b/update-11ty/assets/author-llepage@2x.png differ diff --git a/update-11ty/assets/build-webkit-org-screenshot.png b/update-11ty/assets/build-webkit-org-screenshot.png new file mode 100644 index 000000000..9750e75a8 Binary files /dev/null and b/update-11ty/assets/build-webkit-org-screenshot.png differ diff --git a/update-11ty/assets/bw_Twitter_Profile_400px.png b/update-11ty/assets/bw_Twitter_Profile_400px.png new file mode 100644 index 000000000..8b2d58bbd Binary files /dev/null and b/update-11ty/assets/bw_Twitter_Profile_400px.png differ diff --git a/update-11ty/assets/bw_Twitter_Profile_WhiteBg_400px.png b/update-11ty/assets/bw_Twitter_Profile_WhiteBg_400px.png new file mode 100644 index 000000000..53a921d0f Binary files /dev/null and b/update-11ty/assets/bw_Twitter_Profile_WhiteBg_400px.png differ diff --git a/update-11ty/assets/bw_Web_Logo_Header_300x110px.png b/update-11ty/assets/bw_Web_Logo_Header_300x110px.png new file mode 100644 index 000000000..d9ced3938 Binary files /dev/null and b/update-11ty/assets/bw_Web_Logo_Header_300x110px.png differ diff --git a/update-11ty/assets/graphics-attachment.png b/update-11ty/assets/graphics-attachment.png new file mode 100644 index 000000000..a6fc12f7a Binary files /dev/null and b/update-11ty/assets/graphics-attachment.png differ diff --git a/update-11ty/assets/graphics-graphicslayertree.png b/update-11ty/assets/graphics-graphicslayertree.png new file mode 100644 index 000000000..0823dd0ef Binary files /dev/null and b/update-11ty/assets/graphics-graphicslayertree.png differ diff --git a/update-11ty/assets/graphics-renderlayertree.png b/update-11ty/assets/graphics-renderlayertree.png new file mode 100644 index 000000000..e8f6a9ab0 Binary files /dev/null and b/update-11ty/assets/graphics-renderlayertree.png differ diff --git a/update-11ty/assets/graphics-rendertree.png b/update-11ty/assets/graphics-rendertree.png new file mode 100644 index 000000000..a4c683fc9 Binary files /dev/null and b/update-11ty/assets/graphics-rendertree.png differ diff --git a/update-11ty/assets/gtk-cog-screenshot.png b/update-11ty/assets/gtk-cog-screenshot.png new file mode 100644 index 000000000..49be83528 Binary files /dev/null and b/update-11ty/assets/gtk-cog-screenshot.png differ diff --git a/update-11ty/assets/img/Igalia_tagline-white-1.png b/update-11ty/assets/img/Igalia_tagline-white-1.png new file mode 100644 index 000000000..dc6a63e02 Binary files /dev/null and b/update-11ty/assets/img/Igalia_tagline-white-1.png differ diff --git a/update-11ty/assets/img/Metrological-hero-orig.jpg b/update-11ty/assets/img/Metrological-hero-orig.jpg new file mode 100644 index 000000000..eed532c39 Binary files /dev/null and b/update-11ty/assets/img/Metrological-hero-orig.jpg differ diff --git a/update-11ty/assets/img/Metrological-hero.jpg b/update-11ty/assets/img/Metrological-hero.jpg new file mode 100644 index 000000000..2649107f3 Binary files /dev/null and b/update-11ty/assets/img/Metrological-hero.jpg differ diff --git a/update-11ty/assets/img/WPE-design.svg b/update-11ty/assets/img/WPE-design.svg new file mode 100755 index 000000000..6fffc615e --- /dev/null +++ b/update-11ty/assets/img/WPE-design.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hardware + Operating System + Cog + Application + Backend + WebKit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/WhyChooseWPE-ExploreLand.png b/update-11ty/assets/img/WhyChooseWPE-ExploreLand.png new file mode 100644 index 000000000..d7ca5e659 Binary files /dev/null and b/update-11ty/assets/img/WhyChooseWPE-ExploreLand.png differ diff --git a/update-11ty/assets/img/WhyChooseWPE-homepage.png b/update-11ty/assets/img/WhyChooseWPE-homepage.png new file mode 100644 index 000000000..bfaf565c5 Binary files /dev/null and b/update-11ty/assets/img/WhyChooseWPE-homepage.png differ diff --git a/update-11ty/assets/img/WhyChooseWPE.png b/update-11ty/assets/img/WhyChooseWPE.png new file mode 100644 index 000000000..8532eda72 Binary files /dev/null and b/update-11ty/assets/img/WhyChooseWPE.png differ diff --git a/update-11ty/assets/img/av-production.jpg b/update-11ty/assets/img/av-production.jpg new file mode 100644 index 000000000..859c15606 Binary files /dev/null and b/update-11ty/assets/img/av-production.jpg differ diff --git a/update-11ty/assets/img/blog-backends-thumb@1x.png b/update-11ty/assets/img/blog-backends-thumb@1x.png new file mode 100644 index 000000000..f6d6f23fd Binary files /dev/null and b/update-11ty/assets/img/blog-backends-thumb@1x.png differ diff --git a/update-11ty/assets/img/blog-backends-thumb@2x.png b/update-11ty/assets/img/blog-backends-thumb@2x.png new file mode 100644 index 000000000..b2ba0807f Binary files /dev/null and b/update-11ty/assets/img/blog-backends-thumb@2x.png differ diff --git a/update-11ty/assets/img/border-dot-black.svg b/update-11ty/assets/img/border-dot-black.svg new file mode 100755 index 000000000..11750c638 --- /dev/null +++ b/update-11ty/assets/img/border-dot-black.svg @@ -0,0 +1,4 @@ + + + + diff --git a/update-11ty/assets/img/border-dot-white.svg b/update-11ty/assets/img/border-dot-white.svg new file mode 100755 index 000000000..d0318fa5d --- /dev/null +++ b/update-11ty/assets/img/border-dot-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/update-11ty/assets/img/checkmark.png b/update-11ty/assets/img/checkmark.png new file mode 100644 index 000000000..d593d3fe0 Binary files /dev/null and b/update-11ty/assets/img/checkmark.png differ diff --git a/update-11ty/assets/img/dash-h.svg b/update-11ty/assets/img/dash-h.svg new file mode 100644 index 000000000..690121e88 --- /dev/null +++ b/update-11ty/assets/img/dash-h.svg @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/dash-v.svg b/update-11ty/assets/img/dash-v.svg new file mode 100644 index 000000000..c1a0df340 --- /dev/null +++ b/update-11ty/assets/img/dash-v.svg @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/diagram-WPE-design.svg b/update-11ty/assets/img/diagram-WPE-design.svg new file mode 100644 index 000000000..5b4bde790 --- /dev/null +++ b/update-11ty/assets/img/diagram-WPE-design.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/dotted-continents.svg b/update-11ty/assets/img/dotted-continents.svg new file mode 100644 index 000000000..5fef31ce8 --- /dev/null +++ b/update-11ty/assets/img/dotted-continents.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/update-11ty/assets/img/extending-minicog-echouri.png b/update-11ty/assets/img/extending-minicog-echouri.png new file mode 100644 index 000000000..072691d78 Binary files /dev/null and b/update-11ty/assets/img/extending-minicog-echouri.png differ diff --git a/update-11ty/assets/img/graphic-title-blue.svg b/update-11ty/assets/img/graphic-title-blue.svg new file mode 100755 index 000000000..a5a83c8b8 --- /dev/null +++ b/update-11ty/assets/img/graphic-title-blue.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/update-11ty/assets/img/graphic-title-white.svg b/update-11ty/assets/img/graphic-title-white.svg new file mode 100755 index 000000000..48006b66c --- /dev/null +++ b/update-11ty/assets/img/graphic-title-white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/update-11ty/assets/img/igalia-larger.png b/update-11ty/assets/img/igalia-larger.png new file mode 100644 index 000000000..de7d77cba Binary files /dev/null and b/update-11ty/assets/img/igalia-larger.png differ diff --git a/update-11ty/assets/img/igalia-logo.png b/update-11ty/assets/img/igalia-logo.png new file mode 100644 index 000000000..23ef44b1a Binary files /dev/null and b/update-11ty/assets/img/igalia-logo.png differ diff --git a/update-11ty/assets/img/igalia.png b/update-11ty/assets/img/igalia.png new file mode 100644 index 000000000..c2aa4cc79 Binary files /dev/null and b/update-11ty/assets/img/igalia.png differ diff --git a/update-11ty/assets/img/illustration-community.svg b/update-11ty/assets/img/illustration-community.svg new file mode 100755 index 000000000..dee25750b --- /dev/null +++ b/update-11ty/assets/img/illustration-community.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/illustration-resources.svg b/update-11ty/assets/img/illustration-resources.svg new file mode 100755 index 000000000..ca162dbd1 --- /dev/null +++ b/update-11ty/assets/img/illustration-resources.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/infotainment.jpg b/update-11ty/assets/img/infotainment.jpg new file mode 100644 index 000000000..a30a98361 Binary files /dev/null and b/update-11ty/assets/img/infotainment.jpg differ diff --git a/update-11ty/assets/img/link_ext_icon.svg b/update-11ty/assets/img/link_ext_icon.svg new file mode 100644 index 000000000..32b63637d --- /dev/null +++ b/update-11ty/assets/img/link_ext_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/update-11ty/assets/img/list-arrow.svg b/update-11ty/assets/img/list-arrow.svg new file mode 100644 index 000000000..a31b9c7f8 --- /dev/null +++ b/update-11ty/assets/img/list-arrow.svg @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/logo-NOS.png b/update-11ty/assets/img/logo-NOS.png new file mode 100755 index 000000000..2a7ecea4e Binary files /dev/null and b/update-11ty/assets/img/logo-NOS.png differ diff --git a/update-11ty/assets/img/logo-NOS@2x.png b/update-11ty/assets/img/logo-NOS@2x.png new file mode 100755 index 000000000..9c4a40cdf Binary files /dev/null and b/update-11ty/assets/img/logo-NOS@2x.png differ diff --git a/update-11ty/assets/img/logo-black.svg b/update-11ty/assets/img/logo-black.svg new file mode 100644 index 000000000..f94bb095d --- /dev/null +++ b/update-11ty/assets/img/logo-black.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/logo-blue.svg b/update-11ty/assets/img/logo-blue.svg new file mode 100644 index 000000000..84e47beb2 --- /dev/null +++ b/update-11ty/assets/img/logo-blue.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/logo-digital-signage.png b/update-11ty/assets/img/logo-digital-signage.png new file mode 100644 index 000000000..7aa44568d Binary files /dev/null and b/update-11ty/assets/img/logo-digital-signage.png differ diff --git a/update-11ty/assets/img/logo-digital-signage@2x.png b/update-11ty/assets/img/logo-digital-signage@2x.png new file mode 100644 index 000000000..887c294d8 Binary files /dev/null and b/update-11ty/assets/img/logo-digital-signage@2x.png differ diff --git a/update-11ty/assets/img/logo-garmin.png b/update-11ty/assets/img/logo-garmin.png new file mode 100755 index 000000000..597b5f12f Binary files /dev/null and b/update-11ty/assets/img/logo-garmin.png differ diff --git a/update-11ty/assets/img/logo-garmin@2x.png b/update-11ty/assets/img/logo-garmin@2x.png new file mode 100755 index 000000000..8c0c817be Binary files /dev/null and b/update-11ty/assets/img/logo-garmin@2x.png differ diff --git a/update-11ty/assets/img/logo-liberty.png b/update-11ty/assets/img/logo-liberty.png new file mode 100755 index 000000000..2103a5248 Binary files /dev/null and b/update-11ty/assets/img/logo-liberty.png differ diff --git a/update-11ty/assets/img/logo-liberty@2x.png b/update-11ty/assets/img/logo-liberty@2x.png new file mode 100755 index 000000000..cc047c920 Binary files /dev/null and b/update-11ty/assets/img/logo-liberty@2x.png differ diff --git a/update-11ty/assets/img/logo-metrological.png b/update-11ty/assets/img/logo-metrological.png new file mode 100755 index 000000000..1a22433f2 Binary files /dev/null and b/update-11ty/assets/img/logo-metrological.png differ diff --git a/update-11ty/assets/img/logo-metrological@2x.png b/update-11ty/assets/img/logo-metrological@2x.png new file mode 100755 index 000000000..eae62ac29 Binary files /dev/null and b/update-11ty/assets/img/logo-metrological@2x.png differ diff --git a/update-11ty/assets/img/logo-multichoice.png b/update-11ty/assets/img/logo-multichoice.png new file mode 100755 index 000000000..fc5a5b538 Binary files /dev/null and b/update-11ty/assets/img/logo-multichoice.png differ diff --git a/update-11ty/assets/img/logo-multichoice@2x.png b/update-11ty/assets/img/logo-multichoice@2x.png new file mode 100755 index 000000000..1421e9d1f Binary files /dev/null and b/update-11ty/assets/img/logo-multichoice@2x.png differ diff --git a/update-11ty/assets/img/logo-nvidia.png b/update-11ty/assets/img/logo-nvidia.png new file mode 100755 index 000000000..b70b901d0 Binary files /dev/null and b/update-11ty/assets/img/logo-nvidia.png differ diff --git a/update-11ty/assets/img/logo-nvidia@2x.png b/update-11ty/assets/img/logo-nvidia@2x.png new file mode 100755 index 000000000..ba1226187 Binary files /dev/null and b/update-11ty/assets/img/logo-nvidia@2x.png differ diff --git a/update-11ty/assets/img/logo-nxp.png b/update-11ty/assets/img/logo-nxp.png new file mode 100644 index 000000000..51171af90 Binary files /dev/null and b/update-11ty/assets/img/logo-nxp.png differ diff --git a/update-11ty/assets/img/logo-nxp@2x.png b/update-11ty/assets/img/logo-nxp@2x.png new file mode 100755 index 000000000..8470d229b Binary files /dev/null and b/update-11ty/assets/img/logo-nxp@2x.png differ diff --git a/update-11ty/assets/img/logo-ombori.png b/update-11ty/assets/img/logo-ombori.png new file mode 100755 index 000000000..774bf4bf3 Binary files /dev/null and b/update-11ty/assets/img/logo-ombori.png differ diff --git a/update-11ty/assets/img/logo-ombori@2x.png b/update-11ty/assets/img/logo-ombori@2x.png new file mode 100755 index 000000000..133d7730b Binary files /dev/null and b/update-11ty/assets/img/logo-ombori@2x.png differ diff --git a/update-11ty/assets/img/logo-qualcomm.png b/update-11ty/assets/img/logo-qualcomm.png new file mode 100755 index 000000000..02e1d5b60 Binary files /dev/null and b/update-11ty/assets/img/logo-qualcomm.png differ diff --git a/update-11ty/assets/img/logo-qualcomm@2x.png b/update-11ty/assets/img/logo-qualcomm@2x.png new file mode 100755 index 000000000..b74fa5592 Binary files /dev/null and b/update-11ty/assets/img/logo-qualcomm@2x.png differ diff --git a/update-11ty/assets/img/logo-rockchip.png b/update-11ty/assets/img/logo-rockchip.png new file mode 100755 index 000000000..f0040c19b Binary files /dev/null and b/update-11ty/assets/img/logo-rockchip.png differ diff --git a/update-11ty/assets/img/logo-rockchip@2x.png b/update-11ty/assets/img/logo-rockchip@2x.png new file mode 100755 index 000000000..c6e59367b Binary files /dev/null and b/update-11ty/assets/img/logo-rockchip@2x.png differ diff --git a/update-11ty/assets/img/logo-server-side-rendering.png b/update-11ty/assets/img/logo-server-side-rendering.png new file mode 100644 index 000000000..961621b28 Binary files /dev/null and b/update-11ty/assets/img/logo-server-side-rendering.png differ diff --git a/update-11ty/assets/img/logo-server-side-rendering@2x.png b/update-11ty/assets/img/logo-server-side-rendering@2x.png new file mode 100644 index 000000000..1c22b326c Binary files /dev/null and b/update-11ty/assets/img/logo-server-side-rendering@2x.png differ diff --git a/update-11ty/assets/img/logo-telefonica.png b/update-11ty/assets/img/logo-telefonica.png new file mode 100755 index 000000000..946e0f087 Binary files /dev/null and b/update-11ty/assets/img/logo-telefonica.png differ diff --git a/update-11ty/assets/img/logo-telefonica@2x.png b/update-11ty/assets/img/logo-telefonica@2x.png new file mode 100755 index 000000000..7109a60e9 Binary files /dev/null and b/update-11ty/assets/img/logo-telefonica@2x.png differ diff --git a/update-11ty/assets/img/logo-televic.png b/update-11ty/assets/img/logo-televic.png new file mode 100755 index 000000000..570c67a9f Binary files /dev/null and b/update-11ty/assets/img/logo-televic.png differ diff --git a/update-11ty/assets/img/logo-televic@2x.png b/update-11ty/assets/img/logo-televic@2x.png new file mode 100755 index 000000000..ed7257a0a Binary files /dev/null and b/update-11ty/assets/img/logo-televic@2x.png differ diff --git a/update-11ty/assets/img/logo-white.svg b/update-11ty/assets/img/logo-white.svg new file mode 100644 index 000000000..116d23915 --- /dev/null +++ b/update-11ty/assets/img/logo-white.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/update-11ty/assets/img/media-pause.svg b/update-11ty/assets/img/media-pause.svg new file mode 100644 index 000000000..c91909489 --- /dev/null +++ b/update-11ty/assets/img/media-pause.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/media-play.svg b/update-11ty/assets/img/media-play.svg new file mode 100644 index 000000000..02c9ac9c9 --- /dev/null +++ b/update-11ty/assets/img/media-play.svg @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/menu-x.svg b/update-11ty/assets/img/menu-x.svg new file mode 100644 index 000000000..a3ceabb70 --- /dev/null +++ b/update-11ty/assets/img/menu-x.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/menu.svg b/update-11ty/assets/img/menu.svg new file mode 100644 index 000000000..f4b9cb69d --- /dev/null +++ b/update-11ty/assets/img/menu.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/pepe-silvia-all-javascript.jpg b/update-11ty/assets/img/pepe-silvia-all-javascript.jpg new file mode 100644 index 000000000..4f58c7f63 Binary files /dev/null and b/update-11ty/assets/img/pepe-silvia-all-javascript.jpg differ diff --git a/update-11ty/assets/img/placeholder.png b/update-11ty/assets/img/placeholder.png new file mode 100644 index 000000000..60b803408 Binary files /dev/null and b/update-11ty/assets/img/placeholder.png differ diff --git a/update-11ty/assets/img/pointofsale.jpg b/update-11ty/assets/img/pointofsale.jpg new file mode 100644 index 000000000..356f90200 Binary files /dev/null and b/update-11ty/assets/img/pointofsale.jpg differ diff --git a/update-11ty/assets/img/smart-appliance.jpg b/update-11ty/assets/img/smart-appliance.jpg new file mode 100644 index 000000000..2abf38397 Binary files /dev/null and b/update-11ty/assets/img/smart-appliance.jpg differ diff --git a/update-11ty/assets/img/survey.svg b/update-11ty/assets/img/survey.svg new file mode 100644 index 000000000..03f43c667 --- /dev/null +++ b/update-11ty/assets/img/survey.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/update-11ty/assets/img/tv.jpg b/update-11ty/assets/img/tv.jpg new file mode 100644 index 000000000..698786fd7 Binary files /dev/null and b/update-11ty/assets/img/tv.jpg differ diff --git a/update-11ty/assets/img/use_case-digital_signage.png b/update-11ty/assets/img/use_case-digital_signage.png new file mode 100644 index 000000000..e4228c852 Binary files /dev/null and b/update-11ty/assets/img/use_case-digital_signage.png differ diff --git a/update-11ty/assets/lbse-logo-wide.png b/update-11ty/assets/lbse-logo-wide.png new file mode 100644 index 000000000..b4978347a Binary files /dev/null and b/update-11ty/assets/lbse-logo-wide.png differ diff --git a/update-11ty/assets/networking-flow.svg b/update-11ty/assets/networking-flow.svg new file mode 100644 index 000000000..dd6d32697 --- /dev/null +++ b/update-11ty/assets/networking-flow.svg @@ -0,0 +1,3 @@ + + +
ResourceLoader
ResourceLoader
WebCore
WebCore
NetworkDataTask
NetworkDataTask
NetworkProcess
NetworkProcess
ResourceResponse
ResourceResponse
ResourceRequest
ResourceRequest
Text is not SVG - cannot display
\ No newline at end of file diff --git a/update-11ty/assets/networking-layers.svg b/update-11ty/assets/networking-layers.svg new file mode 100644 index 000000000..62ddf4f48 --- /dev/null +++ b/update-11ty/assets/networking-layers.svg @@ -0,0 +1,3 @@ + + +
glib
glib
WebKit
WebKit
libsoup
libsoup
glib-networking
glib-networking
Text is not SVG - cannot display
\ No newline at end of file diff --git a/update-11ty/assets/svg/URI_syntax_diagram.svg b/update-11ty/assets/svg/URI_syntax_diagram.svg new file mode 100644 index 000000000..db042d9dd --- /dev/null +++ b/update-11ty/assets/svg/URI_syntax_diagram.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + scheme + + + + + : + + + + + // + + + + + userinfo + + + + + @ + + + + + host + + + + + : + + + + + port + + + + + path + + + + + ? + + + + + query + + + + + # + + + + + fragment + + diff --git a/update-11ty/assets/svg/black_Web_Logo.svg b/update-11ty/assets/svg/black_Web_Logo.svg new file mode 100644 index 000000000..1cc18a20e --- /dev/null +++ b/update-11ty/assets/svg/black_Web_Logo.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/blue_Web_Logo.png b/update-11ty/assets/svg/blue_Web_Logo.png new file mode 100644 index 000000000..c7178565c Binary files /dev/null and b/update-11ty/assets/svg/blue_Web_Logo.png differ diff --git a/update-11ty/assets/svg/blue_Web_Logo.svg b/update-11ty/assets/svg/blue_Web_Logo.svg new file mode 100644 index 000000000..d3f56399b --- /dev/null +++ b/update-11ty/assets/svg/blue_Web_Logo.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/igalia-tagline.svg b/update-11ty/assets/svg/igalia-tagline.svg new file mode 100644 index 000000000..eb6d74433 --- /dev/null +++ b/update-11ty/assets/svg/igalia-tagline.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/lbse-logo-wide.svg b/update-11ty/assets/svg/lbse-logo-wide.svg new file mode 100644 index 000000000..11d3ea721 --- /dev/null +++ b/update-11ty/assets/svg/lbse-logo-wide.svg @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LBSE + + + + + + + + + + + + + + + New SVG enginefor WebKit + diff --git a/update-11ty/assets/svg/lbse-logo.svg b/update-11ty/assets/svg/lbse-logo.svg new file mode 100644 index 000000000..059b21a8e --- /dev/null +++ b/update-11ty/assets/svg/lbse-logo.svg @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + New SVG enginefor WebKit + + + + + + + + + + L + B + S + E + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/part1-basics.md-1.svg b/update-11ty/assets/svg/part1-basics.md-1.svg new file mode 100644 index 000000000..0ebe5b999 --- /dev/null +++ b/update-11ty/assets/svg/part1-basics.md-1.svg @@ -0,0 +1 @@ +
Application Process
WPEWebProcess
draw
User Application
WPE ThreadedCompositor
WPE Backend
\ No newline at end of file diff --git a/update-11ty/assets/svg/part1-basics.md-2.svg b/update-11ty/assets/svg/part1-basics.md-2.svg new file mode 100644 index 000000000..66af63288 --- /dev/null +++ b/update-11ty/assets/svg/part1-basics.md-2.svg @@ -0,0 +1 @@ +
Application Process
WPEWebProcess
WPEWebProcess
Clients
IPC
IPC
IPC
IPC
IPC
IPC
IPC
rendererHost
viewBackend
viewBackend
viewBackend
viewBackend
viewBackend
rendererHostClient
rendererHostClient
rendererBackendEGL
rendererBackendEGLTarget
rendererBackendEGLTarget
rendererBackendEGL
rendererBackendEGLTarget
rendererBackendEGLTarget
rendererBackendEGLTarget
\ No newline at end of file diff --git a/update-11ty/assets/svg/part1-basics.md-3.svg b/update-11ty/assets/svg/part1-basics.md-3.svg new file mode 100644 index 000000000..dd6cc12db --- /dev/null +++ b/update-11ty/assets/svg/part1-basics.md-3.svg @@ -0,0 +1 @@ +ApplicationWPE BackendThreadedCompositorApplicationWPE BackendThreadedCompositorloop[Rendering]frame_will_render(...)GLES renderingframe_rendered(...)frame transferframe presentationdispatch_frame_complete(...) \ No newline at end of file diff --git a/update-11ty/assets/svg/part2-eglstream.md-1.svg b/update-11ty/assets/svg/part2-eglstream.md-1.svg new file mode 100644 index 000000000..49249627d --- /dev/null +++ b/update-11ty/assets/svg/part2-eglstream.md-1.svg @@ -0,0 +1 @@ +ConsumerProducerConsumerProducerloop[Rendering]Create the EGLStreamGet the EGLStream file descriptorInitialize the consumerSend the file descriptorCreate the EGLStreamInitialize the producerConnectedDraw frameSend frameConsume frameEGLStream ready for next frame \ No newline at end of file diff --git a/update-11ty/assets/svg/part2-eglstream.md-2.svg b/update-11ty/assets/svg/part2-eglstream.md-2.svg new file mode 100644 index 000000000..1f98e77fc --- /dev/null +++ b/update-11ty/assets/svg/part2-eglstream.md-2.svg @@ -0,0 +1 @@ +End-user applicationWPE Backend - Application main threadWPE Backend - Consumer threadEnd-user applicationWPE Backend - Application main threadWPE Backend - Consumer threadAcquire frame from producerwaitCall presentation callback with actual EGLImageUse the EGLImagewpe_offscreen_nvidia_view_backend_dispatch_frame_complete(...)Unlock consumer threadRelease frame \ No newline at end of file diff --git a/update-11ty/assets/svg/svg_render_tree_lbse.svg b/update-11ty/assets/svg/svg_render_tree_lbse.svg new file mode 100644 index 000000000..31057f129 --- /dev/null +++ b/update-11ty/assets/svg/svg_render_tree_lbse.svg @@ -0,0 +1,3 @@ + + +RenderLayerModelObjectBase class for renderers that create layers.Only used by RenderBoxModelObject at present.RenderBoxBase class for non-inline boxes / elements.RenderBoxModelObjectBase class for renderers that adhereto the CSS box model object.RenderElementBase class for renderers thatoperate on DOM ElementsRenderObjectBase class for all renderers.RenderInlineBase class for CSS inline boxes.RenderSVGModelObjectBase class for renderers that adhereto the SVG painting model.RenderSVGImage<image> elementRenderSVGShape<line>, <rect>, <path>, etc.RenderSVGContainer<g>, <defs>, etc.RenderSVGTSpan<tspan> elementRenderSVGTextPath<textPath> elementRenderBlockBase class for CSS block-level elements.RenderBlockFlowContains either block or inline children.RenderSVGText<text> elementRenderSVGForeignObject<foreignObject> elementRenderReplacedBase class for CSS replaced elements.RenderSVGRootOutermost <svg> elementRenderSVGBlockBase class for SVG renderers thatbehave like CSS block-level elements.RenderSVGInlineBase class for SVG renderers thatbehave like CSS inline-level elementsVisual Paradigm Online Free EditionVisual Paradigm Online Free Edition diff --git a/update-11ty/assets/svg/svg_render_tree_legacy.svg b/update-11ty/assets/svg/svg_render_tree_legacy.svg new file mode 100644 index 000000000..d66ab66e1 --- /dev/null +++ b/update-11ty/assets/svg/svg_render_tree_legacy.svg @@ -0,0 +1,3 @@ + + +RenderLayerModelObjectBase class for renderers that create layers.Only used by RenderBoxModelObject at present.RenderBoxBase class for non-inline boxes / elements.RenderBoxModelObjectBase class for renderers that adhereto the CSS box model object.RenderElementBase class for renderers thatoperate on DOM ElementsRenderObjectBase class for all renderers.RenderInlineBase class for CSS inline boxes.RenderSVGModelObjectBase class for renderers that adhereto the SVG painting model.RenderTextBase class for renderers thatoperate on DOM Text nodesRenderSVGImage<image> elementRenderSVGShape<line>, <rect>, <path>, etc.RenderSVGContainer<g>, <defs>, etc.RenderSVGTSpan<tspan> elementRenderSVGTextPath<textPath> elementRenderBlockBase class for CSS block-level elements.RenderBlockFlowContains either block or inline children.RenderQuoteRenderRubyRenderSVGText<text> elementRenderSVGForeignObject<foreignObject> elementRenderReplacedBase class for CSS replaced elements.RenderSVGRootOutermost <svg> elementRenderSVGBlockBase class for SVG renderers thatbehave like CSS block-level elements.RenderSVGInlineBase class for SVG renderers thatbehave like CSS inline-level elementsRenderLineBreaketc.RenderTableRowRenderFrameSetRenderGridetc.RenderTableRenderListItemRenderMeteretc.RenderHTMLCanvasRenderImageetc.Visual Paradigm Online Free EditionVisual Paradigm Online Free Edition diff --git a/update-11ty/assets/svg/twitter_Profile_Color_400px.svg b/update-11ty/assets/svg/twitter_Profile_Color_400px.svg new file mode 100644 index 000000000..bd2d00840 --- /dev/null +++ b/update-11ty/assets/svg/twitter_Profile_Color_400px.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/twitter_Profile_bw_400px.svg b/update-11ty/assets/svg/twitter_Profile_bw_400px.svg new file mode 100644 index 000000000..fcd982be4 --- /dev/null +++ b/update-11ty/assets/svg/twitter_Profile_bw_400px.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/web_Logo_Header_Color_300x110px.svg b/update-11ty/assets/svg/web_Logo_Header_Color_300x110px.svg new file mode 100644 index 000000000..2dfdb2bb7 --- /dev/null +++ b/update-11ty/assets/svg/web_Logo_Header_Color_300x110px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/update-11ty/assets/svg/wix-logo.svg b/update-11ty/assets/svg/wix-logo.svg new file mode 100644 index 000000000..68b5df2e8 --- /dev/null +++ b/update-11ty/assets/svg/wix-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/update-11ty/assets/svg/wpe-architecture-diagram.svg b/update-11ty/assets/svg/wpe-architecture-diagram.svg new file mode 100644 index 000000000..05878b47b --- /dev/null +++ b/update-11ty/assets/svg/wpe-architecture-diagram.svg @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Hardware + + Operating System + + + + + Cog + Backend + WebKit + + Application + + diff --git a/update-11ty/assets/svg/wpe-birthday-cake-5-years.svg b/update-11ty/assets/svg/wpe-birthday-cake-5-years.svg new file mode 100644 index 000000000..526741935 --- /dev/null +++ b/update-11ty/assets/svg/wpe-birthday-cake-5-years.svg @@ -0,0 +1,1207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + Initial release: 21.04.2017 + + + + + + diff --git a/update-11ty/assets/twitter_Profile_400px.png b/update-11ty/assets/twitter_Profile_400px.png new file mode 100644 index 000000000..312d0c24b Binary files /dev/null and b/update-11ty/assets/twitter_Profile_400px.png differ diff --git a/update-11ty/assets/twitter_Profile_WhiteBg_400px.png b/update-11ty/assets/twitter_Profile_WhiteBg_400px.png new file mode 100644 index 000000000..d70dc10ad Binary files /dev/null and b/update-11ty/assets/twitter_Profile_WhiteBg_400px.png differ diff --git a/update-11ty/assets/video/WPE-1.mp4 b/update-11ty/assets/video/WPE-1.mp4 new file mode 100644 index 000000000..186744378 Binary files /dev/null and b/update-11ty/assets/video/WPE-1.mp4 differ diff --git a/update-11ty/assets/video/homepage.mp4 b/update-11ty/assets/video/homepage.mp4 new file mode 100644 index 000000000..2eba2fa57 Binary files /dev/null and b/update-11ty/assets/video/homepage.mp4 differ diff --git a/update-11ty/assets/web_Logo_Header_300x110px.png b/update-11ty/assets/web_Logo_Header_300x110px.png new file mode 100644 index 000000000..2a1cd5671 Binary files /dev/null and b/update-11ty/assets/web_Logo_Header_300x110px.png differ diff --git a/update-11ty/assets/wpe-architecture-diagram.png b/update-11ty/assets/wpe-architecture-diagram.png new file mode 100644 index 000000000..480b8dae5 Binary files /dev/null and b/update-11ty/assets/wpe-architecture-diagram.png differ diff --git a/update-11ty/assets/wpe-architecture-diagram@2x.png b/update-11ty/assets/wpe-architecture-diagram@2x.png new file mode 100644 index 000000000..4550524cf Binary files /dev/null and b/update-11ty/assets/wpe-architecture-diagram@2x.png differ diff --git a/update-11ty/blog.xml b/update-11ty/blog.xml new file mode 100644 index 000000000..dca163da8 --- /dev/null +++ b/update-11ty/blog.xml @@ -0,0 +1,1093 @@ + + + WPE WebKit Blog + News related to WPE WebKit. + + + 2024-10-07T00:00:00Z + https://wpewebkit.org/blog/ + + + WPE WebKit 2.46 highlights + + 2024-10-07T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/2024-wpewebkit-2.46.html + <p>A couple of weeks ago, the WPE WebKit team released version 2.46. This is an important milestone for the project as, for the first time in a stable series, the Skia backend takes over rendering. Skia brings significant improvements to the graphics stack, so we are very happy for this release. The list of changes goes beyond graphics, and it’s not short of awesome, so let’s have a look to what’s new!</p> +<h3 id="cairo-is-out%2C-skia-is-in" tabindex="-1">Cairo is out, Skia is in</h3> +<p>We <a href="https://blogs.igalia.com/carlosgc/2024/02/19/webkit-switching-to-skia-for-2d-graphics-rendering/">announced</a> some time ago that a new rendering backend with <a href="https://skia.org/">Skia</a> was on the works and that it would eventually replace Cairo. 2.46 the first release series where Skia is used, bringing important improvements in rendering and performance.</p> +<p>While Skia can use a GPU for rendering, our testing with common embedded SoCs has shown that the way WPE WebKit works may result in slightly worse performance in some cases than letting Skia use the CPU. Hence, for the 2.46 releases the latter is the default, while development continues to improve GPU usage on low-powered devices with the ultimate goal of making accelerated rendering the default choice in all cases.</p> +<p>The Cairo backend is still present and will be selected automatically at build time for big-endian architectures, where Skia is not yet supported. We plan to remove support for Cairo in the near future, and this approach allows us to ship the new renderer while solving the remaining issues. At any rate, the Cairo renderer is no longer receiving active development.</p> +<p>It is important to notice that it is recommended to build WPE with Clang instead of GCC. This comes from upstream Skia; see their <a href="https://skia.org/docs/user/build/#supported-and-preferred-compilers">supported and preferred compilers page</a> for details.</p> +<h3 id="graphics-stack-revamped" tabindex="-1">Graphics stack revamped</h3> +<p>Tha switch to Skia has made possible a significant number of changes and improvements in the WebKit graphics stack. These changes relate to accelerated canvas, accelerated CSS filters, color spaces, and more. <a href="https://blogs.igalia.com/carlosgc/2024/09/27/graphics-improvements-in-webkitgtk-and-wpewebkit-2-46/">Carlos García has written extensively about these changes</a> in his blog, we recommend reading his article for more details.</p> +<h3 id="trace-point-profiling-with-sysprof" tabindex="-1">Trace point profiling with sysprof</h3> +<p>Sysprof is a profiling and performance analysis tool for Linux. Thanks to integration with <code>libsysprof-capture</code>, it is now possible to use Sysprof to record trace points to do profiling and performance analysis of WebKit internals. This is a major improvement that will allow us to more effectively analyze the code paths that are more performance-sensitive and find ways to optimize them. It will also allow vendors to profile their specific hardware configurations and specific use-cases as well.</p> +<p>For a more in-depth presentation of the integration with Sysprof, please read <a href="https://feaneron.com/2024/07/12/profiling-a-web-engine/">Georges Stavacras’ blog post on the topic</a>.</p> +<h3 id="api-changes" tabindex="-1">API changes</h3> +<h4 id="additions" tabindex="-1">Additions</h4> +<ul> +<li><a href="https://webkitgtk.org/reference/webkitgtk/unstable/method.Settings.apply_from_key_file.html"><code>webkit_settings_apply_from_key_file()</code></a> allows applying WebKit settings directly from a key file</li> +<li>The console message API, which had been previously deprecated, has been brought to the current API</li> +<li><a href="https://webkitgtk.org/reference/webkitgtk/2.46.0/signal.AutomationSession.will-close.html"><code>WebKitAutomationSession::will-close</code></a> signal, which allows clients to perform cleanup tasks before an automation session is closed</li> +<li><a href="https://webkitgtk.org/reference/webkitgtk/2.46.0/property.Settings.enable-2d-canvas-acceleration.html"><code>enable-2d-canvas-acceleration</code></a> WebSetting can be used to control 2D-canvas acceleration in Skia-enabled builds</li> +<li><code>webkit_web_view_toggle_inspector()</code> shows or hides the web inspector for a given webview (only available with the WPE platform API)</li> +</ul> +<h4 id="deprecations" tabindex="-1">Deprecations</h4> +<ul> +<li><code>WebKitWebView::insecure-content-detected</code> signal.</li> +<li><code>WebKitWebContext:use-system-appearance-for-scrollbars</code> property.</li> +<li><code>webkit_web_context_set_use_system_appearance_for_scrollbars()</code> and <code>webkit_web_context_get_use_system_appearance_for_scrollbars()</code>.</li> +</ul> +<h3 id="gstreamer-customizations" tabindex="-1">GStreamer customizations</h3> +<p>Compile-time platform-specific GStreamer customizations are now done at runtime, using the <code>WEBKIT_GST_QUIRKS</code> and <code>WEBKIT_GST_HOLE_PUNCH_QUIRK</code> environment variables. Setting their value to <code>help</code> will return a help message with the possible values to <code>stderr</code>. A list of the removed CMake defines:</p> +<ul> +<li><code>USE_GSTREAMER_NATIVE_VIDEO</code></li> +<li><code>USE_GSTREAMER_NATIVE_AUDIO</code></li> +<li><code>USE_GSTREAMER_TEXT_SINK</code></li> +<li><code>USE_GSTREAMER_HOLEPUNCH</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_WESTEROS</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_BCM_NEXUS</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_AMLOGIC</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_RPI</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_BROADCOM</code></li> +<li><code>USE_WESTEROS_SINK</code></li> +</ul> +<h3 id="web-platform-changes" tabindex="-1">Web Platform changes</h3> +<p>The changes to supported Web Platform features between releases of WebKit are always substantial, and for that reason listing all of those changes here would be a major endeavour. The following is an incomplete list of some of the features that have been enabled, removed, and marked in preview state since 2.44, in no particular order:</p> +<ul> +<li>CSS Container/Style Queries</li> +<li>CSS <code>text-wrap-style</code></li> +<li>CSS <code>background-clip: border-area</code></li> +<li>CSS <code>text-underline-position: left|right</code></li> +<li>CSS <code>scrollbar-width</code></li> +<li>CSS View Transitions</li> +<li>CSS Grid Masonry layout (preview)</li> +<li>CSS <code>::target-text</code> pseudo element</li> +<li>WebCrypto X25519 algorithm (preview)</li> +<li>AppCache support has been removed</li> +<li>New <code>Promise.try()</code> method</li> +<li>New <code>Observable</code> methods, like <code>.map()</code> and <code>.filter()</code></li> +</ul> +<h3 id="other-noteworthy-changes" tabindex="-1">Other noteworthy changes</h3> +<ul> +<li>Suport for the WebP image format is now always enabled.</li> +<li>WebDriver clients may now connect to an already running process, instead of always needing to spawn a new one.</li> +<li>The <code>gst-libav</code> AAC decoders are now disabled due to outstanding bugs. Distributors are encouraged to use the GStreamer FDK AAC decoder (part of <code>gst-plugins-bad</code>) instead.</li> +</ul> +<h3 id="and-much-more!" tabindex="-1">And much more!</h3> +<p>WebKit evolves and changes a lot between major stable releases. Listing all changes would not be possible. There are countless bug fixes, performance improvements, new web features supported, and so on. We recommend checking the <a href="https://wpewebkit.org/release/">release notes</a> and the git log for more details.</p> +<p>The WPE WebKit team is already working on the 2.48 release, schedule for early next year. Until then!</p> + + + + + Update on the Layer Based SVG Engine (LBSE) in WebKit + + 2024-05-21T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/status-of-lbse-in-webkit.html + <p>This blog entry gives an update on what we at <a href="https://www.igalia.com/">Igalia</a> have done on upstreaming and development of LBSE in WebKit in the last seven months. For an explanation of +what LBSE is and how it is related to WPE, see this <a href="https://wpewebkit.org/blog/05-new-svg-engine.html">previous entry</a> as a refresher.</p> +<div style="float: right"> +<figure> + <a class="btn" href="https://www.igalia.com/" target="_blank"><img style="display: block; height: 100px;" src="https://wpewebkit.org/assets/svg/igalia-tagline.svg" alt="The Igalia logo" /> + </a> +</figure> +<figure> + <a class="btn" href="https://wix.com/" target="_blank"><img style="display: block; height: 60px;" src="https://wpewebkit.org/assets/svg/wix-logo.svg" alt="The Wix logo" /> + </a> +</figure> +</div> +<p>Thanks to generous funding by <a href="https://www.wix.com/" alt="Wix homepage"><b>Wix</b></a>, which extensively uses SVG in their products and has a broad +knowledge of the SVG peculiarities across browser engines, LBSE has made great progress in the past seven months. During this period, several advanced SVG painting +features were implemented (e.g. clip-paths, masks, gradients, patterns), along with important performance improvements that expanded the new engine’s capabilities +and stability. All this was possible thanks to Wix’s decision to address their problems by funding upstream work at the core of WebKit, instead of accepting +the status-quo and implementing case-by-case fixes and workarounds on their end. As a result, WebKit-based browsers now benefit from the results of this fruitful +collaboration, which we’ll try to explain in more detail in this blog post.</p> +<h2 id="project-kick-off-and-webkit-contributors-meeting" tabindex="-1">Project kick off and WebKit Contributors Meeting</h2> +<p>In <strong>October 2023</strong> we started the project mostly by thinking about the design and roadmap. We also did some general SVG bug fixing. For example, visual overflow computation for SVG renderers was <a href="https://commits.webkit.org/268981@main">corrected</a>, which fixed quite a few SVG pixel tests. Various visual bugs were also fixed, such as <a href="https://commits.webkit.org/269034@main">unnecessary repainting when <code>viewBox</code> is used on &lt;svg&gt; elements</a>, and <a href="https://commits.webkit.org/269360@main">incorrect clipping for outermost &lt;svg&gt; elements</a></p> +<p>Also in the same month, we attended the <a href="https://docs.webkit.org/Other/Contributor%20Meetings/ContributorMeeting2023.html">WebKit Contributors Meeting</a>, where we presented a talk on the LBSE (<a href="https://www.slideshare.net/igalia/integrating-the-new-layerbased-svg-engine">slides are available here</a>). The feedback on LBSE at the meeting was very positive. Giving the talk early on in the process actually helped us since we needed to have a good design in place.</p> +<h2 id="supporting-svg-resources" tabindex="-1">Supporting SVG resources</h2> +<p>The main focus in <strong>October 2023</strong> was introducing the SVG resource concept, as already outlined in the WebKit Contributors Meeting talk. Thus, we started with <a href="https://commits.webkit.org/269522@main">adding a base SVG resource class: <code>RenderSVGResourceContainer</code> </a>. This class was kept as simple as possible, with no support for resource invalidation or repainting logic. The main task of <code>RenderSVGResourceContainer</code> is to take care of registration so that the resource can be looked up by its clients.</p> +<p>For the first SVG resource to implement, we chose the SVG &lt;clip-path&gt; element, so we landed <a href="https://commits.webkit.org/269635@main"><code>RenderSVGResourceClipper</code></a>. To comply with the specification, the <code>RenderSVGResourceClipper</code> implementation produces 1-bit masks and uses a special rendering mode:</p> +<ul> +<li><code>fill-opacity</code>/<code>stroke-opacity</code>/<code>opacity</code> set to <code>1</code></li> +<li>masker/filter not applied when rendering the children</li> +<li><code>fill</code> set to solid black and <code>stroke</code> set to <code>none</code></li> +</ul> +<p>The initial implementation did not use caching of ImageBuffers for clipping, but relied on Porter-Duff DestinationIn/SourceOver compositing operations to achieve the same effect, but faster. By integrating <code>RenderSVGResourceClipper</code> properly into <code>RenderLayer</code>, it aligned SVG clipping with HTML/CSS clipping.</p> +<p>Finally, the implementation prefers a pure clipping solution internally, as in legacy rendering, but for more complicated clip-paths (for example when the clip-path involves text content), a fallback to a mask is done.</p> +<h2 id="resource-invalidation-handling" tabindex="-1">Resource invalidation handling</h2> +<p>After introducing the first SVG resource (<code>RenderSVGResourceClipper</code>), we noticed some issues with handling invalidations for it, such as adding to clip-path contents. In the legacy engine, invalidations have been handled through layouting. This caused various problems: for one, it could cause calling the <code>setNeedsLayout</code> method from within layout, which meant the invalidation chain depended on the DOM order.</p> +<p>In <strong>November 2023</strong>, <a href="https://commits.webkit.org/271017@main">an implementation landed</a> that avoided using layout for resource invalidation. Instead, on dynamic updates, the style system is used to determine the appropriate action:</p> +<ul> +<li>For non resources, changes that cause renderer geometry changes, like changing the <code>x</code> value of a &lt;rect&gt; element, still require a relayout. For visual changes not affecting geometry, like changing the fill color of a &lt;rect&gt;, a repaint action is enough.</li> +<li>For resources, the resource is invalidated/updated with the change and any of its clients are repainted using the new resource.</li> +</ul> +<h2 id="support-for-masks" tabindex="-1">Support for masks</h2> +<p>With improved support for SVG resource invalidation, in <strong>late November 2023</strong> we were ready to upstream support for the next SVG resource, <a href="https://commits.webkit.org/271153@main">RenderSVGResourceMasker</a>.</p> +<p>Like the support for clip-path, <code>RenderSVGResourceMasker</code> started out without caching image buffers and relied on creating temporary image buffers at rendering time. Mask content invalidations/changes were supported out of the box since we had improved resource invalidation handling (see above).</p> +<h2 id="support-for-gradients" tabindex="-1">Support for gradients</h2> +<p>In early <strong>January 2024</strong>, <a href="https://commits.webkit.org/272653@main">support for SVG gradients was upstreamed</a>. Gradients are a kind of SVG resource that is a bit different to the previously implemented clipping paths and masks because it is a <a href="https://www.w3.org/TR/SVG2/pservers.html">paint server</a>, so a helper class for that called <code>SVGPaintServerHandling</code> and a base class <code>RenderSVGResourcePaintServer</code> were introduced. The main difference is in invalidation: paint servers simply need a repaint of all its clients on invalidation, whereas clipping paths/masks may need to do more work; i.e., masks underlying image buffers need to be updated before its clients can be repainted.</p> +<h2 id="support-for-patterns-and-markers" tabindex="-1">Support for patterns and markers</h2> +<p>By the end of <strong>January 2024</strong>, <a href="https://commits.webkit.org/273757@main">support for SVG patterns was upstreamed</a>. In the first implementation, no image buffer caching was implemented in order to keep things clean and simple. This implementation is different from the legacy implementation because the pattern contents are being rendered through pattern content layers (see <a href="https://github.com/WebKit/WebKit/blob/main/Source/WebCore/rendering/RenderLayer.cpp#L2846"><code>RenderLayer::paintSVGResourceLayer</code></a>). To make this work, <code>RenderSVGResourcePattern</code> has to set up the graphics context matrix correctly before calling <code>paintSVGResourceLayer</code>.</p> +<p>Around that same time, we implemented the next to last SVG resource on our TODO list, namely <a href="https://commits.webkit.org/273820@main">SVG markers</a>.</p> +<h2 id="svg-filters" tabindex="-1">SVG filters</h2> +<p>In <strong>February 2024</strong>, Apple started work on <a href="https://github.com/WebKit/WebKit/pull/25087">supporting SVG filters</a> in LBSE. A first iteration managed to fix a lot of the official SVG filter tests, but it turned out a <a href="https://github.com/WebKit/WebKit/pull/25512">filter regression</a> had to be fixed first. Moreover, the initial work uncovered issues with the HTML/CSS filters implementation that need to be fixed in general. Finally, another reason why this support takes more time than some other features is that there is a strong requirement to make the support efficient in both memory usage and overall (re)painting speed. Still, the early results are very promising!</p> +<h2 id="cycle-detection" tabindex="-1">Cycle detection</h2> +<p>It is quite easy in SVG to cause direct circular references:</p> +<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>defs</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pattern</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>p<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#p<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>defs</span><span class="token punctuation">></span></span> + ... +<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre> +<p>It is also possible to cause indirect circular references:</p> +<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>defs</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mask</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>z<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#z)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mask</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>defs</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ellipse</span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#z)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> +<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre> +<p>The legacy engine solved this in an ad-hoc way in various places in the engine; it tried to break cycles before rendering, but still needed cycle protections in various places, since the solution was never unified or complete.</p> +<p>In <strong>February 2024</strong> we provided a unified solution for LBSE by introducing <code>SVGVisitedRendererTracking</code>; <a href="https://commits.webkit.org/274392@main">see this commit</a> for more. In the new approach, we don’t attempt to remove cycles, but detect them everywhere upon usage and stop processing in well-defined ways, all centralized in <code>SVGVisitedRendererTracking</code>.</p> +<h2 id="nested-mask%2Fpattern-slowness" tabindex="-1">Nested mask/pattern slowness</h2> +<p>In <strong>April 2024</strong>, we addressed the slowness problems with nested masks/patterns. As an example, consider this for nested masks:</p> +<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>defs</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mask</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>z<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#y)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#y)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + ... + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mask</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mask</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>y<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#x)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rect</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#x)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + ... + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mask</span><span class="token punctuation">></span></span> + ... + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>defs</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ellipse</span> <span class="token attr-name">mask</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url(#z)<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> +<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre> +<p>For this example, the complexity can be increased at will by adding more masks and contents per mask.</p> +<p>The solution was twofold:</p> +<ul> +<li>For masks, we realized bounding box calculations for a mask were not affected by masks used in the mask contents, so we could cut off bounding box calculations for nested masks.</li> +<li>For both masks and patterns, we added caching of image buffers per resource client so nested masks/patterns that are already encountered can reuse the image buffer cache.</li> +</ul> +<p>See optimizations here for nested <a href="https://commits.webkit.org/273820@main">masks</a> and <a href="https://commits.webkit.org/277306@main">patterns</a>.</p> +<h2 id="next-steps" tabindex="-1">Next steps</h2> +<p>For the short and mid-term, the plan is to make LBSE at least as good as legacy in regards to test coverage; i.e., all tests that pass in legacy should pass in LBSE. We have made a lot of progress over the +last seven months just because of the amount of SVG resources that were implemented, but for example ,we will need to have SVG filters in place to pass this goal.</p> +<p>Another goal is to make sure LBSE passes all security requirements, as failing that would be a blocker to replacing the current engine. Fortunately, we are already taking this into account in several ways, such as adopting a lot of good <a href="https://github.com/WebKit/WebKit/wiki/Smart-Pointer-Usage-Guidelines">smart pointer practices</a>.</p> +<p>Finally, a big goal will be for LBSE to perform well on certain benchmarks like <a href="https://browserbench.org/MotionMark1.2/">MotionMark</a>, since WebKit has a golden rule to never ship a performance regression. So far there has not been an explicit focus +on performance, and we know there are likely optimizations possible in <code>RenderLayer</code> usage, both in reducing the number of <code>RenderLayer</code> objects we create in certain situations as well as a possible reduction in complexity of <code>RenderLayer</code> for LBSE usage.</p> +<p>All in all, we are very pleased with the results and the progress we made in the last seven months. We at <a href="https://www.igalia.com/">Igalia</a> look forward to finishing the work to get the new engine in a shippable state in the near future!</p> + + + + + WPE WebKit 2.44 highlights + + 2024-04-08T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/2024-wpewebkit-2.44.html + <p>The WPE team released WPE WebKit 2.44 a few weeks ago. Let’s have a look at the most significant changes to the port for this release cycle.</p> +<h2 id="webkit%E2%80%99s-displaylink-support" tabindex="-1">WebKit’s DisplayLink support</h2> +<p>DisplayLink is a WebCore feature that improves resource utilization and improves synchronization with vertical screen retrace. 2.44 adds an implementation of this feature for the WPE port that improves rendering performance.</p> +<h2 id="improved-hardware-acceleration-video-decoding-and-rendering" tabindex="-1">Improved hardware-acceleration video decoding and rendering</h2> +<p>When WebKit is using GStreamer 1.24 or newer, video playback can use the new support for DRM modifiers in the DMA-BUF sink. This improves video decoding and rendering, as it allows for zero-copy negotiation with the video decoders.</p> +<h2 id="webcodec-api-supported" tabindex="-1">WebCodec API supported</h2> +<p>WPE now supports the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API">WebCodecs API</a>, which allows web developers low-level access to video frames and audio chunks, a feature of importance for multimedia applications that need finer grain control over what gets played on the browser.</p> +<h2 id="other-noteworthy-changes" tabindex="-1">Other noteworthy changes</h2> +<ul> +<li>Support for the JPEG2000 image format has been removed. WebKit was the only major engine still supporting the format, which these days is rarely used. As a consequence, OpenJPEG is no longer a dependency. JPEG2000 should not be confused with JPEG-XL, which is still supported.</li> +<li>Support usage of libbacktrace, enabled by default at build time. This library provides quality stacktraces that can help developers and deployers more efficiently debug crashes in WPE-powered browsers.</li> +<li>Many memory and stability improvements, particularly on the multimedia backends.</li> +</ul> +<p>For a complete list of changes, please check the <a href="https://wpewebkit.org/release/">releases page</a>.</p> + + + + + Use Case: Server-side headless rendering + + 2024-02-01T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/2024-use-case-server-side-rendering.html + <div class="success-top"> +<img alt="WPE and server-side headless rendering" align="center" src="https://wpewebkit.org/assets/img/logo-server-side-rendering.png" srcset="https://wpewebkit.org/assets/img/logo-server-side-rendering@2x.png 2x" /> +</div> +<p>In many distributed applications, it can be useful to run a light web browser on the server side to render some HTML content or process images, video and/or audio using JavaScript.</p> +<p>Some concrete use-cases can be:</p> +<ul> +<li>Video post-production using HTML overlays.</li> +<li>Easy 3D rendering with WebGL that can be broadcasted as a video stream.</li> +<li>Reusing the same JavaScript code between a frontend web application and the backend processing.</li> +</ul> +<p>WPE WebKit is the perfect solution for all those use cases as it offers a lightweight solution which can run on low-end hardware or even within a container. It provides a lot of flexibility at the moment of choosing the backend infrastructure as WPE WebKit can, for instance, run from within a container with a very minimal Linux configuration (no need for any windowing system) and with full hardware acceleration and zero-copy of the video buffers between the GPU and the CPU.</p> +<p>Additionally, the fact that WPE WebKit is optimized for lower-powered devices, makes it also the perfect option for server-side rendering when scaling commercial deployments while keeping cost under control, which is yet another important factor to take into account when considering cloud rendering.</p> + + + + + A New WPE Backend Using EGLStream + + 2024-01-29T06:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/07-creating-wpe-backends.html + <h2 id="what-is-a-wpe-backend%3F" tabindex="-1">What is a WPE Backend?</h2> +<div class="sidebar"> +<p>This article is a mashup of <a href="https://blogs.igalia.com/llepage/the-process-of-creating-a-new-wpe-backend/">The process of creating a new WPE +backend</a> +and <a href="https://blogs.igalia.com/llepage/use-eglstreams-in-a-wpe-webkit-backend/">Use EGLStreams in a WPE WebKit +backend</a>, +originally published at Loïc’s blog.</p> +</div> +<p>Depending on the target hardware WPE may need to use different techniques and +technologies to ensure correct graphical rendering. To be independent of any +user-interface toolkit and windowing system, WPE WebKit delegates the rendering +to a third-party API defined in the +<a href="https://github.com/WebPlatformForEmbedded/libwpe">libwpe</a> library. A concrete +implementation of this API is a “WPE backend”.</p> +<p>WPE WebKit is a multiprocess application, the end-user starts and controls the +web widgets in the application process (which we often call “the <abbr title="User Interface">UI</abbr> process” while the web engine itself uses +different subprocesses: <code>WPENetworkProcess</code> is in charge of managing network +connections and <code>WPEWebProcess</code> (or “web process”) in charge of the HTML and +JavaScript parsing, execution and rendering. The WPE backend is at a crossroads +between the UI process and one or more web process instances.</p> +<figure> + <a href="https://wpewebkit.org/assets/svg/part1-basics.md-1.svg" target="_blank"><img src="https://wpewebkit.org/assets/svg/part1-basics.md-1.svg" alt="Diagram showing a box for the WPE backend in between the UI process and WPEWebProcess" /> + </a> +</figure> +<p>The WPE backend is a shared library that is loaded at runtime by the web +process and by the UI process. It is used to render the visual aspect of a web +page and transfer the resulting video buffer from the web process to the +application process.</p> +<h2 id="backend-interfaces" tabindex="-1">Backend Interfaces</h2> +<p>The WPE backend shared library must export at least one symbol called +<code>_wpe_loader_interface</code> of type <code>struct wpe_loader_interface</code> as defined <a href="https://github.com/WebPlatformForEmbedded/libwpe/blob/d7c669ca6f5ec0d544c264016d270669b336c931/include/wpe/loader.h#L57">in +the <em>libwpe</em> +API</a>. +Presently its only member is <code>load_object</code>, a callback function that receives a +string with an interface name and returns concrete implementations of the +following interfaces:</p> +<ul> +<li><a href="https://github.com/WebPlatformForEmbedded/libwpe/blob/d7c669ca6f5ec0d544c264016d270669b336c931/include/wpe/renderer-host.h#L48">struct wpe_renderer_host_interface</a></li> +<li><a href="https://github.com/WebPlatformForEmbedded/libwpe/blob/d7c669ca6f5ec0d544c264016d270669b336c931/include/wpe/renderer-backend-egl.h#L75">struct wpe_renderer_backend_egl_interface</a></li> +<li><a href="https://github.com/WebPlatformForEmbedded/libwpe/blob/d7c669ca6f5ec0d544c264016d270669b336c931/include/wpe/renderer-backend-egl.h#L93">struct wpe_renderer_backend_egl_target_interface</a></li> +<li><a href="https://github.com/WebPlatformForEmbedded/libwpe/blob/d7c669ca6f5ec0d544c264016d270669b336c931/include/wpe/renderer-backend-egl.h#L115">struct wpe_renderer_backend_egl_offscreen_target_interface</a></li> +<li><a href="https://github.com/WebPlatformForEmbedded/libwpe/blob/d7c669ca6f5ec0d544c264016d270669b336c931/include/wpe/view-backend.h#L63">struct wpe_view_backend_interface</a></li> +</ul> +<p>The names passed to the <code>.load_object()</code> function are the same as those of the +interface types, prefixed with an underscore. For example, a +<code>.load_object(&quot;_wpe_renderer_host_interface&quot;)</code> call must return a pointer to a +<code>struct wpe_renderer_host_interface</code> object.</p> +<details> + <summary>Example C code for a <code>load_object</code> callback.</summary> +<!-- load_object example <<<1 --> +<pre class="language-c"><code class="language-c"><span class="token keyword">static</span> <span class="token keyword">struct</span> <span class="token class-name">wpe_renderer_host_interface</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> +<span class="token keyword">static</span> <span class="token keyword">struct</span> <span class="token class-name">wpe_renderer_backend_egl_interface</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> + +<span class="token keyword">static</span> <span class="token keyword">void</span><span class="token operator">*</span> +<span class="token function">my_backend_load_object</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>name<span class="token punctuation">)</span> +<span class="token punctuation">{</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">strcmp</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token string">"_wpe_renderer_host_interface"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> + <span class="token keyword">return</span> <span class="token operator">&amp;</span>my_renderer_host<span class="token punctuation">;</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">strcmp</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token string">"_wpe_renderer_backend_egl_interface"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> + <span class="token keyword">return</span> <span class="token operator">&amp;</span>my_renderer_backend_egl<span class="token punctuation">;</span> + + <span class="token comment">/* ... */</span> + + <span class="token keyword">return</span> <span class="token constant">NULL</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span> + +<span class="token keyword">struct</span> <span class="token class-name">wpe_loader_interface</span> _wpe_loader_interface <span class="token operator">=</span> <span class="token punctuation">{</span> + <span class="token punctuation">.</span>load_object <span class="token operator">=</span> my_backend_load_object<span class="token punctuation">,</span> +<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> +<!-- 1>>> --> +</details> +<p>Each of these interfaces follow the same base structure: the <code>struct</code> members +are callback functions, all interfaces have <code>create</code> and <code>destroy</code> members which +act as instance constructor and destructor, plus any additional “methods”. +The pointer returned by the <code>create</code> callback will be passed as the <code>object</code> +“instance” of the other methods:</p> +<pre class="language-c"><code class="language-c"><span class="token keyword">struct</span> <span class="token class-name">wpe_renderer_host_interface</span> <span class="token punctuation">{</span> + <span class="token keyword">void</span><span class="token operator">*</span> <span class="token punctuation">(</span><span class="token operator">*</span>create<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">void</span> <span class="token punctuation">(</span><span class="token operator">*</span>destroy<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">void</span> <span class="token operator">*</span>object<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token comment">/* ... */</span> +<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> +<p>In the <strong>UI process</strong> side WPE WebKit will create:</p> +<ul> +<li>One “renderer host” instance, using <code>wpe_renderer_host_interface.create()</code>.</li> +<li>Multiple “renderer host client” instances, using <code>wpe_renderer_host_interface.create_client()</code>. +These are mainly used for IPC communication, one instance gets created for +each web process launched by WebKit.</li> +<li>Multiple “view backend” instances, using <code>wpe_view_backend_interface.create()</code>. +One instance is created for each rendering target in the web process.</li> +</ul> +<p>In each <strong>web process</strong>—there can be more than one—WPE WebKit +will create:</p> +<ul> +<li>One “renderer backend EGL” instance, using <code>wpe_renderer_backend_egl_interface.create()</code>.</li> +<li>Multiple “renderer backend EGL target” instances, using +<code>wpe_renderer_backend_egl_target_interface.create()</code>. An instance is created +for each new rendering target needed by the application.</li> +</ul> +<details> + <summary>How about <code>wpe_renderer_backend_egl_offscreen_target_interface</code>?</summary> + <div> +<p>The <code>rendererBackendEGLTarget</code> instances may be created by the <code>wpe_renderer_backend_egl_target_interface</code>, or +the <code>wpe_renderer_backend_egl_offscreen_target_interface</code> depending on the interfaces implemented in the backend.</p> +<p>Here we are only focusing on the <code>wpe_renderer_backend_egl_target_interface</code> that is relying on a classical EGL +display (defined in the <code>rendererBackendEGL</code> instance). The <code>wpe_renderer_backend_egl_offscreen_target_interface</code> may +be used in very specific use-cases that are out of the scope of this post. You can check its usage in the WPE WebKit +<a href="https://github.com/WebKit/WebKit/blob/f32cd0f7cb7961420ce08ae78b8f01f287bec199/Source/WebCore/platform/graphics/egl/GLContextLibWPE.cpp#L61">source code</a> +for more information.</p> + </div> +</details> +<p>These instances typically communicate with each others using Unix sockets for +<abbr title="Inter-Process Communication">IPC</abbr>. The IPC layer must be +implemented in the WPE backend itself because the <em>libwpe</em> interfaces only pass +around the file descriptors to be used as communication endpoints.</p> +<p>From a topological point of view, all those instances are organized as follows:</p> +<figure> + <a href="https://wpewebkit.org/assets/svg/part1-basics.md-2.svg" target="_blank"><img src="https://wpewebkit.org/assets/svg/part1-basics.md-2.svg" /> + </a> +</figure> +<p>From an usage point of view:</p> +<ul> +<li>The <code>rendererHost</code> and <code>rendererHostClient</code> instances are only used to manage +IPC endpoints on the UI process side that are connected to each running +web process. They are not used by the graphical rendering system.</li> +<li>The <code>rendererBackendEGL</code> instance (one per web process) is only used to +connect to the native display for a specific platform. For example, on a +desktop Linux, the platform may be X11 where the native display would be the +result of calling <code>XOpenDisplay()</code>; or the platform may be Wayland and in +this case the native display would be the result of calling +<code>wl_display_connect()</code>; and so on.</li> +<li>The <code>rendererBackendEGLTarget</code> (on the web process side) and <code>viewBackend</code> +(on the UI process side) instances are the ones truly managing the web page +graphical rendering.</li> +</ul> +<h2 id="graphics-rendering" tabindex="-1">Graphics Rendering</h2> +<p>As seen above, the interfaces in charge of the rendering are +<code>wpe_renderer_backend_egl_target_interface</code> and <code>wpe_view_backend_interface</code>. +During their creation, WPE WebKit exchanges the file descriptors used to +establish a direct IPC connection between a <code>rendererBackendEGL</code> (in the +web process), and a <code>viewBackend</code> (in the UI process).</p> +<p>During the EGL initialization phase, when a new web process is launched, WebKit +will use the native display and platform provided by the +<code>wpe_renderer_backend_egl_interface.get_native_display()</code> and <code>.get_platform()</code> +functions to create a suitable OpenGL ES context.</p> +<p>When WebKit’s +<a href="https://github.com/WebKit/WebKit/blob/c22f641da18b8c4eee23b8021b37aeec69268675/Source/WebKit/Shared/CoordinatedGraphics/threadedcompositor/ThreadedCompositor.cpp#L220">ThreadedCompositor</a> +is ready to render a new frame (in the web process), it calls the +<code>wpe_renderer_backend_egl_target_interface.frame_will_render()</code> function to let +the WPE backend know that rendering is about to start. At this moment, the +previously created OpenGL ES context is made current to be used as the target +for GL drawing commands.</p> +<p>Once the threaded compositor has finished drawing, it will swap the front and +back EGL buffers and call the +<code>wpe_renderer_backend_egl_target_interface.frame_rendered()</code> function to signal +that the frame is ready. The compositor will then wait until the WPE backend +calls <code>wpe_renderer_backend_egl_target_dispatch_frame_complete()</code> to indicate +that the compositor may produce a new frame.</p> +<p>What happens inside the <code>.frame_will_render()</code> and <code>.frame_rendered()</code> +implementations is up to the WPE backend. As en example, it could +set up a <a href="https://www.khronos.org/opengl/wiki/Framebuffer_Object">Frame Buffer Object</a> +to have the web content draw offscreen, in a texture that can be passed +back to the UI process for further processing, or use extensions like +<a href="https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_stream.txt">EGLStream</a>, +or <a href="https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_image_dma_buf_export.txt">DMA-BUF exports</a> +to transfer the frame to the UI process without copying the pixel data.</p> +<figure> + <a href="https://wpewebkit.org/assets/svg/part1-basics.md-3.svg" target="_blank"><img src="https://wpewebkit.org/assets/svg/part1-basics.md-3.svg" /> + </a> +</figure> +<p>Typically the backend sends each new frame to the corresponding view backend in +in its <code>.frame_rendered()</code> function. The application can use the frame until it +sends back an <abbr>IPC</abbr> message to the renderer target (in the web +process) to indicate that the frame is not in use anymore and may be be freed +or recycled. Although it is not a requirement to do it at this exact point, +usually when a renderer backend receives this message it calls the +<code>wpe_renderer_backend_egl_target_dispatch_frame_complete()</code> function to trigger +the rendering of a new frame. As a side effect, this mechanism also allows +controlling the pace at which new frames are produced.</p> +<h2 id="using-eglstream" tabindex="-1">Using EGLStream</h2> +<p>EGLStream is an EGL extension that defines a mechanism to transfer hardware +video buffers from one process to another efficiently, without getting them +out of GPU memory. Although the extension is supported only in Nvidia +hardware, it makes for a good example as it transparently handles some +complexities involved, like buffers with multiple planes.</p> +<p>This backend uses the EGLStream extension to transfer graphics buffers from the +web process, which acts as a producer, to the UI process acting as a consumer. +The producer extension +<a href="https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_stream_producer_eglsurface.txt">EGL_KHR_stream_producer_eglsurface</a> +allows creating a surface that may be used as target for rendering, then using +<a href="https://registry.khronos.org/EGL/sdk/docs/man/html/eglSwapBuffers.xhtml">eglSwapBuffers()</a> +finishes drawing and sends the result to the consumer. Meanwhile, in the +consumer side, the +<a href="https://registry.khronos.org/EGL/extensions/NV/EGL_NV_stream_consumer_eglimage.txt">EGL_NV_stream_consumer_eglimage</a> +extension is used to turn each buffer into an <code>EGLImage</code>.</p> +<p>The reference source code for this WPE backend is available in the +<a href="https://github.com/Igalia/WPEBackend-offscreen-nvidia">WPEBackend-offscreen-nvidia</a> +repository, which has been tested with WPE WebKit 2.38.x or 2.40.x, and +<em>libwpe</em> version 1.14.x.</p> +<details> + <summary>Behold, the Future Belongs to DMA-BUF!</summary> + <div> +<p>With the growing adoption of +<a href="https://docs.kernel.org/driver-api/dma-buf.html">DMA-BUF</a> for sharing memory +buffers on modern Linux platforms, the WPE WebKit architecture will be +evolving and, in the future, the need for a WPE Backend should disappear in +most cases.</p> +<p>Ongoing work on WPE WebKit removes the need to provide a WPE backend +implementation for most hardware platforms, with a generic implementation +using DMA-BUF provided as an integral, built-in feature of WebKit. It will +still be possible to provide external implementations for platforms that +might need to use custom buffer sharing mechanisms.</p> +<p>From the application developer point of view, in most cases writing +programs that use the WPE WebKit API will be simpler, with the complexity +of the communication among multiple processes handled by WebKit.</p> + </div> +</details> +<h3 id="stream-setup" tabindex="-1">Stream Setup</h3> +<p>The steps needed to set up EGLStream endpoints need to be done in a particular +order:</p> +<ol> +<li>Create the consumer.</li> +<li>Get the stream file descriptor for the consumer.</li> +<li>Send the stream file descriptor to the producer.</li> +<li>Create the producer.</li> +</ol> +<p><strong>First</strong>, the consumer needs to be created:</p> +<pre class="language-cpp"><code class="language-cpp">EGLStream <span class="token function">createConsumerStream</span><span class="token punctuation">(</span>EGLDisplay eglDisplay<span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">static</span> <span class="token keyword">const</span> EGLint s_streamAttribs<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span> + EGL_STREAM_FIFO_LENGTH_KHR<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> + EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR<span class="token punctuation">,</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">,</span> + EGL_NONE + <span class="token punctuation">}</span><span class="token punctuation">;</span> + <span class="token keyword">return</span> <span class="token function">eglCreateStreamKHR</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">,</span> s_streamAttribs<span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<p>The <code>EGL_STREAM_FIFO_LENGTH_KHR</code> parameter defines the length of the EGLStream +queue. If set to zero, the stream will work in “mailbox” mode and each time the +producer has a new frame it will empty the stream content and replace the frame +by the new one. If non-zero, the stream works work in “<abbr title="First-In, +First-Out">FIFO</abbr>” mode, which means that the stream queue can contain up +to <code>EGL_STREAM_FIFO_LENGTH_KHR</code> frames.</p> +<p>Here we configure a queue for one frame because in this case the specification +of <code>EGL_KHR_stream_producer_eglsurface</code> guarantees that calling +<code>eglSwapBuffers()</code> on the producer the call will block until the consumer +retires the previous frame from queue. This is used as implicit synchronization +between the UI process side and the web process side without needing to rely on +custom IPC, which would add a small delay between frames.</p> +<p>The <code>EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR</code> parameter defines the maximum +timeout in microseconds to wait on the consumer side to acquire a frame when +calling <code>eglStreamConsumerAcquireKHR()</code>. It is only used with the +<code>EGL_KHR_stream_consumer_gltexture</code> extension because the +<code>EGL_NV_stream_consumer_eglimage</code> extension allows setting a timeout on each +call to <code>eglQueryStreamConsumerEventNV()</code> function.</p> +<p><strong>Second</strong>, to initialize the consumer using the <code>EGL_NV_stream_consumer_eglimage</code> +extension it is enough to call the <code>eglStreamImageConsumerConnectNV()</code> function.</p> +<p><strong>Once the consumer has been initialized</strong>, you need to send the EGLStream +file descriptor to the producer process. The usual way of achieving this would +be using IPC between the two processes, sending the file descriptor in a +<code>SCM_RIGHTS</code> message through an Unix socket—although with recent kernels +using <a href="https://lwn.net/Articles/808997/">pidfd_getfd()</a> may be an option if +both processes are related.</p> +<p>When the file descriptor is <strong>finally</strong> received, the producer endpoint can be +created using the <code>EGL_KHR_stream_producer_eglsurface</code> extension:</p> +<pre class="language-cpp"><code class="language-cpp"><span class="token keyword">const</span> EGLint surfaceAttribs<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span> + EGL_WIDTH<span class="token punctuation">,</span> width<span class="token punctuation">,</span> + EGL_HEIGHT<span class="token punctuation">,</span> height<span class="token punctuation">,</span> + EGL_NONE +<span class="token punctuation">}</span><span class="token punctuation">;</span> +EGLStream eglStream <span class="token operator">=</span> <span class="token function">eglCreateStreamFromFileDescriptorKHR</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">,</span> consumerFD<span class="token punctuation">)</span><span class="token punctuation">;</span> +EGLSurface eglSurface <span class="token operator">=</span> <span class="token function">eglCreateStreamProducerSurfaceKHR</span><span class="token punctuation">(</span>eglDisplay<span class="token punctuation">,</span> config<span class="token punctuation">,</span> eglStream<span class="token punctuation">,</span> surfaceAttribs<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> +<p>As with <abbr title="Pixel Buffer">pbuffer</abbr> surfaces, the dimensions +need to be specified as surface attributes. When picking a frame buffer +configuration with <code>eglChooseConfig()</code> the <code>EGL_SURFACE_TYPE</code> attribute must +be set to <code>EGL_STREAM_BIT_KHR</code>. From this point onwards, rendering proceeds as +usual: the EGL surface and context are made active, and once the painting is +done a call to <code>eglSwapBuffers()</code> will “present” the frame, which in this case +means sending the buffer with the pixel data down the EGLStream to the +consumer.</p> +<figure> + <a href="https://wpewebkit.org/assets/svg/part2-eglstream.md-1.svg" target="_blank"><img src="https://wpewebkit.org/assets/svg/part2-eglstream.md-1.svg" /> + </a> +</figure> +<h3 id="consuming-frames" tabindex="-1">Consuming Frames</h3> +<p>While on the producer side rendering treats the EGLStream surface like any +other, on the consumer some more work is needed to manager the lifetime of +the data received: frames have to be manually acquired and released once +they are not needed anymore.</p> +<p>The producer calls <code>eglQueryStreamConsumerEventNV()</code> repeatedly to retire the +next event from the stream:</p> +<ul> +<li><code>EGL_STREAM_IMAGE_ADD_NV</code> indicates that there is a buffer in the stream +that has not yet been bound to an <code>EGLImage</code>, and the application needs to +create a new one to which the actual data will be bound later.</li> +<li><code>EGL_STREAM_IMAGE_AVAILABLE_NV</code> indicates that a new frame is available +and that it can be bound to the previously created <code>EGLImage</code>.</li> +<li><code>EGL_STREAM_IMAGE_REMOVE_NV</code> indicates that a buffer has been retired from +the stream, and that its associated <code>EGLImage</code> may be released once the +application has finished using it.</li> +</ul> +<p>This translates roughly to the following code:</p> +<pre class="language-cpp"><code class="language-cpp"><span class="token keyword">static</span> <span class="token keyword">constexpr</span> EGLTime MAX_TIMEOUT_USEC <span class="token operator">=</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">;</span> +EGLImage eglImage <span class="token operator">=</span> EGL_NO_IMAGE<span class="token punctuation">;</span> + +<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + EGLenum event <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> + EGLAttrib data <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> + + <span class="token comment">// WARNING: The specification states that the timeout is in nanoseconds</span> + <span class="token comment">// (see: https://registry.khronos.org/EGL/extensions/NV/EGL_NV_stream_consumer_eglimage.txt)</span> + <span class="token comment">// but in reality it is in microseconds, at least with the version 535.113.01 of the NVidia drivers.</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">eglQueryStreamConsumerEventNV</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> eglStream<span class="token punctuation">,</span> MAX_TIMEOUT_USEC<span class="token punctuation">,</span> <span class="token operator">&amp;</span>event<span class="token punctuation">,</span> <span class="token operator">&amp;</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> + <span class="token keyword">break</span><span class="token punctuation">;</span> + + <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token keyword">case</span> EGL_STREAM_IMAGE_ADD_NV<span class="token operator">:</span> <span class="token comment">// Bind an incoming buffer to an EGLImage.</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span>eglImage<span class="token punctuation">)</span> <span class="token function">eglDestroyImage</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> eglImage<span class="token punctuation">)</span><span class="token punctuation">;</span> + eglImage <span class="token operator">=</span> <span class="token function">eglCreateImage</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> EGL_NO_CONTEXT<span class="token punctuation">,</span> EGL_STREAM_CONSUMER_IMAGE_NV<span class="token punctuation">,</span> + <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>EGLClientBuffer<span class="token operator">></span></span></span><span class="token punctuation">(</span>eglStream<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">nullptr</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">continue</span><span class="token punctuation">;</span> <span class="token comment">// Handle the next event.</span> + + <span class="token keyword">case</span> EGL_STREAM_IMAGE_REMOVE_NV<span class="token operator">:</span> <span class="token comment">// Buffer removed, EGLImage may be disposed.</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">{</span> + EGLImage image <span class="token operator">=</span> <span class="token generic-function"><span class="token function">reinterpret_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>EGLImage<span class="token operator">></span></span></span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">eglDestroyImage</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> image<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span>image <span class="token operator">==</span> eglImage<span class="token punctuation">)</span> + eglImage <span class="token operator">=</span> EGL_NO_IMAGE<span class="token punctuation">;</span> + <span class="token punctuation">}</span> + <span class="token keyword">continue</span><span class="token punctuation">;</span> <span class="token comment">// Handle the next event.</span> + + <span class="token keyword">case</span> EGL_STREAM_IMAGE_AVAILABLE_NV<span class="token operator">:</span> <span class="token comment">// New frame available.</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">eglStreamAcquireImageNV</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> eglStream<span class="token punctuation">,</span> <span class="token operator">&amp;</span>eglImage<span class="token punctuation">,</span> EGL_NO_SYNC<span class="token punctuation">)</span><span class="token punctuation">)</span> + <span class="token keyword">break</span><span class="token punctuation">;</span> + + <span class="token keyword">default</span><span class="token operator">:</span> + <span class="token keyword">continue</span><span class="token punctuation">;</span> <span class="token comment">// Handle the next event.</span> + <span class="token punctuation">}</span> + + <span class="token comment">/*** Use the EGLImage here ***/</span> + + <span class="token function">eglStreamReleaseImageNV</span><span class="token punctuation">(</span>display<span class="token punctuation">,</span> eglStream<span class="token punctuation">,</span> eglImage<span class="token punctuation">,</span> EGL_NO_SYNC<span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<p>The application is free to use each <code>EGLImage</code> as it sees fit. An obvious +example would be to use it as the contents for a texture, which then gets +painted in the “content” area of a web browser; or as the contents of the +screen for an in-game computer that the player can interact with, enabling +display of real, live web content as part of the gaming experience—now +<em>that</em> would be a deeply embedded browser!</p> +<h3 id="one-last-thing" tabindex="-1">One Last Thing</h3> +<p>There is a small showstopper to have EGLStream support working: +<a href="https://github.com/WebKit/WebKit/blob/cb07c70c253a35b0e09e46e6100e1cdcebab26e2/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp#L135">currently</a> +when WPE WebKit uses surfaceless EGL contexts it sets the surface type to +<code>EGL_WINDOW_BIT</code> attribute, while <code>EGL_STREAM_BIT_KHR</code> would be needed +instead. <a href="https://github.com/Igalia/WPEBackend-offscreen-nvidia/blob/main/wpewebkit-patches/005-fix-surfaceless-egl-context-creation.patch">A small +patch</a> +is enough to apply this tweak:</p> +<pre class="language-diff"><code class="language-diff">diff --git a/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp b/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp +index d5efa070..5f200edc 100644 +<span class="token coord">--- a/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp</span> +<span class="token coord">+++ b/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp</span> +@@ -122,9 +122,11 @@ bool GLContextEGL::getEGLConfig(EGLDisplay display, EGLConfig* config, EGLSurfac +<span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line"> attributeList[13] = EGL_PIXMAP_BIT; +</span><span class="token prefix unchanged"> </span><span class="token line"> break; +</span><span class="token prefix unchanged"> </span><span class="token line"> case GLContextEGL::WindowSurface: +</span></span><span class="token deleted-sign deleted"><span class="token prefix deleted">-</span><span class="token line"> case GLContextEGL::Surfaceless: +</span></span><span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line"> attributeList[13] = EGL_WINDOW_BIT; +</span><span class="token prefix unchanged"> </span><span class="token line"> break; +</span></span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span><span class="token line"> case GLContextEGL::Surfaceless: +</span><span class="token prefix inserted">+</span><span class="token line"> attributeList[13] = EGL_STREAM_BIT_KHR; +</span><span class="token prefix inserted">+</span><span class="token line"> break; +</span></span><span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line"> } +</span></span> +<span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line"> EGLint count; +</span></span></code></pre> +<!-- vim:set foldmethod=marker foldmarker=<<<,>>>: --> + + + + + Integrating WPE: URI Scheme Handlers and Script Messages + + 2023-03-07T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html + <p>Most Web content is designed entirely for screen display—and there is <em>a +lot</em> of it—so it will spend its life in the somewhat restricted sandbox +implemented by a web browser. But rich user interfaces using Web technologies +in all kinds of consumer devices require <em>some</em> degree of integration, an +escape hatch to interact with the rest of their software and hardware. This is +where a Web engine like WPE designed to be <em>embeddable</em> shines: not only does +WPE provide a <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/">stable API</a>, it is also comprehensive in +supporting a number of ways to <em>integrate</em> with its environment further than +the plethora of available <a href="https://developer.mozilla.org/en-US/docs/Web/API">Web platform APIs</a>.</p> +<p>Integrating a “Web view” (the main <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/class.WebView.html">entry point of the WPE embedding +API</a>) involves providing extension points, which allow the +Web content (HTML/CSS/JavaScript) it loads to call into native code provided +by the client application (typically written in C/C++) from JavaScript, and +vice versa. There are a number of ways in which this can be achieved:</p> +<ul> +<li><strong><a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#uri-scheme-handlers">URI scheme handlers</a></strong> allow native code to +register a custom <abbr title="Uniform Resource Identifier">URI</abbr> +scheme, which will run a user provided +function to produce content that can be “fetched” regularly.</li> +<li><strong><a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#user-script-messages">User script messaging</a></strong> can be used to send JSON +messages from JavaScript running in the same context as Web pages to an user +function, and vice versa.</li> +<li>The <strong>JavaScriptCore API</strong> is a powerful solution to provide new JavaScript +functionality to Web content seamlessly, almost as if they were implemented +inside the Web engine itself—akin to <a href="https://nodejs.org/api/addons.html#c-addons">NodeJS C++ addons</a>.</li> +</ul> +<p>In this post we will explore the first two, as they can support many +interesting use cases without introducing the additional complexity of +extending the JavaScript virtual machine. Let’s dive in!</p> +<h2 id="intermission" tabindex="-1">Intermission</h2> +<p>We will be referring to the code of a tiny browser written for the occasion. +Telling WebKit how to call our native code involves creating a +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/class.UserContentManager.html">WebKitUserContentManager</a>, customizing it, and then +associating it with web views during their creation. The only exception to +this are <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#uri-scheme-handlers">URI scheme handlers</a>, which are registered +using <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.WebContext.register_uri_scheme.html">webkit_web_context_register_uri_scheme()</a>. This +minimal browser includes an <code>on_create_view</code> function, which is the perfect +place to do the configuration:</p> +<pre class="language-c"><code class="language-c"><span class="token keyword">static</span> WebKitWebView<span class="token operator">*</span> +<span class="token function">on_create_view</span><span class="token punctuation">(</span>CogShell <span class="token operator">*</span>shell<span class="token punctuation">,</span> CogPlatform <span class="token operator">*</span>platform<span class="token punctuation">)</span> +<span class="token punctuation">{</span> + <span class="token function">g_autoptr</span><span class="token punctuation">(</span>GError<span class="token punctuation">)</span> error <span class="token operator">=</span> <span class="token constant">NULL</span><span class="token punctuation">;</span> + WebKitWebViewBackend <span class="token operator">*</span>view_backend <span class="token operator">=</span> <span class="token function">cog_platform_get_view_backend</span><span class="token punctuation">(</span>platform<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>view_backend<span class="token punctuation">)</span> + <span class="token function">g_error</span><span class="token punctuation">(</span><span class="token string">"Cannot obtain view backend: %s"</span><span class="token punctuation">,</span> error<span class="token operator">-></span>message<span class="token punctuation">)</span><span class="token punctuation">;</span> + + <span class="token function">g_autoptr</span><span class="token punctuation">(</span>WebKitUserContentManager<span class="token punctuation">)</span> content_manager <span class="token operator">=</span> <span class="token function">create_content_manager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/** NEW! **/</span> + <span class="token function">configure_web_context</span><span class="token punctuation">(</span><span class="token function">cog_shell_get_web_context</span><span class="token punctuation">(</span>shell<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/** NEW! **/</span> + + <span class="token function">g_autoptr</span><span class="token punctuation">(</span>WebKitWebView<span class="token punctuation">)</span> web_view <span class="token operator">=</span> + <span class="token function">g_object_new</span><span class="token punctuation">(</span>WEBKIT_TYPE_WEB_VIEW<span class="token punctuation">,</span> + <span class="token string">"user-content-manager"</span><span class="token punctuation">,</span> content_manager<span class="token punctuation">,</span> <span class="token comment">/** NEW! **/</span> + <span class="token string">"settings"</span><span class="token punctuation">,</span> <span class="token function">cog_shell_get_web_settings</span><span class="token punctuation">(</span>shell<span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token string">"web-context"</span><span class="token punctuation">,</span> <span class="token function">cog_shell_get_web_context</span><span class="token punctuation">(</span>shell<span class="token punctuation">)</span><span class="token punctuation">,</span> + <span class="token string">"backend"</span><span class="token punctuation">,</span> view_backend<span class="token punctuation">,</span> + <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">cog_platform_init_web_view</span><span class="token punctuation">(</span>platform<span class="token punctuation">,</span> web_view<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">webkit_web_view_load_uri</span><span class="token punctuation">(</span>web_view<span class="token punctuation">,</span> s_starturl<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">return</span> <span class="token function">g_steal_pointer</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>web_view<span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<details> + <summary>What is <code>g_autoptr</code>? + Does it relate to <code>g_steal_pointer</code>? + This does not look like C!</summary><div> +<p>In the shown code examples, <code>g_autoptr(T)</code> is a preprocessor macro provided by +GLib that declares a pointer variable of the <code>T</code> type, and arranges for +freeing resources automatically when the variable goes out of scope. For +objects this results in +<a href="https://docs.gtk.org/gobject/method.Object.unref.html">g_object_unref()</a> +being called.</p> +<p>Internally the macro takes advantage of the <code>__attribute__((cleanup, ...))</code> +compiler extension, which is supported by GCC and Clang. GLib also includes <a href="https://docs.gtk.org/glib/func.DEFINE_AUTOPTR_CLEANUP_FUNC.html">a +convenience +macro</a> that +can be used to define cleanups for your own types.</p> +<p>As for <code>g_steal_pointer</code>, it is useful to indicate that the ownership of a +pointer declared with <code>g_autoptr</code> is transferred outside from the current +scope. The function returns the same pointer passed as parameter and +resets it to <code>NULL</code>, thus preventing cleanup functions from running.</p> +</div></details> +<p>The size has been kept small thanks to reusing code from the <a href="https://github.com/Igalia/cog#cog">Cog +core</a> library. As a bonus, it should +run on Wayland, X11, and even on a bare display using the <abbr title="Direct +Rendering Manager">DRM<abbr>/<abbr title="Kernel Mode Setting">KMS</abbr> +subsystem directly. Compiling and running it, assuming you already have the +dependencies installed, should be as easy as running:</abbr></abbr></p> +<pre class="language-sh"><code class="language-sh">cc <span class="token parameter variable">-o</span> minicog minicog.c <span class="token variable"><span class="token variable">$(</span>pkg-config cogcore <span class="token parameter variable">--libs</span> <span class="token parameter variable">--cflags</span><span class="token variable">)</span></span> +./minicog wpewebkit.org</code></pre> +<p>If the current session kind is not automatically detected, a second parameter +can be used to manually choose among <code>wl</code> (Wayland), <code>x11</code>, <code>drm</code>, and so on:</p> +<pre class="language-sh"><code class="language-sh">./minicog wpewebkit.org x11</code></pre> +<p>The full, unmodified source for this minimal browser is included right below.</p> +<details> + <summary>Complete <code>minicog.c</code> source + (<a target="_blank" rel="noopener" href="https://gist.github.com/aperezdc/f6a65a95f2baa222c0ce9d65e516e13b">Gist</a>) + </summary> +<!-- minicog.c <<<1 --> +<div> +<pre><code class="language-c"> +/* + * SPDX-License-Identifier: MIT + * + * cc -o minicog minicog.c $(pkg-config wpe-webkit-1.1 cogcore --cflags --libs) + */ +&nbsp; +#include &lt;cog/cog.h&gt; +&nbsp; +static const char *s_starturl = NULL; +&nbsp; +static WebKitWebView* +on_create_view(CogShell *shell, CogPlatform *platform) +{ + g_autoptr(GError) error = NULL; + WebKitWebViewBackend *view_backend = cog_platform_get_view_backend(platform, NULL, &error); + if (!view_backend) + g_error("Cannot obtain view backend: %s", error->message); +&nbsp; + g_autoptr(WebKitWebView) web_view = + g_object_new(WEBKIT_TYPE_WEB_VIEW, + "settings", cog_shell_get_web_settings(shell), + "web-context", cog_shell_get_web_context(shell), + "backend", view_backend, + NULL); + cog_platform_init_web_view(platform, web_view); + webkit_web_view_load_uri(web_view, s_starturl); + return g_steal_pointer(&web_view); +} +&nbsp; +int +main(int argc, char *argv[]) +{ + g_set_application_name("minicog"); +&nbsp; + if (argc != 2 && argc != 3) { + g_printerr("Usage: %s [URL [platform]]\n", argv[0]); + return EXIT_FAILURE; + } +&nbsp; + g_autoptr(GError) error = NULL; + if (!(s_starturl = cog_uri_guess_from_user_input(argv[1], TRUE, &error))) + g_error("Invalid URL '%s': %s", argv[1], error->message); +&nbsp; + cog_modules_add_directory(COG_MODULEDIR); +&nbsp; + g_autoptr(GApplication) app = g_application_new(NULL, G_APPLICATION_DEFAULT_FLAGS); + g_autoptr(CogShell) shell = cog_shell_new("minicog", FALSE); + g_autoptr(CogPlatform) platform = + cog_platform_new((argc == 3) ? argv[2] : g_getenv("COG_PLATFORM"), &error); + if (!platform) + g_error("Cannot create platform: %s", error->message); +&nbsp; + if (!cog_platform_setup(platform, shell, "", &error)) + g_error("Cannot setup platform: %s\n", error->message); +&nbsp; + g_signal_connect(shell, "create-view", G_CALLBACK(on_create_view), platform); + g_signal_connect_swapped(app, "shutdown", G_CALLBACK(cog_shell_shutdown), shell); + g_signal_connect_swapped(app, "startup", G_CALLBACK(cog_shell_startup), shell); + g_signal_connect(app, "activate", G_CALLBACK(g_application_hold), NULL); +&nbsp; + return g_application_run(app, 1, argv); +} +</code></pre> +<!-- 1>>> --> +</div> +</details> +<h2 id="uri-scheme-handlers" tabindex="-1">URI Scheme Handlers</h2> +<figure> + <img src="https://wpewebkit.org/assets/svg/URI_syntax_diagram.svg" alt="“Railroad” diagram of URI syntax" /> + <figcaption>URI syntax (<a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a>, + <a target="_blank" rel="noopener" href="https://commons.wikimedia.org/wiki/File:URI_syntax_diagram.svg">source</a>), + notice the “scheme” component at the top left. + </figcaption> +</figure> +<p>A URI scheme handler allows “teaching” the web engine how to handle <em>any</em> +load (pages, subresources, the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a>, +<code>XmlHttpRequest</code>, …)—if you ever wondered how Firefox implements +<code>about:config</code> or how Chromium does <code>chrome://flags</code>, this is it. Also, +WPE WebKit has public API for this. Roughly:</p> +<ol> +<li>A custom URI scheme is registered using +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.WebContext.register_uri_scheme.html">webkit_web_context_register_uri_scheme()</a>. This also associates a callback function to it.</li> +<li>When WebKit detects a load for the scheme, it invokes the provided +function, passing a +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/class.URISchemeRequest.html">WebKitURISchemeRequest</a>.</li> +<li>The function generates data to be returned as the result of the load, +as a <a href="https://docs.gtk.org/gio/class.InputStream.html">GInputStream</a> +and calls <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.URISchemeRequest.finish.html">webkit_uri_scheme_request_finish()</a>. This sends the stream to WebKit as the +response, indicating the length of the response (if known), and the +MIME content type of the data in the stream.</li> +<li>WebKit will now read the data from the input stream.</li> +</ol> +<h3 id="echoes" tabindex="-1">Echoes</h3> +<p>Let’s add an echo handler to our <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#intermission">minimal browser</a> that +replies back with the requested URI. Registering the scheme is +straightforward enough:</p> +<pre class="language-c"><code class="language-c"><span class="token keyword">static</span> <span class="token keyword">void</span> +<span class="token function">configure_web_context</span><span class="token punctuation">(</span>WebKitWebContext <span class="token operator">*</span>context<span class="token punctuation">)</span> +<span class="token punctuation">{</span> + <span class="token function">webkit_web_context_register_uri_scheme</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> + <span class="token string">"echo"</span><span class="token punctuation">,</span> + handle_echo_request<span class="token punctuation">,</span> + <span class="token constant">NULL</span> <span class="token comment">/* userdata */</span><span class="token punctuation">,</span> + <span class="token constant">NULL</span> <span class="token comment">/* destroy_notify */</span><span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<details> + <summary>What are “user data” and “destroy notify”?</summary><div> +<p>The <code>userdata</code> parameter above is a convention used in many C libraries, and +specially in these based on GLib when there are callback functions involved. +It allows the <em>user</em> to supply a pointer to arbitrary <em>data</em>, which will be +passed later on as a parameter to the callback (<code>handle_echo_request</code> in the +example) when it gets invoked later on.</p> +<p>As for the <code>destroy_notify</code> parameter, it allows passing a function with the +signature <code>void func(void*)</code> (type +<a href="https://docs.gtk.org/glib/callback.DestroyNotify.html">GDestroyNotify</a>) which +is invoked with <code>userdata</code> as the argument once the user data is no longer +needed. In the example above, this callback function would be invoked when the +URI scheme is unregistered. Or, from a different perspective, this callback is +used to <em>notify</em> that the user data can now be <em>destroyed</em>.</p> +</div></details> +<p>One way of implementing <code>handle_echo_request()</code> could be wrapping the request +URI, which is part of the <code>WebKitURISchemeRequest</code> parameter to the handler, +stash it into a <a href="https://docs.gtk.org/glib/struct.Bytes.html">GBytes</a> +container, and <a href="https://docs.gtk.org/gio/ctor.MemoryInputStream.new_from_bytes.html">create an input stream to read back its +contents</a>:</p> +<pre class="language-c"><code class="language-c"><span class="token keyword">static</span> <span class="token keyword">void</span> +<span class="token function">handle_echo_request</span><span class="token punctuation">(</span>WebKitURISchemeRequest <span class="token operator">*</span>request<span class="token punctuation">,</span> <span class="token keyword">void</span> <span class="token operator">*</span>userdata<span class="token punctuation">)</span> +<span class="token punctuation">{</span> + <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>request_uri <span class="token operator">=</span> <span class="token function">webkit_uri_scheme_request_get_uri</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">g_print</span><span class="token punctuation">(</span><span class="token string">"Request URI: %s\n"</span><span class="token punctuation">,</span> request_uri<span class="token punctuation">)</span><span class="token punctuation">;</span> + + <span class="token function">g_autoptr</span><span class="token punctuation">(</span>GBytes<span class="token punctuation">)</span> data <span class="token operator">=</span> <span class="token function">g_bytes_new</span><span class="token punctuation">(</span>request_uri<span class="token punctuation">,</span> <span class="token function">strlen</span><span class="token punctuation">(</span>request_uri<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">g_autoptr</span><span class="token punctuation">(</span>GInputStream<span class="token punctuation">)</span> stream <span class="token operator">=</span> <span class="token function">g_memory_input_stream_new_from_bytes</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> + + <span class="token function">webkit_uri_scheme_request_finish</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> stream<span class="token punctuation">,</span> <span class="token function">g_bytes_get_size</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"text/plain"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<p>Note how we need to tell WebKit how to <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.URISchemeRequest.finish.html">finish the load +request</a>, +in this case only with the data stream, but it is possible to have <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.URISchemeRequest.finish_with_response.html">more +control of the +response</a> +or <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.URISchemeRequest.finish_error.html">return an +error</a>.</p> +<p>With these changes, it is now possible to make page loads from the new custom +URI scheme:</p> +<figure> + <img alt="Screenshot of the minicog browser loading a custom echo:// URI" src="https://wpewebkit.org/assets/img/extending-minicog-echouri.png" class="picture" /> + <figcaption>It worked!</figcaption> +</figure> +<h3 id="et-tu%2C-cors%3F" tabindex="-1">Et Tu, CORS?</h3> +<p>The main roadblock one may find when using custom URI schemes is that loads +are affected by <abbr title="Cross-Origin Resource Sharing">CORS</abbr> +checks. Not only that, WebKit by default does <em>not</em> allow sending cross-origin +requests to custom URI schemes. This is by design: instead of accidentally +leaking potentially sensitive data to websites, developers embedding a web +view <em>need</em> to consciously opt-in to allow <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> requests <em>and</em> +send back suitable <code>Access-Control-Allow-*</code> response headers.</p> +<p>In practice, the additional setup involves +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.WebContext.get_security_manager.html">retrieving</a> +the <code>WebKitSecurityManager</code> being used by the <code>WebKitWebContext</code> and +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.SecurityManager.register_uri_scheme_as_cors_enabled.html">registering the scheme as +CORS-enabled</a>. +Then, in the handler function for the custom URI scheme, create a +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/class.URISchemeResponse.html">WebKitURISchemeResponse</a>, +which allows fine-grained control of the response, including setting +<a href="https://libsoup.org/libsoup-3.0/struct.MessageHeaders.html">headers</a>, +and finishing the request instead with +<code>webkit_uri_scheme_request_finish_with_response()</code>.</p> +<p>Note that WebKit cuts some corners when using CORS with custom URI schemes: +handlers will <em>not</em> receive preflight <code>OPTIONS</code> requests. Instead, the CORS +headers from the replies are inspected, and if access needs to be denied +then the data stream with the response contents is discarded.</p> +<p>In addition to providing a complete CORS-enabled custom URI scheme <a href="https://gist.github.com/aperezdc/74809a6cd617faf445c22097a47bcb50">example</a>, +we recommend the <a href="https://httptoolkit.com/will-it-cors">Will It CORS?</a> tool +to help troubleshoot issues.</p> +<h3 id="further-ideas" tabindex="-1">Further Ideas</h3> +<p>Once we have WPE WebKit calling into our custom code, there are no limits +to what a URI scheme handler can do—as long as it involves replying +to requests. Here are some ideas:</p> +<ul> +<li>Allow pages to access a subset of paths from the local file system in a +controlled way (as <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#et-tu%2C-cors%3F">CORS applies</a>). For inspiration, +see <a href="https://igalia.github.io/cog/class.DirectoryFilesHandler.html">CogDirectoryFilesHandler</a>.</li> +<li>Package all your web application assets into a single ZIP file, making +loads from <code>app:/...</code> fetch content from it. Or, make the scheme handler +load data using <a href="https://docs.gtk.org/gio/struct.Resource.html">GResource</a> and bundle the application +inside your program.</li> +<li>Use the presence of a well-known custom URI to have a web application +realize that it is running on a certain device, and make its user +interface adapt accordingly.</li> +<li>Provide a REST API, which internally calls into +<a href="https://networkmanager.dev/">NetworkManager</a> to list and configure +wireless network adapters. Combine it with a local web application and +embedded devices can now easily get on the network.</li> +</ul> +<h2 id="user-script-messages" tabindex="-1">User Script Messages</h2> +<p>While <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#uri-scheme-handlers">URI scheme handlers</a> +allow streaming large chunks of data back into the Web engine, for exchanging +smaller pieces of information in a more programmatic fashion it may be +preferable to exchange messages without the need to trigger resource loads. +The user script messages part of the +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/class.UserContentManager.html">WebKitUserContentManager</a> API can be used this way:</p> +<ol> +<li>Register a user message handler with +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.UserContentManager.register_script_message_handler.html">webkit_user_content_manager_register_script_message_handler()</a>. +As opposed to URI scheme handlers, this only enables receiving messages, +but does not associate a callback function <em>yet</em>.</li> +<li>Associate a callback to the +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/signal.UserContentManager.script-message-received.html">script-message-received</a> +signal. The signal detail should be the name of the registered handler.</li> +<li>Now, whenever JavaScript code calls +<code>window.webkit.messageHandlers.&lt;name&gt;.postMessage()</code>, the signal is +emitted, and the native callback functions invoked.</li> +</ol> +<details> + <summary>Haven't I seen <code>postMessage()</code> elsewhere?</summary><div> +<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage">Yes</a>, +<a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage">you</a> +<a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/postMessage">have</a>. +The name is the same because it provides a similar functionality (send a +message), it guarantees little (the receiver should validate messages), and +there are <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm">similar +restrictions</a> +in the kind of values that can be passed along.</p> +</div></details> +<h3 id="it%E2%80%99s-all-javascript" tabindex="-1">It’s All JavaScript</h3> +<p>Let’s add a feature to our <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/blog/06-integrating-wpe.html#intermission">minimal browser</a> that will allow +JavaScript code to trigger rebooting or powering off the device where it is +running. While this should definitely <em>not</em> be functionality exposed to the +open Web, it is perfectly acceptable in an embedded device where we control +what gets loaded with WPE, and that exclusively uses a web application as its +user interface.</p> +<figure> + <img src="https://wpewebkit.org/assets/img/pepe-silvia-all-javascript.jpg" class="picture" alt="Pepe Silvia conspiracy image meme, with the text “It's all JavaScript” superimposed" /> + <figcaption>Yet most of the code shown in this post is C.</figcaption> +</figure> +<p>First, create a <code>WebKitUserContentManager</code>, register the message handler, +and connect a callback to its associated signal:</p> +<pre class="language-c"><code class="language-c"><span class="token keyword">static</span> WebKitUserContentManager<span class="token operator">*</span> +<span class="token function">create_content_manager</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span> +<span class="token punctuation">{</span> + <span class="token function">g_autoptr</span><span class="token punctuation">(</span>WebKitUserContentManager<span class="token punctuation">)</span> content_manager <span class="token operator">=</span> <span class="token function">webkit_user_content_manager_new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">webkit_user_content_manager_register_script_message_handler</span><span class="token punctuation">(</span>content_manager<span class="token punctuation">,</span> <span class="token string">"powerControl"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">g_signal_connect</span><span class="token punctuation">(</span>content_manager<span class="token punctuation">,</span> <span class="token string">"script-message-received::powerControl"</span><span class="token punctuation">,</span> + <span class="token function">G_CALLBACK</span><span class="token punctuation">(</span>handle_power_control_message<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">return</span> <span class="token function">g_steal_pointer</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>content_manager<span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<p>The callback receives a <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/struct.JavascriptResult.html">WebKitJavascriptResult</a>, from which we +can get the <a href="https://people.igalia.com/aperez/Documentation/wpe-javascriptcore-1.1/class.Value.html">JSCValue</a> with the contents of the parameter +passed to the <code>postMessage()</code> function. The <code>JSCValue</code> can now be inspected +to check for malformed messages and determine the action to take, and +then arrange to call <code>reboot()</code>:</p> +<pre class="language-c"><code class="language-c"><span class="token keyword">static</span> <span class="token keyword">void</span> +<span class="token function">handle_power_control_message</span><span class="token punctuation">(</span>WebKitUserContentManager <span class="token operator">*</span>content_manager<span class="token punctuation">,</span> + WebKitJavascriptResult <span class="token operator">*</span>js_result<span class="token punctuation">,</span> <span class="token keyword">void</span> <span class="token operator">*</span>userdata<span class="token punctuation">)</span> +<span class="token punctuation">{</span> + JSCValue <span class="token operator">*</span>value <span class="token operator">=</span> <span class="token function">webkit_javascript_result_get_js_value</span><span class="token punctuation">(</span>js_result<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">jsc_value_is_string</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + <span class="token function">g_warning</span><span class="token punctuation">(</span><span class="token string">"Invalid powerControl message: argument is not a string"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">return</span><span class="token punctuation">;</span> + <span class="token punctuation">}</span> + + g_autofree <span class="token keyword">char</span> <span class="token operator">*</span>value_as_string <span class="token operator">=</span> <span class="token function">jsc_value_to_string</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">int</span> action<span class="token punctuation">;</span> + <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">strcmp</span><span class="token punctuation">(</span>value_as_string<span class="token punctuation">,</span> <span class="token string">"poweroff"</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + action <span class="token operator">=</span> RB_POWER_OFF<span class="token punctuation">;</span> + <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">strcmp</span><span class="token punctuation">(</span>value_as_string<span class="token punctuation">,</span> <span class="token string">"reboot"</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + action <span class="token operator">=</span> RB_AUTOBOOT<span class="token punctuation">;</span> + <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> + <span class="token function">g_warning</span><span class="token punctuation">(</span><span class="token string">"Invalid powerControl message: '%s'"</span><span class="token punctuation">,</span> value_as_string<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">return</span><span class="token punctuation">;</span> + <span class="token punctuation">}</span> + + <span class="token function">g_message</span><span class="token punctuation">(</span><span class="token string">"Device will %s now!"</span><span class="token punctuation">,</span> value_as_string<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">sync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">reboot</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span><span class="token punctuation">;</span> +<span class="token punctuation">}</span></code></pre> +<p>Note that the <code>reboot()</code> system call above will most likely fail because it +needs administrative privileges. While the browser process could run as <code>root</code> +to sidestep this issue—definitely <em>not</em> recommended!—it would be +better to grant the <code>CAP_SYS_BOOT</code> capability to the process, and <em>much</em> +better to ask the system manager daemon to handle the job. In machines +using <a href="https://systemd.io/">systemd</a> a good option is to call the <code>.Halt()</code> +and <code>.Reboot()</code> methods of its <code>org.freedesktop.systemd1</code> interface.</p> +<p>Now we can write a small HTML document with some JavaScript sprinkled on top +to arrange sending the messages:</p> +<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>utf-8<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Device Power Control<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>reboot<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Reboot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>poweroff<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Power Off<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text/javascript<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> + <span class="token keyword">function</span> <span class="token function">addHandler</span><span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> + document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> + window<span class="token punctuation">.</span>webkit<span class="token punctuation">.</span>messageHandlers<span class="token punctuation">.</span>powerControl<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> + <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token punctuation">}</span> + <span class="token function">addHandler</span><span class="token punctuation">(</span><span class="token string">"reboot"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + <span class="token function">addHandler</span><span class="token punctuation">(</span><span class="token string">"poweroff"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> + </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> + <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span> +<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span></code></pre> +<p>The complete source code for this example can be found +<a href="https://gist.github.com/aperezdc/621c1ec6bb78923e27fc035fa0689522">in this Gist</a>.</p> +<h3 id="going-in-the-other-direction" tabindex="-1">Going In The Other Direction</h3> +<p>But how can one return values from user messages back to the JavaScript code +running in the context of the web page? Until recently, the only option +available was exposing some known function in the page’s JavaScript code, and +then using +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.WebView.run_javascript.html">webkit_web_view_run_javascript()</a> +to call it from native code later on. To make this more idiomatic and allow +waiting on a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a>, an approach like the following works:</p> +<ol> +<li>Have convenience JavaScript functions wrapping the calls to +<code>.postMessage()</code> which add an unique identifier as part of the message, +create a <code>Promise</code>, and store it in a <code>Map</code> indexed by the identifier. +The <code>Promise</code> is itself returned from the functions.</li> +<li>When the callback in native code handle messages, they need to take +note of the message identifier, and then use +<code>webkit_web_view_run_javascript()</code> to pass it back, along with the +information needed to resolve the promise.</li> +<li>The Javascript code running in the page takes the <code>Promise</code> from +the <code>Map</code> that corresponds to the identifier, and resolves it.</li> +</ol> +<p>To make this approach a bit more palatable, we could tell WebKit to <a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-1.1/method.UserContentManager.add_script.html">inject a +script</a> +along with the regular content, which would provide the <a href="https://gist.github.com/aperezdc/a112c6a61a5a11885eac2498702e3a6d">helper +functions</a> +needed to achieve this.</p> +<p>Nevertheless, the approach outlined above is cumbersome and can be +tricky to get right, not to mention that the effort needs to be duplicated in +each application. Therefore, we have recently added new API hooks to provide this +as a built-in feature, so starting in WPE WebKit 2.40 the recommended approach +involves using +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-2.0/method.UserContentManager.register_script_message_handler_with_reply.html">webkit_user_content_manager_register_script_message_handler_with_reply()</a> +to register handlers instead. This way, calling <code>.postMessage()</code> now returns a +<code>Promise</code> to the JavaScript code, and the callbacks connected to the +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-2.0/signal.UserContentManager.script-message-with-reply-received.html">script-message-with-reply-received</a> +signal now receive a +<a href="https://people.igalia.com/aperez/Documentation/wpe-webkit-2.0/struct.ScriptMessageReply.html">WebKitScriptMessageReply</a>, +which can be used to resolve the promise—either on the spot, or +asynchronously later on.</p> +<h3 id="even-more-ideas" tabindex="-1">Even More Ideas</h3> +<p>User script messages are a powerful and rather flexible facility to make WPE +integrate web content into a complete system. The provided example is rather +simple, but as long as we do not need to pass huge amounts of data in +messages the possibilities are almost endless—especially with the +added convenience in WPE WebKit 2.40. Here are more ideas that can +be built on top of user script messages:</p> +<ul> +<li>A handler could receive requests to “monitor” some object, and +return a <code>Promise</code> that gets resolved when it has received changes. +For example, this could make the user interface of a smart thermostat +react to temperate updates from a sensor.</li> +<li>A generic handler that takes the message payload and converts it into +<a href="https://en.wikipedia.org/wiki/D-Bus">D-Bus</a> method calls, allowing +web pages to control many aspects of a typical Linux system.</li> +</ul> +<h2 id="wrapping-up" tabindex="-1">Wrapping Up</h2> +<p>WPE has been designed from the ground up to integrate with the rest of the +system, instead of having a sole focus on rendering Web content inside a +monolithic web browser application. Accordingly, the public API must be +comprehensive enough to use it as a component of <em>any</em> application. This +results in features that allow plugging into the web engine at different +layers to provide custom behaviour.</p> +<p>At Igalia we have years of experience embedding WebKit into all kinds of +applications, and we are always sympathetic to the needs of such systems. If +you are interested collaborating with WPE development, or searching for a +solution that can tightly integrate web content in your device, feel free to +<a href="https://www.igalia.com/contact/">contact us</a>.</p> +<!-- vim:set foldmethod=marker foldmarker=<<<,>>>: --> + + + diff --git a/update-11ty/blog/01-happy-birthday-wpe.html b/update-11ty/blog/01-happy-birthday-wpe.html new file mode 100644 index 000000000..59a5e8169 --- /dev/null +++ b/update-11ty/blog/01-happy-birthday-wpe.html @@ -0,0 +1,354 @@ + + + + + + + + + + + + Happy birthday WPE! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Happy birthday WPE!

+ + + + + +
+ +
+ +

Welcome to the new Blog section on wpewebkit.org!

+

Today is a special day for Igalia, especially for those colleagues that work on WebKit: +Five years ago, on the 21st of April 2017, the WPE port was announced by our colleague +Žan Doberšek on the WebKit mailing list.

+

Let’s take some time to celebrate and recap how WPE evolved from the early prototyping days to the product empowering hundreds of millions of devices +worldwide today.

+
+Celebrating WPEs 5th birthday with a cake +
+

WPE is … what exactly?

+

To get everyone on the same page, let’s start by reiterating what WPE is: a WebKit port optimized for embedded devices. +It allows you to embed a full-fledged Web browser engine that supports a large set of modern Web technologies into your product. +WPE itself is not a Web browser such as Safari, Chrome or Firefox but contains the underlying building blocks to load, parse and +render websites. To learn more about the distinction between a Web browser and a Web browser engine read our explainer.

+

You might ask yourself, what does “optimized for embedded devices” mean in practice? Unlike most other WebKit ports, WPE does not +rely on a specific user-interface toolkit, such as Qt, GTK, Cocoa, +etc., nor does it offer any integration with these kinds of toolkits. WPE WebKit is light-weight, integrates well with a +variety of hardware configurations, and only requires a minimum set of APIs on your side: +EGL and OpenGL ES 2.

+

The early days 2014 - 2017

+

The idea for a new WebKit port was born in 2014, as part of a collaboration between Metrological +and Igalia. The goal of this collaboration was to have a WebKit port running efficiently on their set-top boxes, +utilizing a modern Wayland based Linux graphics architecture. Back then, QtWebKit was popular +among embedders; however, it was unmaintained and its future was unclear since Qt wanted to transition +from using WebKit to Blink.

+

In September 2014 a group of Igalians forked the WebKitGtk port, removed all GTK toolkit dependencies, and prototyped what was necessary +to achieve the goal: rendering websites without involving any of the traditional toolkits and instead utilizing a Wayland-based rendering approach.

+

During development it became apparent that this WebKit port is generally useful for all our customers and the community as a whole. +Therefore Igalia decided to aim for an even more flexible design, where Wayland is only one of the possible backends. +Our fellow Igalian Miguel Gomez reported in his late 2016 blog post +about this change, and the renaming of the port: WPE appears for the first time in public.

+

The project’s removal of the Wayland dependency and the subsequent reorganization lead to the architecture we have today, +consisting of not only the WPE port itself but a whole ecosystem of projects such as libwpe, +WPEBackend-fdo, WPEBackend-rdk, etc., +that together form the WPE project.

+

2017 - today

+

After months of focused engineering efforts, the downstream work was finished and Igalia was ready to announce WPE to the +public on the 17th of April 2017, with the promise that Igalia +will maintain the port alongside the existing WebKitGtk port. That is not a cheap bill: maintaining an upstream port is a recurring +multi-million dollar investment. Just in order to keep the port itself healthy, as updates are made all around it, requires infrastructure, +bots and a team of fully dedicated engineers to deal with maintenance, testing, triaging, tickets, etc. To implement new Web standards, fix +related bugs or design and contribute features requires an even more considerable amount of resources.

+

Since then, Igalia ramped up the WPE investments and steadily advanced the port while helping customers to integrate WPE into their +environments. Today WPE is healthy, runs on many platforms, and offers the most flexible browser architecture at present. Also, thanks in great +part to this work, Igalia was responsible for nearly 16.5% of all commits in WebKit itself last year, helping make the larger project +and ecosystem around it healthier too.

+

However, none of this would be possible without the commitment of many Igalians pushing the project forward every day for the past 8 years. +A new People Behind WPE series will be launched soon: over the following months, the Igalians involved with WPE will introduce themselves, their area of +expertise, and talk about a specific WPE related technical topic. You’ll get to know the people behind the product and a first-class technical overview +of individual parts of the WPE architecture! We plan to release a new article every 3-4 weeks, so be sure to visit wpewebkit.org/blog +again soon and enjoy the upcoming People Behind WPE series.

+

Feel free to spread the word and make noise about WPE. Stay healthy, stay tuned!

+ + + +
+
+
+
+
+
+ + Picture of Nikolas Zimmermann + This article was written by Nikolas Zimmermann.

I'm a proud Igalian since 2019 and have been working on WebKits predecessors since the early 2000s, namely kjs/khtml/ksvg, and kdom/kcanvas/ksvg2/khtml2 that all found their way into WebKit. Since that time, Web technology - especially SVG - is my main area of interest. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/02-overview-of-wpe.html b/update-11ty/blog/02-overview-of-wpe.html new file mode 100644 index 000000000..5711a7d18 --- /dev/null +++ b/update-11ty/blog/02-overview-of-wpe.html @@ -0,0 +1,439 @@ + + + + + + + + + + + + An overview of the WPE WebKit project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

An overview of the WPE WebKit project

+ + + + + +
+ +
+ +

In the previous post in this series, +we explained that WPE is a WebKit +port optimized for embedded devices. In this post, we’ll dive into a +more technical overview of the different components of WPE, WebKit, +and how they all fit together. If you’re still wondering what a web +engine is or how WPE came to be, we recommend you to go back to the +first post in the series and then come back here.

+

WebKit architecture in a nutshell

+

To understand what makes WPE special, we first need to have a basic +understanding of the architecture of WebKit itself, and how it ties +together a given architecture/platform and a user-facing web browser.

+

WebKit, the engine, is split into different components that +encapsulate its different parts. At the heart of it is WebCore. As the +name suggests, this contains the core features of the engine +(rendering, layout, platform access, HTML and DOM support, the +graphics layer, etc). However, some of these ultimately depend heavily +on the OS and underlying software platform in order to function. For +example: how do we actually do any I/O on different platforms? How do +we render onscreen? What’s the underlying multimedia platform and how +does it decode media and play it?

+

WebCore handles the multitude of potential answers to these questions +by abstracting the implementation of each component and allowing port +developers to fill the gaps for each supported platforms. For example, +for rendering on Mac, Cocoa APIs implement the graphics APIs +needed. On Linux, this can be done through different implementations +via Wayland, Vulkan, etc. For networking I/O on Mac, the networking +APIs in the Foundation framework are used. On Linux, libsoup fills +that gap, and so on.

+

On the opposite side, for browser implementors to be able to write a +browser using WebKit, an API is needed. WebKit, after all, is a +library. WebKit ports, besides providing the platform support +described above, also provide APIs that suit the target environments: +The Apple ports provide Objective-C APIs (which are then used to write +Safari and the iOS browsers, for instance), while the GTK+ port +provides a GObject-based APIs for Linux (that are used in Epiphany, +the GNOME browser, and other GNOME applications that rely on WebKit to +render HTML). All of these APIs are built on top of an internal, +middle-man, C API that is meant to make it easy for each port to +provide a high-level API for browser developers.

+

With all this in place, it would seem that it shouldn’t be so +difficult for any vendor trying to reuse WebKit in a new platform to +support new hardware and implement a browser, right? All that you need +to do is:

+
    +
  • Implement backends that integrate with your hardware platform: for +multimedia, IO, OS support, networking, graphics, etc.
  • +
  • Write an API that you can use to plug the engine into your browser.
  • +
  • Maintain the changes needed off-tree, that is, outside the source code tree +of WebKit.
  • +
  • Keep your implementation up-to-date with the many changes that happen in the +WebKit codebase on a daily basis, so that you can update WebKit regularly +and take advantage of the many bug fixes, improvements, and new features +that land on WebKit continuously.
  • +
+

Does that sound easy? No, it’s not easy at all! In fact, +implementation of ports in this fashion is strongly discouraged and +vendors who have tried this approach in the past have had to do a huge +effort just to play catch-up with the fast-paced development of +WebKit. This is where WPE comes to the rescue.

+

Simplifying browsers development in the diverse embedded world

+

To simplify the task of porting WebKit to different platforms, Igalia +started working on a platform-agnostic, Linux-based, and full-featured +port of WebKit. This port relies on existing and mature platform +backends for everything that can be easily reused across platforms: +multimedia, networking, and I/O, which are already present in-tree and +are used by Linux ports, like the GTK one. For the areas that are most +likely to require hardware-specific support (that is, graphics and +input), WPE abstracts the implementation so that it can be more easily +provided out of tree, allowing implementors to avoid having to deal +with the WebKit internals more than what’s strictly needed.

+

Additionally, WPE provides a high-level API that can be used to +implement actual browsers. This API is very similar to the WebKitGTK +API, making it easy for developers already familiar with the latter to +start working with WPE. The cog library also serves as a wrapper +around WPE to make it easier still. Once WPE was mature enough, it was +accepted by Apple as an official WebKit port, meaning that the port +lives now in-tree and takes immediate advantage of the many +improvements that land on the WebKit repository on a daily basis.

+

How does WPE integrate with WebKit?

+

A diagram of the WPE WebKit architecture

+

The WPE port has several components. Some are in-tree (that is, are a +part of WebKit itself), while others are out-of-tree. Let’s examine +those components and how they relate to each other, from top to +bottom:

+
    +
  • The Cog library. +While not an integral part of WPE, libcog is a shell library that simplifies +the task of writing a WPE browser from the scratch, by providing common +functionality and helper APIs. This component also includes the cog browser, +a simple WPE browser built on top of libcog that can be used as a reference +or a starting point for the development of a new browser for a specific use +case.
  • +
  • The WPE WebKit API: +the entry point for browser developers to the WebKit engine, provides a +comprehensive GObject/C API. The cog library uses this API extensively and +we recommend relying on it, but for more specific needs and more fine-tuning +of the engine, working directly with the WebKit API can be often necessary. +The API is stable and easy to use, especially, and for those familiar with +the GTK/GNOME platform.
  • +
  • WPE’s WebCore implementation: This part, internal to WebKit, implements +an abstraction of the graphics and input layers of WebKit. This +implementation relies on the libwpe library to provide the functionality +required in an abstract way. Thanks to the architecture of WPE, implementors +don’t need to bother with the complexities of WebCore and WebKit internals.
  • +
  • The libwpe +library. This is an out-of-tree library that provides the API required by +the WPE port in a generic way to implement the graphical and input backends. +Specific functionality for a concrete platform is not provided, but the +library relies on the existence of a backend implementation, as is described +next.
  • +
  • Finally, a WPE backend implementation. This is where all the +platform-specific code lives. Backends are loadable modules that can be +chosen depending on the underlying hardware. These should provide access to +graphics and input depending on the specific architecture, platform, and +operating system requirements. As a reference, WPEBackend-fdo is a +freedesktop.org-based backend, which uses Wayland and freekdesktop.org +technologies, and is +supported for several architectures, including NXP and Broadcom chipsets, like the +Raspberry Pi, and also regular PC architectures, easing testing and +development.
  • +
+

An implementor interested in building a browser in a new architecture +only needs to focus on the development of the last component – a WPE +backend. Having a backend, starting the development of a +WebKit-powered browser is already much easier than it ever was!

+

For a more detailed description of the architecture of WPE and WebKit, +check this article on the architecture of WPE.

+

OK, sounds interesting, how do I get my hands dirty?

+

If you have made it this far, you should give WPE a try!

+

We have listed several on the exploring WPE +page. From there, you will see that depending on how interested you +are in the project, your background, and what you’d like to do with +it, there are different ways!

+

It can be as easy as installing WPE directly from the most popular +Linux distributions. If you want to build WPE yourself, +you can use Yocto and if +you’d like to contribute—that’s very welcome!

+

Happy hacking!

+ + + +
+
+
+
+
+
+ + This article was written by Claudio Saavedra.

Claudio is long-time WebKit contributor from Igalia, working in different areas of WebKit, WPE, and the stack around it. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/03-wpe-graphics-architecture.html b/update-11ty/blog/03-wpe-graphics-architecture.html new file mode 100644 index 000000000..020b97d64 --- /dev/null +++ b/update-11ty/blog/03-wpe-graphics-architecture.html @@ -0,0 +1,386 @@ + + + + + + + + + + + + WPE Graphics architecture + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE Graphics architecture

+ + + + + +
+ +
+ +

Following the previous post in the series about WPE where we talked about the WPE components, this post will explain briefly the WPE graphics architecture, and how the engine is able to render HTML content into the display. If you haven’t read the previous entries in this blog post series about WPE, we recommend you to start with the first post in the series for an introduction, and then come back to this.

+

DOM + CSS = RenderTree

+

As the document is parsed, it will begin building the DOM tree and load-blocking CSS resources. At some point, possibly before the entire DOM tree is built, it’s time to draw things on the screen. The first step to render the content of a page is to perform what’s called the attachment, which is merging the DOM tree with the CSS rules, in order to create the RenderTree. This RenderTree is a collection of RenderObjects, structured into a tree, and each of these RenderObjects represent the elements in the DOM tree that have visual output. RenderObjects have the capability to render the associated DOM tree node into a surface by using the GraphicsContext class (in the case of WPE, this GraphicsContext uses Cairo to perform the rendering).

+

Once the RenderTree is created, the layout is performed, ensuring that each of the RenderObjects have their proper size and position set.

+

Going from source content to displayed content

+

It would be possible to render the content of the web page just traversing this RenderTree and painting each of the RenderObjects, but there would be problems when rendering elements that overlap each other, because the order of the elements in the RenderTree doesn’t necessarily match the order in which they must be painted in order to get the appropriate result. For example, an element with a big z-index value should be painted last, no matter its position in the RenderTree.

+

This is an example of how some HTML content is translated into the RenderTree (there are some RenderObjects missing here that are not relevant for the explanation).

+

RenderTree generated from example HTML

+

RenderLayers

+

In order to ensure that the elements of the RenderTree are rendered in the appropriate order, the concept of RenderLayer is added. A RenderLayer represents a layer in the document containing some elements that have to be rendered at the same depth (even though this is not exactly the case, you can think of each RenderLayer as a group of RenderObjects that are at a certain z-index). Each RenderObject is associated to a RenderLayer either directly or indirectly via an ancestor RenderObject.

+

RenderLayers are grouped into a tree, which is called the RenderLayer tree, and RenderLayer children are sorted into two lists: those that are below the RenderLayer, and those that are above. With this we have a structure that has grouped all the RenderObjects that have to be rendered together: they will be on top of the content that has been rendered by the RenderLayers below this one, and below the content rendered by the RenderLayers over this one.

+

There are several conditions that can decide whether a RenderLayer is needed for some element, it doesn’t necessarily needs to be due to the usage of z-index. It can be required due to transparency, CSS filters, overflow, transformations, and so on.

+

Continuing with the example, these are RenderLayers that we would get for that HTML code:

+

RenderLayer tree generated from example HTML

+

We can see that there are four RenderLayers:

+
    +
  • The root one, corresponding to the RenderView element. This is mandatory.
  • +
  • Another one corresponding to the first RenderBlock.
  • +
  • One corresponding to the RenderVideo element, because video elements always get their own RenderLayer.
  • +
  • One corresponding to the transformed RenderBlock.
  • +
+

RenderLayers have a paint method that is able to paint all the RenderObjects associated to the layer into a GraphicsContext (as mentioned, WPE uses Cairo for this). As in the previous case, it’s possible to paint the content of the page at this point just by traversing the RenderLayer tree and requesting the RenderLayers to paint their content, but in this case the result will be the correct one. Actually this is what WebKitGTK does when it’s run with accelerated compositing disabled.

+

Layer composition

+

While with the previous step we are already able to render the page contents, this approach is not very efficient, especially when the page contains animations, elements with transparency, etc. This is because in order to paint a single pixel, all the RenderLayers need to be traversed, and those that are contributing to that pixel need to be repainted (totally or partially), even if the content of those RenderLayers hasn’t changed. For example, think about an animation that’s moving an element. For each frame of that animation, the animated element needs to be repainted, but the area that was covered by the animated element in the last frame needs to be repainted as well. The same happens if there’s a translucent element on top of other content. If the translucent element changes, it needs to be repainted, but the content below the translucent element needs to be repainted as well because the blend needs to be performed again.

+

This would be much more efficient if the content that doesn’t change was somehow separated from the content that’s changing, and we could render those two types of content separately. This is where the composition stage comes into action.

+

The idea here is that we’re going to paint the RenderLayer contents into intermediate buffers, and then compose those buffers one on top of the other to get the final result. This last step is what we call composition. And it fixes the problems we mentioned with animations of transparency: animations don’t require repainting several RenderLayers. Actually moving an element just means painting one buffer with an offset during the composition. And for transparency, we just need to perform the new blending of the two buffers during the composition, but the RenderLayers of the content below the translucent element don’t need to be repainted.

+

Once we have the RenderLayer tree, we could just paint each RenderLayer in its own buffer in order to perform the composition. But this would be a waste of memory, as not every RenderLayer needs a buffer. We introduce here a new component, the GraphicsLayer.

+

GraphicsLayers are a structure used to group those RenderLayers that will render into the same buffer, and it will also contain all the information required to perform the composition of these buffers. A RenderLayer may have a GraphicsLayer associated to it if it requires its own buffer to render. Otherwise, it will render into an ancestor’s buffer (specifically, the first ancestor that has a GraphicsLayer). As usual, GraphicsLayers are structured into a tree.

+

This is how the example code would be translated into GraphicsLayers.

+

GraphicsLayer tree generated from example HTML

+

We can see that we have now three GraphicsLayers:

+
    +
  • The root one, which is mandatory. It belongs to the RenderView element, but the first RenderBlock will render into this GraphicsLayer's buffer as well.
  • +
  • The one for the RenderVideo element, as videos are updated independently from the rest of the content.
  • +
  • The one for the transformed element, as the transformed elements are updated independently from the rest of the content.
  • +
+

With this structure, now we can render the intermediate buffers of the RenderView and the transformed RenderBlock, and we don’t need to update them any more. For each frame, those buffers will be composited together with the RenderVideo buffer. This RenderVideo will be updating its buffer whenever a new video frame arrives, but it won’t affect the content of the other GraphicsLayers.

+

So now we have successfully separated the content that is changing and needs to be updated from the content that remains constant and doesn’t need to be repainted anymore, just composited.

+

Accelerated compositing and threaded accelerated compositing

+

There’s something else that be done in order to increase the rendering performance, and it’s using the GPU to perform the composition. The GPU is highly optimized to perform operations like the buffer composition that we need to do, as well as handle 3D transforms, blending, etc. We just need to upload the buffers into textures and let the GPU handle the required operations. WPE does this though the usage of the EGL and GLES2 graphics APIs. In order to perform the composition, EGL is used to create a GLES2 EGLContext. Using that context, the intermediate buffers are uploaded to textures, and then those textures are positioned and composited according to their appropriate positions. This leverages the GPU for the composition work, leaving the CPU free to perform other tasks.

+

This is why this step is called accelerated compositing.

+

But there’s more.

+

Until this point, all the steps that are needed to render the content of the page are performed in the main thread. This means that while the main thread is rendering and compositing, it’s not able to perform other tasks, like run JS code.

+

WPE improves this by using a parallel thread whose only mission is to perform the composition. You can think of it as a thread that runs a loop that composites the incoming buffers using the GPU when there’s content to render. This is what we call threaded accelerated compositing.

+

This is specially useful when there’s a video or an animation running on the page:

+
    +
  • +

    If there’s a video running in the page, in the non-threaded case, for each video frame the main thread would need to get the frame and perform the composition with the rest of the page content. In the threaded case, the video element delivers the frames directly to the compositor thread, and requests a composition to be done, without the main thread being involved at all.

    +
  • +
  • +

    If there’s an animation, in the non-threaded case, for each frame of the animation the main thread would need to calculate the animation step and then perform the composition of the animated element with the rest of the page content. In the threaded case, the animation is passed to the compositor thread, and the animation steps are calculated on that thread, triggering a composition when needed. The main thread doesn’t need to do anything besides starting the animation.

    +
  • +
+

It would take another post to explain in detail how the threaded accelerated composition is implemented on WPE, but if you’re curious about it, know that WPE uses an specialization of the GraphicsLayer called CoordinatedGraphicsLayer in order to implement this. You can use that as an starting point.

+

So this is the whole process that’s performed in WPE in order to display the content of a page. We hope it’s useful!

+

Future

+

At Igalia we’re constantly evolving and improving WPE, and have ongoing efforts to improve the graphics architecture as well. Besides small optimizations and refactors here and there, the most important goals that we have are:

+
    +
  • +

    Add a GPU process. Currently the EGL and GLES2 operations are performed in the web process. As there can be several web processes running when several pages are open, this means the browser can be using a lot of EGL contexts in total, which is a waste of memory. Also, all these processes could potentially be affected by errors, leaks, etc., in the code that handles the GPU operations. The idea is to centralize all the GPU operations into a single process, the GPU one, so all the web processes will issue paint requests to the GPU process instead of painting their content themselves. This will reduce the memory usage and improve the software’s robustness.

    +
  • +
  • +

    Remove CPU rasterization and paint all the content with GLES2. Using the CPU to paint the layer contents with cairo is expensive, especially in platforms with slow CPUs, as embedded devices sometimes do. Our goal here is to completely remove the cairo rasterization and use GLES2 calls to render the 2D primitives. This will greatly improve the rendering performance.

    +
  • +
  • +

    Use ANGLE to perform WebGL operations. WPE currently implements the WebGL 1.0 specification through direct calls to GLES2 methods. We are changing this in order to perform the operations using ANGLE, which will allow WPE to support the WebGL 2.0 specification as well.

    +
  • +
+

But what about the backends?

+

In the previous post there was a mention of backends that are used to integrate with the underlying platform. How is this relevant to the graphics architecture?

+

Backends have several missions when it comes to communicate with the platform, but regarding graphics, they have two functions to achieve:

+
    +
  • +

    Provide a platform dependent surface that WPE will render to. This can be a normal buffer, a Wayland buffer, a native window, or whatever, as long as the system EGL implementation allows creating an EGLContext to render to it.

    +
  • +
  • +

    Process WPE indications that a new frame has been rendered, performing whatever tasks are necessary to take that frame to the display. Also notify WPE when that frame was been displayed.

    +
  • +
+

The most common example of this is a Wayland backend, which provides a buffer to WPE for rendering. When WPE has finished rendering the content, it notifies the backend, which sends the buffer to the Wayland compositor, and notifies back to WPE when the frame has been displayed.

+

So, whatever platform you want to run WPE on, you need to have a backend providing at least these capabilities.

+

Final thoughts

+

This was a brief overview of how WPE rendering works, and also what are the major improvements we’re trying to achieve at Igalia. We’re constantly putting in a lot of work to keep WPE the best web engine available for embedded devices.

+

If this post got you interested in collaborating with WPE development, or you are in need of a web engine to run on your embedded device, feel free to contact us. We’ll be pleased to help!

+

We also have open positions at the WebKit team at Igalia. If you’re motivated by this field and you’re interested in developing your career around it, you can apply here!

+ + + +
+
+
+
+
+
+ + This article was written by Miguel Gómez.

Miguel has been contributing to WebKit and WPE for almost ten years now, focusing specially on the graphics part of the code. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/04-wpe-networking-overview.html b/update-11ty/blog/04-wpe-networking-overview.html new file mode 100644 index 000000000..33054c06b --- /dev/null +++ b/update-11ty/blog/04-wpe-networking-overview.html @@ -0,0 +1,344 @@ + + + + + + + + + + + + WPE Networking Overview + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE Networking Overview

+ + + + + +
+ +
+ +

At the heart of any browser engine is networking: Connecting with services and other users. Unlike other engines, WebKit approaches this more abstractly by leaving a large portion of the networking up to individual ports. This includes network protocols such as HTTP, WebSockets, and WebRTC. The upside to this approach is a higher level of integration with the system-provided libraries and features so WebKit will behave similarly to other software on the platform often with more centralized configuration.

+

Due to this abstraction there are a few independent layers that make up the networking stack of WPE. In this post, I’ll break down what each layer accomplishes as well as give some insight into the codebase’s structure.

+

Networking Layers

+
+ WebKit Network Layers +
+

WebKit

+

Before we get into the libraries used for WPE, let’s discuss WebKit itself. Despite abstracting out a lot of the protocol handling, WebKit itself still needs to understand a lot of fundamentals of HTTP.

+

WebCore (discussed in WPE Overview) understands HTTP requests, headers, and cookies, as they are required to implement many higher-level features. What it does not do is the network operations, most parsing, or on-disk storage. In the codebase, these are represented by ResourceRequest and ResourceResponse objects, which map to general HTTP functionality.

+

NetworkProcess

+

A core part of modern web engine security is the multi-process model. In order to defend against exploits, each website runs in its own isolated process that does not have network access. In order to allow for network access, they must talk over IPC to a dedicated NetworkProcess, typically one per browser instance. The NetworkProcess receives a ResourceRequest, creates a NetworkDataTask with it to download the data, and responds with a ResourceResponse to the WebProcess which looks like this:

+
+ WebKit Network Flowchart +
+

WPE

+

WPE implements the platform-specific versions of the classes above as ResourceRequestSoup and NetworkDataTaskSoup, primarily using a library called libsoup.

+

The libsoup library was originally created for the GNOME project’s email client and has since grown to be a very featureful HTTP implementation, now maintained by Igalia.

+

At a high level, the main task that libsoup does is manage connections and queued requests to websites and then efficiently streams the responses back to WPE. Properly implementing HTTP is a fairly large task, and this is a non-exhaustive list of features it implements: HTTP/1.1, HTTP/2, WebSockets, cookies, decompression, multiple authentication standards, HSTS, and HTTP proxies.

+

On its own, libsoup is really focused on the HTTP layer and uses the GLib library to implement many of its networking features in a portable way. This is where TCP, DNS, and TLS are handled. It is also directly used by WebKit for URI parsing and DNS pre-caching.

+

Using GLib also helps standardize behavior across modern Linux systems. It allows configuration of a global proxy resolver that WebKit, along with other applications, can use.

+

TLS

+

Another unique detail of our stack is that TLS is fully abstracted inside of GLib by a project called GLib-Networking. This project provides multiple implementations of TLS that can be chosen at runtime, including OpenSSL and gnutls on Linux. The benefit here is that clients can choose the implementation they prefer—whether for licensing, certification, or technical reasons.

+

Usage

+

Let’s go step by step to see some real world usage. If we call webkit_web_view_load_uri() for a new domain it will:

+
    +
  1. Create a ResourceRequest in WebCore that represents an HTTP request with a few basic headers set. +
      +
    • ResourceRequestSoup will create its own internal representation for the request using soup_message_new_for_uri().
    • +
    +
  2. +
  3. This is passed to the NetworkProcess to load this request as a NetworkDataTask.
  4. +
  5. NetworkDataTaskSoup will send/receive the request/response with soup_session_send() which queues the message to be sent.
  6. +
  7. libsoup will connect to the host using GSocketClient which does a DNS lookup and TCP connection. +
      +
    • If this is a TLS connection GTlsClientConnection will use a library such as gnutls to do a TLS handshake.
    • +
    +
  8. +
  9. libsoup will write the HTTP request and read from the socket parsing the HTTP responses eventually returning the data to WebKit.
  10. +
  11. WebKit receives this data, along with periodic updates about the state of the request, and sends it out of the NetworkProcess back to the main process as a ResourceResponse eventually loading the data in the WebProcess.
  12. +
+

Summary

+

In conclusion, WebKit provides a very flexible abstraction for platforms, and WPE leverages mature system libraries to provide a portable implementation. It has many layers, but they are all well organized and suited to their tasks.

+

If you are working with WPE and are interested in collaborating, feel free to contact us. If you are interested in working with Igalia, you can apply here.

+ + + +
+
+
+
+
+
+ + This article was written by Patrick Griffis.

Patrick has been contributing to WebKit since 2018 and does work around networking, security, and the platform libraries WPE uses. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/04-wpe-qa-tooling.html b/update-11ty/blog/04-wpe-qa-tooling.html new file mode 100644 index 000000000..e97d1de58 --- /dev/null +++ b/update-11ty/blog/04-wpe-qa-tooling.html @@ -0,0 +1,372 @@ + + + + + + + + + + + + WPE QA and tooling + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE QA and tooling

+ + + + + +
+ +
+ +

In the previous posts, my colleagues Claudio and Miguel wrote respectively about the major components of the project and, specifically, the graphics architecture of WPE. Today, you’ll see our efforts to improve the quality of both WPE and the experience of working and using it. While the previous entries in this blog post series about WPE aren’t necessarily required in order to read this one, we recommend you to starting with the first post in the series.

+

Automated testing

+

Testing is an essential part of the WebKit project, primarily due to the large number of use cases covered by HTML/CSS/Javascript specifications and the need for the project to work correctly in a wide range of configurations.

+

As an official port of WebKit, WPE uses the former’s testing infrastructure, based on BuildBot. There are two primary servers, one working as an early warning system by testing the patches before they’re committed to the main repository, and another for more extensive testing after accepting the incoming changes.

+
+build.webkit.org screenshot +
+
+

Currently, the WPE testing bots target debug and release configurations using the Flatpak SDK (more on it later in this article) on 64bit Intel-based Linux Debian systems. We have plans of adding bots running on Raspberry Pi boards in the future. Alongside nightly testing, we keep builder bots covering the Ubuntu LTS/Debian versions we support. After August 14th, 2022, the earliest supported versions will be Ubuntu 20.04 LTS and Debian 11 (Bullseye).

+

Test suites

+

Initially, the WPE builder bots build WPE in both release and debug configurations and feed the built packages into the tester bots, which run some test suites according to their configuration, each suite focused in one aspect of the project:

+
    +
  • Layout tests: The main suite tests whether WebKit correctly renders web pages and its implementation of web APIs. This suite comprises both WebKit’s test cases and the imported tests from Web Platform Test. At the time of writing, it runs over 50,000 test cases.
  • +
  • API Tests: This suite tests the API provided to developers by WebKit and its ports. For example, this step tests the WPE API used in Cog.
  • +
  • Javascriptcore tests: Covers the JavascriptCore engine, running WebKit’s tests alongside test262, the reference test suite for JS/ECMAScript implementations.
  • +
  • WebDriver: Tests from Selenium and W3C WebDriver APIs for browser automation.
  • +
  • Other small suites: Tests for WebKit’s tooling components.
  • +
+

Due to a large number of tests and the fast development of both WebKit and the specifications—it’s not uncommon to have dozens of daily commits touching dozens of tests—it’s hard to keep the testing bots green.

+

For example, while we try to make the tests work on all platforms, many old layout tests use the -expected.txt scheme, where the render tree is printed in a textual format with the text sized in pixels for every node. While this works fine in most cases, many tests have minor differences between the expected result in the Mac platform and the WPE/GTK platform. One of the causes is the font rendering particularities of each port.

+

Thankfully, this situation improved significantly since the beginning of the project. Among the efforts, many tests are now using a “reference” HTML file, which are HTML files that render to the same expected result as the test case, so both the test case and the reference will use the same font rendering scheme and can be compared pixel by pixel.

+

Building and running WPE

+

This section focuses on the experience of building and running WPE in a regular Linux x86–64 system. In a future post, we’ll cover building for and running on embedded devices.

+

Checking out the code

+

Recently, WebKit moved to GitHub, so you can clone it directly from there:

+
$ mkdir ~/dev
+$ git clone https://github.com/WebKit/WebKit.git
+
+

Note: Due to the size of the project history, you might want to use --depth=1 to clone a single revision, followed by git pull --unshallow from inside the cloned repository to fetch the history if needed.

+

There’s more information in WebKit’s GitHub wiki about setting up the git checkout for contributing code back to WebKit. It’ll set up some git hooks to do some tasks required by the project, like formatting the commit message and automatically linking the pull request to the Bugzilla issue.

+

All commands in the following sections are run from inside the cloned repository.

+

Updating the dependencies (aka The WebKit Flatpak SDK)

+

Like most complex software projects, WebKit has a reasonably extensive list of dependencies. Keeping a reference set of their versions frozen during development is desirable to make it easier to reproduce bugs and test results. In older times, WPE and WebKitGTK used JHBuild to freeze a set of dependencies. While this worked for a long time, it did not cover all dependencies. Sometimes, there could be minor differences in the layout tests between the reference test bots and the developer machine due to some dependency resolved by the host system outside JHBuild.

+

To improve reproducibility, since 2020, WPE and WebKitGTK have been using an SDK based on Flatpak (kudos to my colleagues Thibault Saunier and Philippe Normand), with a much more extensive dependency coverage and isolation from the host system. Alongside the dependencies, it ships some tools like rr and supports tools like clangd. Almost all bots enable this SDK, the exception being the LTS/Stable bots; as in the latter, we want to build with the already available packages in each distribution.

+
$ ./Tools/Scripts/update-webkit-flatpak
+
+

The command will set up the local flatpak repository at ./WebKitBuild/UserFlatpak with the downloaded SDK and create some bundled icecc toolchains. This enables distributed builds in local networks…

+

Building

+

Once the SDK download finishes, you can use the helper script ./Tools/Scripts/build-webkit, which wraps the cmake command with some pre-set options commonly used in normal development, like enabling developer-only features usually disabled in regular builds. Manually invoking cmake is possible, although usually only when you want more control over the build. To build WPE in release mode, use:

+
$ ./Tools/Scripts/build-webkit --release --wpe
+
+

Optionally, you can pass it multiple arguments to be fed directly to make or cmake with the switches --makeargs=... and --cmakeargs=..., respectively. For example, --makeargs="-j8" will limit make to 8 parallel jobs and --cmakeargs="-DENABLE_GAMEPAD=1" will enable gamepad support (requires libmanette, bundled in the SDK).

+

The first build might take a while (up to almost one hour in a regular laptop). Fortunately, the SDK uses ccache to avoid recompiling the same object files, so subsequent builds without significant changes usually are faster. For more info on speeding the build, check the wiki.

+

Running the browser (Cog)

+

To run Cog, the reference WPE browser, you need a Wayland server, which is common in most Linux systems nowadays.

+
$ ./Tools/Scripts/run-minibrowser --wpe --release https://wpewebkit.org/
+
+
+Cog with GTK4 shell screenshot +
+
+

Running some tests

+

To run the API tests, which reside in Tools/TestWebKitAPI/Tests/, you can use the following command:

+
$ ./Tools/Scripts/run-wpe-tests --release --display-server=headless
+
+

Other test suites:

+
    +
  • Layout tests: ./Tools/Scripts/run-wpe-tests
  • +
  • JSC tests: ./Tools/Scripts/run-javascriptcore-tests
  • +
  • WebDriver: ./Tools/Scripts/run-webdriver-tests
  • +
+

As stated when we described the test suites, the main challenge in testing is keeping up with the fast pace of development, as it’s not uncommon to have some revisions updating hundreds of tests.

+

Contributing code to WPE

+

After hacking locally, you can submit your changes following the workflow listed in the WebKit wiki.

+

Testing WPE in the wild

+

If you don’t want to build your WPE build or image, there are some options to get a taste of WPE listed on our website.

+

Final thoughts

+

With this, we conclude this brief overview of WPE automated testing and the main tools we use in our daily work with WPE. In future posts in this area we’ll go deeper in other subjects like testing on embedded boards and debugging practices.

+

If this post got you interested in collaborating with WPE development, or you are in need of a web engine to run on your embedded device, feel free to contact us. We’ll be pleased to help!

+

We also have open positions at the WebKit team at Igalia. If you’re motivated by this field and you’re interested in developing your career around it, you can apply here!

+ + + +
+
+
+
+
+
+ + This article was written by Lauro Moura.

Lauro is webkit contributor from Igalia, working mainly on QA. +
+
+
+
+
+
+ + + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/05-new-svg-engine.html b/update-11ty/blog/05-new-svg-engine.html new file mode 100644 index 000000000..d504884b0 --- /dev/null +++ b/update-11ty/blog/05-new-svg-engine.html @@ -0,0 +1,430 @@ + + + + + + + + + + + + Status of the new SVG engine in WebKit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Status of the new SVG engine in WebKit

+ + + + + +
+ +
+ + +

In the previous posts of this series, various aspects of the WPE port architecture were covered. Besides maintaining and advancing the WPE port according to our customers’ needs, Igalia also participates in the development of the WebCore engine itself, which is shared by all WebKit ports. WebCore is the part of the browser engine that does the heavy lifting: it contains all functionality necessary to load, parse, lay out, and paint Web content.

+

Since late 2019, Igalia has been working on a new SVG engine, dubbed Layer-Based SVG Engine (LBSE), that will unify the HTML/SVG rendering pipelines in WebCore. This will resolve long-standing design issues of the “legacy” SVG engine and unlock a bunch of new exciting possibilities for Web developers to get creative with SVG. Hardware-accelerated compositing, driven by CSS transform animations, 3D perspective transformations for arbitrary SVG elements, CSS z-index support for all SVG graphics elements, and proper coverage rectangle computations and repaints are just a few highlights of the capabilities the future SVG engine will offer.

+

In this article, an overview is given about the problems that LBSE aims to solve, and the importance of a performant, well-integrated SVG engine especially for the embedded market. Finally, the current upstreaming status is summarized including an outlook for the year 2023.

+

LBSE in a nutshell

+

Before diving into the technical topics, let’s take a few minutes to recap the motivations behind the LBSE work, and explain the importance of a well-integrated, performant SVG engine in WebKit, especially for the embedded market.

+

Motivation

+

Many of our customers build products that utilize a Linux-powered embedded device, typically using non-x86 CPUs, custom displays with built-in input capabilities (e.g., capacitive touchscreens) often without large amounts of memory or even permanent storage. The software stack for these devices usually consists of a device-specific Linux distribution, containing the proprietary network, GPU, and drivers for the embedded device - the vendor-approved “reference distribution”.

+

No matter what type of product is built nowadays, many of them need an active Internet connection, to e.g. update their software stack and access additional information. Besides the UI needed to control the product, a lot of additional dialogs, wizards and menus have to be provided to be able to alter the devices’ “system settings”, such as date/time information, time zones, display brightness, WiFi credentials, Bluetooth settings, and so on.

+

A variety of toolkits exist that assist in writing GUI applications for embedded devices, with a few open-source projects on the market, as well as commercial products providing closed-source, proprietary solutions, that specifically target the embedded market and are often optimized for specific target device families, e.g. certain ARM processors / certain GPUs.

+

If the need arises, not only to communicate with the Internet but also to display arbitrary Web content, WPE comes into play. As presented in the first post in this series, the flexible and modular WPE architecture makes it an ideal choice for any product in the embedded market that needs Web browsing abilities. The GLib/C-based WPE public APIs allow for customization of the browsing engine and its settings (react on page load/input events, inject custom JS objects, modify style sheets, etc.) and allow the embedder to control/monitor all relevant Web browsing-related activities.

+

With a full-fledged Web engine at hand, one might ponder if it is feasible to replace the whole native GUI stack with a set of Web pages/applications, and only use WPE to paint the UI in full-screen mode, thus migrating away from native GUI applications — following the trend in the desktop market. The number of organizations migrating native GUI applications into Web applications is rapidly increasing, since there are compelling reasons for Web apps: “write once, use everywhere”, avoiding vendor lock-in, easy/reliable deployment and update mechanisms, and efficient test/development cycles (local in-browser testing!).

+

Due to the sheer capabilities of the Web platform, it has grown to an environment in which any kind of application can be developed – ranging from video editing applications, big data processing pipelines to 3D games, all using JS/WebAssembly in a browser, presented using HTML5/CSS. And as an important bonus: in 2023, it’s much easier to find and attract talented Web developers and designers that are fluent in HTML/CSS/JS, than those that are comfortable designing UI applications in proprietary, closed-source C/C++ frameworks.

+

A long-term customer, successfully using WPE in their products, had very similar thoughts and carried out a study, contracting external Web designers to build a complete UI prototype using Web technology. The mock-up made extensive use of SVG2, embedded inline into HTML5 documents or via other mechanisms (CSS background-image, etc.). The UI fulfilled all expectations and worked great in Blink and WebKit-based browsers, delivering smooth animations. On the target device, however, the performance was too slow, far away from usable. A thorough analysis revealed that large parts of the Web page were constantly repainted, and layout operations were repeated for every frame when animations were active. The accumulated time to display a new frame during animations was in the order of a few milliseconds on desktop machines, but took 20-25 milliseconds on the target device, making smooth 60 FPS animations impossible.

+

The poor performance is not the result of shortcomings in the WPE port of WebKit: when replacing the aforementioned animated SVG document fragments with HTML/CSS “equivalents” (e.g. simulating SVG circles with CSS border-radius tricks) the performance issue vanisheed. Why? SVG lacks support for a key feature called accelerated compositing, which has been available for HTML/CSS since its introduction more than a decade ago. This compositing heavily relies on the Layer Tree, which is unaware of SVG. Extending the Layer Tree implementation to account for SVG is the main motivation for LBSE.

+

If you are unfamiliar with the concepts of Render Tree and Layer Tree, you might want to read the “Key concepts” section of an earlier LBSE design document, which provides an overview of the topic.

+

Prototyping

+

The LBSE effort began in October 2019 as a research project, to find out an ideal design for the SVG Render Tree, that allows SVG to re-use the existing Layer Tree implementation with minimal changes. The aim for LBSE is to share as much code as possible with the HTML/CSS implementation, removing the need for things like SVG specific clipping/masking/filter code and disjoint HTML counterparts for the same operations.

+

After an extensive phase of experimentation, two abandoned approaches, and a long time spent on regression fixing, the LBSE prototype was finally finished after almost two years of work. It passed all 60k+ WebKit layout tests and offered initial support for compositing, 3D transformations, z-index, and more. The intent was to prove that we can reach feature parity with the legacy SVG engine and retrieve the very same visual results, pixel-by-pixel (except for progressions of LBSE). Shortly after the finalization, the prototype was presented during the WebKit contributors meeting in 2021.

+

As the name “prototype” indicates, LBSE was not ready for integration into WebKit at this point. It replaced the old SVG engine with a new one, resulting in a monolithic patch exceeding 650 KB of code changes. External contributions generally demand small patches, with ChangeLogs, tests, etc. – no conscientious reviewer in any company would approve a patch replacing a core component of a browser engine in one shot. Splitting up into small pieces is also not going to work, since SVG needs to be kept intact upstream all the time. Duplicating the whole SVG engine? Not practicable either. With that problem in mind, a fruitful discussion took place with Apple during and after the WebKit contributors meeting: a realistic upstreaming strategy was defined - thanks Simon Fraser for suggesting a pragmatic approach!

+

The idea is simple: bootstrap LBSE in parallel to the legacy SVG engine. Upstream LBSE behind a compile-time flag and additionally a runtime setting. This way the LBSE code is compiled by the EWS bots during upstreaming (rules out bit-rot) and we gain the ability to turn LBSE on, selectively, from our layout tests – very useful during early bootstrapping. For WebKit, that strategy is the best – for LBSE another major effort is necessary: moving from a drop-in replacement approach to a dual-stack SVG engine: LBSE + legacy built into the same WebKit binaries. At least the timing was good since a split-up into small pieces was needed anyhow for upstreaming. Time to dissect the huge branch into logical, atomic pieces with proper change logs.

+

Before we jump to the upstreaming status, one question should be answered, that came up during the WebKit contributors meeting and also during various discussions: why don’t you just fix the existing SVG engine and instead propose a new one - isn’t that too risky for Web compatibility?

+

Why don’t you fix the existing SVG engine?

+LBSE logo +

There was no initial intention to come up with a new SVG engine. During LBSE development it became apparent how much SVG-specific code can be erased when unifying certain aspects with HTML/CSS. After carrying out the integration work, layout/painting and hit-testing work fundamentally different than before. Since that time, LBSE is labeled as a “new SVG engine”, even though the SVG DOM tree part remained almost identical. Web compatibility will improve with LBSE: a few long-standing, critical interop issues with other browser vendors are solved in LBSE. Therefore, there are no concerns regarding Web compatibility risks from our side.

+

To answer the initial question, whether it is possible to fix the existing SVG engine to add layer support without adding a “new” SVG engine in parallel? Short answer: no.

+

In the following section, it is shown that adding support for layers implies changing the class hierarchy of the SVG render tree. All SVG renderers need to inherit from RenderLayerModelObject – a change like this cannot be split up easily into small, atomic pieces. Improving the design is difficult if there’s a requirement to keep the SVG engine working all the time upstream: all patches in that direction end up being large as many renderers have to be changed at the same time. Having distinct, LBSE-only implementations of SVG renderers, independent of the legacy engine, leaves a lot of freedom to strive for an optimal design, free of legacy constraints, and avoids huge patches that are impossible to review.

+

Let’s close the introduction and review the upstreaming status, and discuss where we stand today.

+

Upstreaming progress

+

Planning

+

To unify the HTML/CSS and SVG rendering pipelines there are two possible paths to choose from: teach the Layer Tree about the SVG Render Tree and its rendering model, or vice-versa. For the latter path, the HTML/CSS-specific RenderLayer needs to split into HTML/SVG subclasses and a base class, that is constructible from non-RenderLayerModelObject-derived renderers. The layer management code currently in RenderLayerModelObject would need to move into another place, and so forth. This invasive approach can potentially break lots of things. Besides that danger, many places in the layer/compositing system would need subtle changes to account for the specific needs of SVG (e.g. different coordinate system origin/convention).

+

Therefore the former route was chosen, which requires transforming the SVG render tree class hierarchy, such that all renderers that need to manage layers derive from RenderLayerModelObject. Using this approach support, for SVG can be added to the layer/compositing system in a non-invasive manner, with only a minimum of SVG-specific changes. The following class hierarchy diagrams illustrate the planned changes.

+
+
+
Legacy design (click to enlarge)
+Visualization of the legacy SVG render tree class hierarchy in WebCore +
+
+
LBSE design (click to enlarge)
+Visualization of the LBSE SVG render tree class hierarchy in WebCore +
+
+

The first graph shows the class hierarchy of the render tree in the legacy SVG engine: RenderObject is the base class for all nodes in the render tree. RenderBoxModelObject is the common base class for all HTML/CSS renderers. It inherits from RenderLayerModelObject, potentially allowing HTML renderers to create layers. For the SVG part of the render tree, there is no common base class shared by all the SVG renderers, for historical reasons.

+

The second graph shows only the SVG renderers of the LBSE class hierarchy. In that design, all relevant SVG renderers may create/destroy/manage layers, via RenderLayerModelObject. More information regarding the challenges can be found in the earlier LBSE design document.

+

Report

+

The upstreaming work started in December 2021, with the introduction of a new layer-aware root renderer for the SVG render subtree: RenderSVGRoot. The existing RenderSVGRoot class was renamed to LegacyRenderSVGRoot (as well as any files, comments, etc.) and all call sites and build systems were adapted. Afterward, a stub implementation of a layer-aware RenderSVGRoot class was added and assured that the new renderer is created for the corresponding SVG DOM element if LBSE is activated.

+

That process needs to be repeated for all SVG renderers that have substantially changed in LBSE and thus deserve an LBSE-specific upstream implementation. For all other cases, in-file #if ENABLE(LAYER_BASED_SVG_ENGINE) ... #endif blocks will be used to encapsulate LBSE-specific behavior. For example, RenderSVGText / RenderSVGInlineText are almost identical in LBSE downstream, compared to their legacy variants; thus, they are going to share the renderer implementation between the legacy SVG engine and LBSE.

+

The multi-step procedure was repeated for RenderSVGModelObject (the base class for SVG graphics primitives), RenderSVGShape, RenderSVGRect, and RenderSVGContainer. Core functionality such as laying out children of a container, previously hidden in SVGRenderSupport::layoutChildren() in the legacy SVG engine, now lives in a dedicated class: SVGContainerLayout. Computing the various SVG bounding boxes - object/stroke/decorated bounding box - is precisely specified in SVG2 and got a dedicated implementation as the SVGBoundingBoxComputation class, instead of fragmenting the algorithms all over the SVG render tree as in the legacy SVG engine.

+

By February 2022, enough functionality was in place to construct the LBSE render tree for basic SVG documents, utilizing nested containers and rectangles as leaves. While this doesn’t sound exciting at all, it provided an ideal environment to implement support for SVG in the RenderLayer-related code - before converting all SVG renderers to LBSE, and before implementing painting in the SVG renderers.

+

Both RenderLayer and RenderLayerBacking query CSS geometry information such as border box, padding box, or content box from their associated renderer, which is expected to be a RenderBox in many places. This is incorrect for SVG: RenderSVGModelObject inherits from RenderLayerModelObject, but not from RenderBox since it doesn’t adhere to the CSS box model. Various call sites cast the associated renderer to RenderBox to call e.g. borderBoxRect() to retrieve the border box rectangle. There are similar accessors in SVG to query the geometry, but there is no equivalent of a border box or other CSS concetps in SVG. Therefore, we extended RenderSVGModelObject to provide a CSS box model view of an SVG renderer, by offering methods such as borderBoxRectEquivalent() or visualOverflowRectEquivalent() that return geometry information in the same coordinate system using the same conventions as their HTML/CSS counterparts.

+

We also refactored RenderLayer to use a proxy method - rendererBorderBoxRect() - that provides access to the borderBoxRect() for HTML and the borderBoxRectEquivalent() for SVG renderers, and the same fix to RenderLayerBacking. With these fixes in place, support to position and size SVG layers and to compute overflow information could be added – both pre-conditions to enable painting.

+

By March 2022, LBSE was able to paint basic SVG documents - a major milestone for the bootstrapping process, demonstrating that the layer painting code was functional for SVG. It was time to move on to transformations: implementing RenderSVGTransformableContainer (e.g. <g> elements with a non-identity transform attribute or CSS transform property) and CSS/SVG transform support for all other graphics primitives, utilizing the RenderLayer-based CSS Transform implementation. As preparation, the existing code was reviewed and cleaned up: transform-origin computation was decoupled from CTM computation (CTM = current transformation matrix, see CSS Transforms Module Level 1) and transform-box computations were unified in a single place.

+

In April 2022, 2D transforms were enabled and became fully functional a few weeks later. Besides missing compositing support upstream, downstream work showed that enabling 3D transforms for SVG required fixing a decade-old bug that made the computed perspective transformation dependent on the choice of transform-origin. That became apparent when testing the layer code with SVG, which uses different default values for certain transform-related CSS properties than HTML does: transform-box: view-box and transform-origin: 0 0 are the relevant defaults for SVG, referring to the top-left corner of nearest SVG viewport vs. the center of the element in HTML.

+

By May 2022, the legacy SVG text rendering code was altered to be usable for LBSE as well. At this point, it made sense to run layout tests using LBSE. Previously most tests were expected to fail, as most either utilize text, paths, or shapes, and sometimes all three together. LBSE render tree text dumps (dumping the parsed render tree structure in a text file) were added for all tests in the LayoutTests/svg subdirectory, as well as a new pixel test baseline (screenshots of the rendering as PNGs), generated using the legacy SVG engine, to verify that LBSE produces pixel-accurate results. All upcoming LBSE patches are expected to change the expected layout test result baseline, and/or the TestExpectations file, depending on the type of patch. This will ease the reviewing process a lot for future patches.

+

To further proceed, a test-driven approach was used to prioritize the implementation of the missing functionality. At that time, missing viewBox support for outer <svg> elements was causing many broken tests. The effect of the transformation induced by the viewBox attribute, specified on outer <svg> elements, cannot be implemented as an additional CSS transformation applied to the outermost <svg> element, as that would affect the painted dimensions of the SVG document, which are subject to the CSS width/height properties and the size negotiation logic only. The viewBox attribute is supposed to only affect the visual appearance of the descendants, by establishing a new local coordinate system for them. The legacy SVG engine manually handled the viewBox-induced transformation in various places throughout LegacyRenderSVGRoot, to only affect the painting of the descendants and not e.g. the position/dimension of the border surrounding the <svg>, if the CSS border property is specified. In LBSE, transformations are handled on RenderLayer-level and not in the renderers anymore.

+

By July 2022, after testing different approaches, a proper solution to add viewBox support was upstreamed. The chosen solution makes use of another CSS concept that arises in the context of generated content: “anonymous boxes”. The idea is to wrap the direct descendants of RenderSVGRoot in an anonymous RenderSVGViewportContainer (“anonymous” = no associated DOM element) and apply the viewBox transformation as a regular CSS transformation on the anonymous renderer. With that approach, LBSE is left with just a single, unified viewBox implementation, without error-prone special cases in RenderSVGRoot, unlike the legacy SVG engine which has two disjoint implementations in LegacyRenderSVGViewportContainer and LegacyRenderSVGRoot.

+

After the summer holidays, in August 2022, the next major milestone was reached: enabling compositing support for arbitrary SVG elements, bringing z-index support, hardware-accelerated compositing and 3D transforms to SVG. This time all lessons learned from the previous LBSE prototypes were taken into account, resulting in a complete compositing implementation, that works in various scenarios: different transform-box / transform-origin combinations, inline SVG enclosed by absolute/relative positioned CSS boxes and many more, all way more polished than in the “final” LBSE prototype.

+

The aforementioned patch contained a fix for a long-standing bug (“Composited elements appear pixelated when scaled up using transform”), that made composited elements look blurry when scaling up with a CSS transform animation. The so-called “backing scale factor” of the associated GraphicLayers (see here for details about the role of GraphicLayer in the compositing system) never changes during the animation. Therefore, the rendered image was scaled up instead of re-rendering the content at the right scale. LBSE now enforces updates of that scale factor, to avoid blurry SVGs. The fix is not activated yet for HTML as that requires more thought - see the previously-linked bug report for details.

+

With all the new features in place and covered by tests, it was time to finish the remaining SVG renderers: RenderSVGEllipse, RenderSVGPath and RenderSVGViewportContainer (for inner <svg> elements), RenderSVGHiddenContainer, RenderSVGImage, and RenderSVGForeignObject. A proper <foreignObject> implementation was lacking in WebKit for 15+ years, due to the fundamental problem that the layer tree was not aware of the SVG subtree. The LBSE variant of RenderSVGForeignObject looks trivial, yet offers a fully compatible <foreignObject> implementation - for the first time without issues with non-static positioned content as a direct child of <foreignObject>, at least a few weeks later after it landed.

+

Returning to the test-driven approach, the next best target to fix was text rendering, which was working but not pixel-perfect. The legacy SVG engine takes into account the transformation from the text element up to the topmost renderer when computing the effective “on-screen” font size used to select a font for drawing/measuring, during layout time. LBSE needed a way to calculate the CTM for a given SVG renderer, up to a given ancestor renderer (or root), taking into account all possible transformation scenarios, including CSS transform, translate, rotate, SVG transform attribute, shifts due to transform-origin, perspective transformations, and much more. The same functionality is required to implement getCTM() / getScreenCTM().

+

By the end of August 2022, SVGLayerTransformComputation was added that re-used the existing mapLocalToContainer() / TranformState API to obtain the CTM. The CTM construction and ancestor chain walk - to accumulate the final transformation matrix - is performed by mapLocalToContainer() and no longer needs a special, incomplete SVG approach: the existing general approach now works for SVG too.

+

September 2022 was mostly devoted to bug fixes related to pixel-snapping. Outermost <svg> elements were not always enforcing stacking contexts and failed to align to device pixels. All other elements behaved fine with respect to pixel snapping (not applied for SVG elements) unless compositing layers were active. In that case, a RenderLayerBacking code path was used that unconditionally applied pixel-snapping - avoid that for SVG.

+

By October 2022 LBSE could properly display SVGs embedded into HTML host documents via <object> elements – the size negotiation logic failed to take into account the LBSE-specific renderers before. CSS background-image / list-image / HTML <img> / etc. were fixed as well. Zooming and panning support were implemented and improved compared to the legacy engine. Along the way an important bug was fixed, one that other browsers had already fixed back in 2014. The bug caused percentage-sized documents (e.g. width: 100%; height: 100%) that also specify a viewBox to always keep the document size, regardless of the zoom level. Thus, upon zooming, only the stroke width enlarged, but not the boundaries of the document, and thus scrollbars never appeared.

+

Over the following weeks, text-related issues had to be fixed, which were responsible for a bunch of the remaining test issues. Transformed text did not render, which turned out to be a simple mistake. More tests were upstreamed, related to compositing and transformations. More test coverage revealed that transform changes were not handled consistently – it took a period of investigation to land a proper fix. SVG transform / SMIL <animateMotion> / SMIL <animateTransform> / CSS transform changes are now handled consistently in LBSE, leading to proper repaints, as expected.

+

Transformation support can be considered complete and properly handled both during the painting and layout phases. Dynamic changes at runtime are correctly triggering invalidations. However, the Web-exposed SVG DOM API that allows querying the transformation matrices of SVG elements, such as getCTM() and getScreenCTM(), was still missing. By November 2022 a complete implementation was upstreamed, that utilized the new SVGLayerTransformComputation class to construct the desired transformation matrices. This way the same internal API is used for painting/layout/hit-testing and implementing the SVG DOM accessors.

+

By December 2022 LBSE was in a good shape: most important architectural changes were upstreamed and the most basic features were implemented. The year closed with a proposed patch that will avoid re-layout when an element’s transform changes. The legacy SVG engine always needs a re-layout if transform changes, as the size of each ancestor can depend on the presence of transformations on the child elements – a bad design decision two decades ago that LBSE will resolve. Only repainting should happen, but no layouts, in LBSE.

+

Let’s move on to 2023, and recap what’s still missing in LBSE.

+

Next steps

+

Besides fixing all remaining test regressions (see LayoutTests/platform/mac-ventura-wk2-lbse-text/TestExpectations) “SVG resources” are missing in LBSE. That includes all “paint servers” and advanced painting operations: there is no support for linear/radial gradients, no support for patterns, and no support for clipping/masking and filters.

+

From the painting capabilities, LBSE is still in a basic shape. However, this was intentional, since a lot of the existing code for SVG resource handling is no longer needed in LBSE. Clipping/masking and filters will be handled via RenderLayer, reusing the existing HTML/CSS implementations. Temporary ImageBuffers are no longer needed for clipping, and thus there is no need to cache the “per client” state in the resource system (e.g. re-using the cached clipping mask for repainting). This will simplify the implementation of the “SVG resources” a lot.

+

Therefore the first task in 2023 is to implement clipping, then masking, gradients, patterns, and as the last item, filters, since they require a substantial amount of refactoring in RenderLayerFilters. +Note that these implementations are already complete in LBSE downstream and do not need to be invented from scratch. The first patches in that direction should be up for review by February 2023.

+

After all “SVG resources” are implemented in LBSE, feature parity is almost there and performance work will follow afterward. WebKit has a golden rule to never ship a performance regression; therefore, LBSE needs to be at least as fast in the standard performance tests, such as MotionMark, before it can replace the legacy engine. Currently, LBSE is slower than the legacy engine with respect to static rendering performance. Quoting numbers does not help at present, since the problem is well understood and will be resolved in the following months.

+

LBSE currently creates more RenderLayer objects than necessary: for each renderer, unconditionally. This is a great stress test of the layer system, and helpful for bootstrapping, but the associated overhead and complexity are simply not necessary for many cases, and actively hurt performance. LBSE already outperforms the legacy SVG engine whenever animated content is viewed, if it benefits from the hardware acceleration in LBSE.

+

2023 will be an exciting year, and hopefully brings LBSE to the masses, stay tuned!

+

Demos

+

“A picture is worth a thousand words”, so we’d like to share with you the videos shown during the WebKit contributors meeting in 2022 that demo the LBSE capabilities. Be sure to check them out so you can get a good picture of the state of the work. Enjoy!

+
    +
  1. +

    Accelerated 2D transforms (Tiger)

    +

    + +

    +
  2. +
  3. +

    Accelerated 3D transform (Tiger)

    +

    + +

    +
  4. +
  5. +

    Transition storm (Tiger)

    +

    + +

    +
  6. +
  7. +

    Vibrant example

    +

    + +

    +
  8. +
+

Final thoughts

+

We at Igalia are doing our best to fulfill the mission and complete the LBSE upstreaming as fast as possible. In the meanwhile, let us know about your thoughts:

+
    +
  • What would you do with a performant, next-level SVG engine?
  • +
  • Any particular desktop / embedded project that would benefit from it?
  • +
  • Anything in reach now, that seemed impossible before with the given constraints in WebKit?
  • +
+

Thanks for your attention! Be sure to keep an eye on our “Upstreaming status” page at GitHub to follow LBSE development.

+ + + +
+
+
+
+
+
+ + Picture of Nikolas Zimmermann + This article was written by Nikolas Zimmermann.

I'm a proud Igalian since 2019 and have been working on WebKits predecessors since the early 2000s, namely kjs/khtml/ksvg, and kdom/kcanvas/ksvg2/khtml2 that all found their way into WebKit. Since that time, Web technology - especially SVG - is my main area of interest. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/06-integrating-wpe.html b/update-11ty/blog/06-integrating-wpe.html new file mode 100644 index 000000000..c67f12ae0 --- /dev/null +++ b/update-11ty/blog/06-integrating-wpe.html @@ -0,0 +1,777 @@ + + + + + + + + + + + + Integrating WPE: URI Scheme Handlers and Script Messages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Integrating WPE: URI Scheme Handlers and Script Messages

+ + + + + +
+ +
+ +

Most Web content is designed entirely for screen display—and there is a +lot of it—so it will spend its life in the somewhat restricted sandbox +implemented by a web browser. But rich user interfaces using Web technologies +in all kinds of consumer devices require some degree of integration, an +escape hatch to interact with the rest of their software and hardware. This is +where a Web engine like WPE designed to be embeddable shines: not only does +WPE provide a stable API, it is also comprehensive in +supporting a number of ways to integrate with its environment further than +the plethora of available Web platform APIs.

+

Integrating a “Web view” (the main entry point of the WPE embedding +API) involves providing extension points, which allow the +Web content (HTML/CSS/JavaScript) it loads to call into native code provided +by the client application (typically written in C/C++) from JavaScript, and +vice versa. There are a number of ways in which this can be achieved:

+
    +
  • URI scheme handlers allow native code to +register a custom URI +scheme, which will run a user provided +function to produce content that can be “fetched” regularly.
  • +
  • User script messaging can be used to send JSON +messages from JavaScript running in the same context as Web pages to an user +function, and vice versa.
  • +
  • The JavaScriptCore API is a powerful solution to provide new JavaScript +functionality to Web content seamlessly, almost as if they were implemented +inside the Web engine itself—akin to NodeJS C++ addons.
  • +
+

In this post we will explore the first two, as they can support many +interesting use cases without introducing the additional complexity of +extending the JavaScript virtual machine. Let’s dive in!

+

Intermission

+

We will be referring to the code of a tiny browser written for the occasion. +Telling WebKit how to call our native code involves creating a +WebKitUserContentManager, customizing it, and then +associating it with web views during their creation. The only exception to +this are URI scheme handlers, which are registered +using webkit_web_context_register_uri_scheme(). This +minimal browser includes an on_create_view function, which is the perfect +place to do the configuration:

+
static WebKitWebView*
+on_create_view(CogShell *shell, CogPlatform *platform)
+{
+    g_autoptr(GError) error = NULL;
+    WebKitWebViewBackend *view_backend = cog_platform_get_view_backend(platform, NULL, &error);
+    if (!view_backend)
+        g_error("Cannot obtain view backend: %s", error->message);
+
+    g_autoptr(WebKitUserContentManager) content_manager = create_content_manager();  /** NEW! **/
+    configure_web_context(cog_shell_get_web_context(shell));                         /** NEW! **/
+ 
+    g_autoptr(WebKitWebView) web_view =
+        g_object_new(WEBKIT_TYPE_WEB_VIEW,
+                     "user-content-manager", content_manager,  /** NEW! **/
+                     "settings", cog_shell_get_web_settings(shell),
+                     "web-context", cog_shell_get_web_context(shell),
+                     "backend", view_backend,
+                     NULL);
+    cog_platform_init_web_view(platform, web_view);
+    webkit_web_view_load_uri(web_view, s_starturl);
+    return g_steal_pointer(&web_view);
+}
+
+ What is g_autoptr? + Does it relate to g_steal_pointer? + This does not look like C!
+

In the shown code examples, g_autoptr(T) is a preprocessor macro provided by +GLib that declares a pointer variable of the T type, and arranges for +freeing resources automatically when the variable goes out of scope. For +objects this results in +g_object_unref() +being called.

+

Internally the macro takes advantage of the __attribute__((cleanup, ...)) +compiler extension, which is supported by GCC and Clang. GLib also includes a +convenience +macro that +can be used to define cleanups for your own types.

+

As for g_steal_pointer, it is useful to indicate that the ownership of a +pointer declared with g_autoptr is transferred outside from the current +scope. The function returns the same pointer passed as parameter and +resets it to NULL, thus preventing cleanup functions from running.

+
+

The size has been kept small thanks to reusing code from the Cog +core library. As a bonus, it should +run on Wayland, X11, and even on a bare display using the DRM/KMS +subsystem directly. Compiling and running it, assuming you already have the +dependencies installed, should be as easy as running:

+
cc -o minicog minicog.c $(pkg-config cogcore --libs --cflags)
+./minicog wpewebkit.org
+

If the current session kind is not automatically detected, a second parameter +can be used to manually choose among wl (Wayland), x11, drm, and so on:

+
./minicog wpewebkit.org x11
+

The full, unmodified source for this minimal browser is included right below.

+
+ Complete minicog.c source + (Gist) + + +
+

+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * cc -o minicog minicog.c $(pkg-config wpe-webkit-1.1 cogcore --cflags --libs)
+ */
+ 
+#include <cog/cog.h>
+ 
+static const char *s_starturl = NULL;
+ 
+static WebKitWebView*
+on_create_view(CogShell *shell, CogPlatform *platform)
+{
+    g_autoptr(GError) error = NULL;
+    WebKitWebViewBackend *view_backend = cog_platform_get_view_backend(platform, NULL, &error);
+    if (!view_backend)
+        g_error("Cannot obtain view backend: %s", error->message);
+ 
+    g_autoptr(WebKitWebView) web_view =
+        g_object_new(WEBKIT_TYPE_WEB_VIEW,
+                     "settings", cog_shell_get_web_settings(shell),
+                     "web-context", cog_shell_get_web_context(shell),
+                     "backend", view_backend,
+                     NULL);
+    cog_platform_init_web_view(platform, web_view);
+    webkit_web_view_load_uri(web_view, s_starturl);
+    return g_steal_pointer(&web_view);
+}
+ 
+int
+main(int argc, char *argv[])
+{
+    g_set_application_name("minicog");
+ 
+    if (argc != 2 && argc != 3) {
+        g_printerr("Usage: %s [URL [platform]]\n", argv[0]);
+        return EXIT_FAILURE;
+    }
+ 
+    g_autoptr(GError) error = NULL;
+    if (!(s_starturl = cog_uri_guess_from_user_input(argv[1], TRUE, &error)))
+        g_error("Invalid URL '%s': %s", argv[1], error->message);
+ 
+    cog_modules_add_directory(COG_MODULEDIR);
+ 
+    g_autoptr(GApplication) app = g_application_new(NULL, G_APPLICATION_DEFAULT_FLAGS);
+    g_autoptr(CogShell) shell = cog_shell_new("minicog", FALSE);
+    g_autoptr(CogPlatform) platform =
+        cog_platform_new((argc == 3) ? argv[2] : g_getenv("COG_PLATFORM"), &error);
+    if (!platform)
+        g_error("Cannot create platform: %s", error->message);
+ 
+    if (!cog_platform_setup(platform, shell, "", &error))
+        g_error("Cannot setup platform: %s\n", error->message);
+ 
+    g_signal_connect(shell, "create-view", G_CALLBACK(on_create_view), platform);
+    g_signal_connect_swapped(app, "shutdown", G_CALLBACK(cog_shell_shutdown), shell);
+    g_signal_connect_swapped(app, "startup", G_CALLBACK(cog_shell_startup), shell);
+    g_signal_connect(app, "activate", G_CALLBACK(g_application_hold), NULL);
+ 
+    return g_application_run(app, 1, argv);
+}
+
+ +
+
+

URI Scheme Handlers

+
+ “Railroad” diagram of URI syntax +
URI syntax (CC BY-SA 4.0, + source), + notice the “scheme” component at the top left. +
+
+

A URI scheme handler allows “teaching” the web engine how to handle any +load (pages, subresources, the Fetch API, +XmlHttpRequest, …)—if you ever wondered how Firefox implements +about:config or how Chromium does chrome://flags, this is it. Also, +WPE WebKit has public API for this. Roughly:

+
    +
  1. A custom URI scheme is registered using +webkit_web_context_register_uri_scheme(). This also associates a callback function to it.
  2. +
  3. When WebKit detects a load for the scheme, it invokes the provided +function, passing a +WebKitURISchemeRequest.
  4. +
  5. The function generates data to be returned as the result of the load, +as a GInputStream +and calls webkit_uri_scheme_request_finish(). This sends the stream to WebKit as the +response, indicating the length of the response (if known), and the +MIME content type of the data in the stream.
  6. +
  7. WebKit will now read the data from the input stream.
  8. +
+

Echoes

+

Let’s add an echo handler to our minimal browser that +replies back with the requested URI. Registering the scheme is +straightforward enough:

+
static void
+configure_web_context(WebKitWebContext *context)
+{
+    webkit_web_context_register_uri_scheme(context,
+                                           "echo",
+                                           handle_echo_request,
+                                           NULL /* userdata */,
+                                           NULL /* destroy_notify */);
+}
+
+ What are “user data” and “destroy notify”?
+

The userdata parameter above is a convention used in many C libraries, and +specially in these based on GLib when there are callback functions involved. +It allows the user to supply a pointer to arbitrary data, which will be +passed later on as a parameter to the callback (handle_echo_request in the +example) when it gets invoked later on.

+

As for the destroy_notify parameter, it allows passing a function with the +signature void func(void*) (type +GDestroyNotify) which +is invoked with userdata as the argument once the user data is no longer +needed. In the example above, this callback function would be invoked when the +URI scheme is unregistered. Or, from a different perspective, this callback is +used to notify that the user data can now be destroyed.

+
+

One way of implementing handle_echo_request() could be wrapping the request +URI, which is part of the WebKitURISchemeRequest parameter to the handler, +stash it into a GBytes +container, and create an input stream to read back its +contents:

+
static void
+handle_echo_request(WebKitURISchemeRequest *request, void *userdata)
+{
+    const char *request_uri = webkit_uri_scheme_request_get_uri(request);
+    g_print("Request URI: %s\n", request_uri);
+
+    g_autoptr(GBytes) data = g_bytes_new(request_uri, strlen(request_uri));
+    g_autoptr(GInputStream) stream = g_memory_input_stream_new_from_bytes(data);
+
+    webkit_uri_scheme_request_finish(request, stream, g_bytes_get_size(data), "text/plain");
+}
+

Note how we need to tell WebKit how to finish the load +request, +in this case only with the data stream, but it is possible to have more +control of the +response +or return an +error.

+

With these changes, it is now possible to make page loads from the new custom +URI scheme:

+
+ Screenshot of the minicog browser loading a custom echo:// URI +
It worked!
+
+

Et Tu, CORS?

+

The main roadblock one may find when using custom URI schemes is that loads +are affected by CORS +checks. Not only that, WebKit by default does not allow sending cross-origin +requests to custom URI schemes. This is by design: instead of accidentally +leaking potentially sensitive data to websites, developers embedding a web +view need to consciously opt-in to allow CORS requests and +send back suitable Access-Control-Allow-* response headers.

+

In practice, the additional setup involves +retrieving +the WebKitSecurityManager being used by the WebKitWebContext and +registering the scheme as +CORS-enabled. +Then, in the handler function for the custom URI scheme, create a +WebKitURISchemeResponse, +which allows fine-grained control of the response, including setting +headers, +and finishing the request instead with +webkit_uri_scheme_request_finish_with_response().

+

Note that WebKit cuts some corners when using CORS with custom URI schemes: +handlers will not receive preflight OPTIONS requests. Instead, the CORS +headers from the replies are inspected, and if access needs to be denied +then the data stream with the response contents is discarded.

+

In addition to providing a complete CORS-enabled custom URI scheme example, +we recommend the Will It CORS? tool +to help troubleshoot issues.

+

Further Ideas

+

Once we have WPE WebKit calling into our custom code, there are no limits +to what a URI scheme handler can do—as long as it involves replying +to requests. Here are some ideas:

+
    +
  • Allow pages to access a subset of paths from the local file system in a +controlled way (as CORS applies). For inspiration, +see CogDirectoryFilesHandler.
  • +
  • Package all your web application assets into a single ZIP file, making +loads from app:/... fetch content from it. Or, make the scheme handler +load data using GResource and bundle the application +inside your program.
  • +
  • Use the presence of a well-known custom URI to have a web application +realize that it is running on a certain device, and make its user +interface adapt accordingly.
  • +
  • Provide a REST API, which internally calls into +NetworkManager to list and configure +wireless network adapters. Combine it with a local web application and +embedded devices can now easily get on the network.
  • +
+

User Script Messages

+

While URI scheme handlers +allow streaming large chunks of data back into the Web engine, for exchanging +smaller pieces of information in a more programmatic fashion it may be +preferable to exchange messages without the need to trigger resource loads. +The user script messages part of the +WebKitUserContentManager API can be used this way:

+
    +
  1. Register a user message handler with +webkit_user_content_manager_register_script_message_handler(). +As opposed to URI scheme handlers, this only enables receiving messages, +but does not associate a callback function yet.
  2. +
  3. Associate a callback to the +script-message-received +signal. The signal detail should be the name of the registered handler.
  4. +
  5. Now, whenever JavaScript code calls +window.webkit.messageHandlers.<name>.postMessage(), the signal is +emitted, and the native callback functions invoked.
  6. +
+
+ Haven't I seen postMessage() elsewhere?
+

Yes, +you +have. +The name is the same because it provides a similar functionality (send a +message), it guarantees little (the receiver should validate messages), and +there are similar +restrictions +in the kind of values that can be passed along.

+
+

It’s All JavaScript

+

Let’s add a feature to our minimal browser that will allow +JavaScript code to trigger rebooting or powering off the device where it is +running. While this should definitely not be functionality exposed to the +open Web, it is perfectly acceptable in an embedded device where we control +what gets loaded with WPE, and that exclusively uses a web application as its +user interface.

+
+ Pepe Silvia conspiracy image meme, with the text “It's all JavaScript” superimposed +
Yet most of the code shown in this post is C.
+
+

First, create a WebKitUserContentManager, register the message handler, +and connect a callback to its associated signal:

+
static WebKitUserContentManager*
+create_content_manager(void)
+{
+    g_autoptr(WebKitUserContentManager) content_manager = webkit_user_content_manager_new();
+    webkit_user_content_manager_register_script_message_handler(content_manager, "powerControl");
+    g_signal_connect(content_manager, "script-message-received::powerControl",
+                     G_CALLBACK(handle_power_control_message), NULL);
+    return g_steal_pointer(&content_manager);
+}
+

The callback receives a WebKitJavascriptResult, from which we +can get the JSCValue with the contents of the parameter +passed to the postMessage() function. The JSCValue can now be inspected +to check for malformed messages and determine the action to take, and +then arrange to call reboot():

+
static void
+handle_power_control_message(WebKitUserContentManager *content_manager,
+                             WebKitJavascriptResult *js_result, void *userdata)
+{
+    JSCValue *value = webkit_javascript_result_get_js_value(js_result);
+    if (!jsc_value_is_string(value)) {
+        g_warning("Invalid powerControl message: argument is not a string");
+        return;
+    }
+
+    g_autofree char *value_as_string = jsc_value_to_string(value);
+    int action;
+    if (strcmp(value_as_string, "poweroff") == 0) {
+        action = RB_POWER_OFF;
+    } else if (strcmp(value_as_string, "reboot") == 0) {
+        action = RB_AUTOBOOT;
+    } else {
+        g_warning("Invalid powerControl message: '%s'", value_as_string);
+        return;
+    }
+
+    g_message("Device will %s now!", value_as_string);
+    sync(); reboot(action);
+}
+

Note that the reboot() system call above will most likely fail because it +needs administrative privileges. While the browser process could run as root +to sidestep this issue—definitely not recommended!—it would be +better to grant the CAP_SYS_BOOT capability to the process, and much +better to ask the system manager daemon to handle the job. In machines +using systemd a good option is to call the .Halt() +and .Reboot() methods of its org.freedesktop.systemd1 interface.

+

Now we can write a small HTML document with some JavaScript sprinkled on top +to arrange sending the messages:

+
<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>Device Power Control</title>
+  </head>
+  <body>
+    <button id="reboot">Reboot</button>
+    <button id="poweroff">Power Off</button>
+    <script type="text/javascript">
+      function addHandler(name) {
+        document.getElementById(name).addEventListener("click", (event) => {
+          window.webkit.messageHandlers.powerControl.postMessage(name);
+          return false;
+        });
+      }
+      addHandler("reboot");
+      addHandler("poweroff");
+    </script>
+  </body>
+</html>
+

The complete source code for this example can be found +in this Gist.

+

Going In The Other Direction

+

But how can one return values from user messages back to the JavaScript code +running in the context of the web page? Until recently, the only option +available was exposing some known function in the page’s JavaScript code, and +then using +webkit_web_view_run_javascript() +to call it from native code later on. To make this more idiomatic and allow +waiting on a Promise, an approach like the following works:

+
    +
  1. Have convenience JavaScript functions wrapping the calls to +.postMessage() which add an unique identifier as part of the message, +create a Promise, and store it in a Map indexed by the identifier. +The Promise is itself returned from the functions.
  2. +
  3. When the callback in native code handle messages, they need to take +note of the message identifier, and then use +webkit_web_view_run_javascript() to pass it back, along with the +information needed to resolve the promise.
  4. +
  5. The Javascript code running in the page takes the Promise from +the Map that corresponds to the identifier, and resolves it.
  6. +
+

To make this approach a bit more palatable, we could tell WebKit to inject a +script +along with the regular content, which would provide the helper +functions +needed to achieve this.

+

Nevertheless, the approach outlined above is cumbersome and can be +tricky to get right, not to mention that the effort needs to be duplicated in +each application. Therefore, we have recently added new API hooks to provide this +as a built-in feature, so starting in WPE WebKit 2.40 the recommended approach +involves using +webkit_user_content_manager_register_script_message_handler_with_reply() +to register handlers instead. This way, calling .postMessage() now returns a +Promise to the JavaScript code, and the callbacks connected to the +script-message-with-reply-received +signal now receive a +WebKitScriptMessageReply, +which can be used to resolve the promise—either on the spot, or +asynchronously later on.

+

Even More Ideas

+

User script messages are a powerful and rather flexible facility to make WPE +integrate web content into a complete system. The provided example is rather +simple, but as long as we do not need to pass huge amounts of data in +messages the possibilities are almost endless—especially with the +added convenience in WPE WebKit 2.40. Here are more ideas that can +be built on top of user script messages:

+
    +
  • A handler could receive requests to “monitor” some object, and +return a Promise that gets resolved when it has received changes. +For example, this could make the user interface of a smart thermostat +react to temperate updates from a sensor.
  • +
  • A generic handler that takes the message payload and converts it into +D-Bus method calls, allowing +web pages to control many aspects of a typical Linux system.
  • +
+

Wrapping Up

+

WPE has been designed from the ground up to integrate with the rest of the +system, instead of having a sole focus on rendering Web content inside a +monolithic web browser application. Accordingly, the public API must be +comprehensive enough to use it as a component of any application. This +results in features that allow plugging into the web engine at different +layers to provide custom behaviour.

+

At Igalia we have years of experience embedding WebKit into all kinds of +applications, and we are always sympathetic to the needs of such systems. If +you are interested collaborating with WPE development, or searching for a +solution that can tightly integrate web content in your device, feel free to +contact us.

+ + + + +
+
+
+
+
+
+ Head shot of Adrián Pérez + This article was written by Adrián Pérez. +
+ I have been working on WebKit since 2012, with a focus on + environment integration, embedding, and distribution. Igalia + has been a life-long project since even earlier. +
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/07-creating-wpe-backends.html b/update-11ty/blog/07-creating-wpe-backends.html new file mode 100644 index 000000000..2c76b8654 --- /dev/null +++ b/update-11ty/blog/07-creating-wpe-backends.html @@ -0,0 +1,678 @@ + + + + + + + + + + + + A New WPE Backend Using EGLStream + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

A New WPE Backend Using EGLStream

+ + + + + +
+ +
+ +

What is a WPE Backend?

+ +

Depending on the target hardware WPE may need to use different techniques and +technologies to ensure correct graphical rendering. To be independent of any +user-interface toolkit and windowing system, WPE WebKit delegates the rendering +to a third-party API defined in the +libwpe library. A concrete +implementation of this API is a “WPE backend”.

+

WPE WebKit is a multiprocess application, the end-user starts and controls the +web widgets in the application process (which we often call “the UI process” while the web engine itself uses +different subprocesses: WPENetworkProcess is in charge of managing network +connections and WPEWebProcess (or “web process”) in charge of the HTML and +JavaScript parsing, execution and rendering. The WPE backend is at a crossroads +between the UI process and one or more web process instances.

+
+ Diagram showing a box for the WPE backend in between the UI process and WPEWebProcess + +
+

The WPE backend is a shared library that is loaded at runtime by the web +process and by the UI process. It is used to render the visual aspect of a web +page and transfer the resulting video buffer from the web process to the +application process.

+

Backend Interfaces

+

The WPE backend shared library must export at least one symbol called +_wpe_loader_interface of type struct wpe_loader_interface as defined in +the libwpe +API. +Presently its only member is load_object, a callback function that receives a +string with an interface name and returns concrete implementations of the +following interfaces:

+ +

The names passed to the .load_object() function are the same as those of the +interface types, prefixed with an underscore. For example, a +.load_object("_wpe_renderer_host_interface") call must return a pointer to a +struct wpe_renderer_host_interface object.

+
+ Example C code for a load_object callback. + +
static struct wpe_renderer_host_interface = { /* ... */ };
+static struct wpe_renderer_backend_egl_interface = { /* ... */ };
+
+static void*
+my_backend_load_object(const char *name)
+{
+    if (!strcmp(name, "_wpe_renderer_host_interface"))
+        return &my_renderer_host;
+    if (!strcmp(name, "_wpe_renderer_backend_egl_interface"))
+        return &my_renderer_backend_egl;
+
+    /* ... */
+
+    return NULL;
+}
+
+struct wpe_loader_interface _wpe_loader_interface = {
+    .load_object = my_backend_load_object,
+};
+ +
+

Each of these interfaces follow the same base structure: the struct members +are callback functions, all interfaces have create and destroy members which +act as instance constructor and destructor, plus any additional “methods”. +The pointer returned by the create callback will be passed as the object +“instance” of the other methods:

+
struct wpe_renderer_host_interface {
+  void* (*create)(void);
+  void  (*destroy)(void *object);
+  /* ... */
+};
+

In the UI process side WPE WebKit will create:

+
    +
  • One “renderer host” instance, using wpe_renderer_host_interface.create().
  • +
  • Multiple “renderer host client” instances, using wpe_renderer_host_interface.create_client(). +These are mainly used for IPC communication, one instance gets created for +each web process launched by WebKit.
  • +
  • Multiple “view backend” instances, using wpe_view_backend_interface.create(). +One instance is created for each rendering target in the web process.
  • +
+

In each web process—there can be more than one—WPE WebKit +will create:

+
    +
  • One “renderer backend EGL” instance, using wpe_renderer_backend_egl_interface.create().
  • +
  • Multiple “renderer backend EGL target” instances, using +wpe_renderer_backend_egl_target_interface.create(). An instance is created +for each new rendering target needed by the application.
  • +
+
+ How about wpe_renderer_backend_egl_offscreen_target_interface? +
+

The rendererBackendEGLTarget instances may be created by the wpe_renderer_backend_egl_target_interface, or +the wpe_renderer_backend_egl_offscreen_target_interface depending on the interfaces implemented in the backend.

+

Here we are only focusing on the wpe_renderer_backend_egl_target_interface that is relying on a classical EGL +display (defined in the rendererBackendEGL instance). The wpe_renderer_backend_egl_offscreen_target_interface may +be used in very specific use-cases that are out of the scope of this post. You can check its usage in the WPE WebKit +source code +for more information.

+
+
+

These instances typically communicate with each others using Unix sockets for +IPC. The IPC layer must be +implemented in the WPE backend itself because the libwpe interfaces only pass +around the file descriptors to be used as communication endpoints.

+

From a topological point of view, all those instances are organized as follows:

+
+ + +
+

From an usage point of view:

+
    +
  • The rendererHost and rendererHostClient instances are only used to manage +IPC endpoints on the UI process side that are connected to each running +web process. They are not used by the graphical rendering system.
  • +
  • The rendererBackendEGL instance (one per web process) is only used to +connect to the native display for a specific platform. For example, on a +desktop Linux, the platform may be X11 where the native display would be the +result of calling XOpenDisplay(); or the platform may be Wayland and in +this case the native display would be the result of calling +wl_display_connect(); and so on.
  • +
  • The rendererBackendEGLTarget (on the web process side) and viewBackend +(on the UI process side) instances are the ones truly managing the web page +graphical rendering.
  • +
+

Graphics Rendering

+

As seen above, the interfaces in charge of the rendering are +wpe_renderer_backend_egl_target_interface and wpe_view_backend_interface. +During their creation, WPE WebKit exchanges the file descriptors used to +establish a direct IPC connection between a rendererBackendEGL (in the +web process), and a viewBackend (in the UI process).

+

During the EGL initialization phase, when a new web process is launched, WebKit +will use the native display and platform provided by the +wpe_renderer_backend_egl_interface.get_native_display() and .get_platform() +functions to create a suitable OpenGL ES context.

+

When WebKit’s +ThreadedCompositor +is ready to render a new frame (in the web process), it calls the +wpe_renderer_backend_egl_target_interface.frame_will_render() function to let +the WPE backend know that rendering is about to start. At this moment, the +previously created OpenGL ES context is made current to be used as the target +for GL drawing commands.

+

Once the threaded compositor has finished drawing, it will swap the front and +back EGL buffers and call the +wpe_renderer_backend_egl_target_interface.frame_rendered() function to signal +that the frame is ready. The compositor will then wait until the WPE backend +calls wpe_renderer_backend_egl_target_dispatch_frame_complete() to indicate +that the compositor may produce a new frame.

+

What happens inside the .frame_will_render() and .frame_rendered() +implementations is up to the WPE backend. As en example, it could +set up a Frame Buffer Object +to have the web content draw offscreen, in a texture that can be passed +back to the UI process for further processing, or use extensions like +EGLStream, +or DMA-BUF exports +to transfer the frame to the UI process without copying the pixel data.

+
+ + +
+

Typically the backend sends each new frame to the corresponding view backend in +in its .frame_rendered() function. The application can use the frame until it +sends back an IPC message to the renderer target (in the web +process) to indicate that the frame is not in use anymore and may be be freed +or recycled. Although it is not a requirement to do it at this exact point, +usually when a renderer backend receives this message it calls the +wpe_renderer_backend_egl_target_dispatch_frame_complete() function to trigger +the rendering of a new frame. As a side effect, this mechanism also allows +controlling the pace at which new frames are produced.

+

Using EGLStream

+

EGLStream is an EGL extension that defines a mechanism to transfer hardware +video buffers from one process to another efficiently, without getting them +out of GPU memory. Although the extension is supported only in Nvidia +hardware, it makes for a good example as it transparently handles some +complexities involved, like buffers with multiple planes.

+

This backend uses the EGLStream extension to transfer graphics buffers from the +web process, which acts as a producer, to the UI process acting as a consumer. +The producer extension +EGL_KHR_stream_producer_eglsurface +allows creating a surface that may be used as target for rendering, then using +eglSwapBuffers() +finishes drawing and sends the result to the consumer. Meanwhile, in the +consumer side, the +EGL_NV_stream_consumer_eglimage +extension is used to turn each buffer into an EGLImage.

+

The reference source code for this WPE backend is available in the +WPEBackend-offscreen-nvidia +repository, which has been tested with WPE WebKit 2.38.x or 2.40.x, and +libwpe version 1.14.x.

+
+ Behold, the Future Belongs to DMA-BUF! +
+

With the growing adoption of +DMA-BUF for sharing memory +buffers on modern Linux platforms, the WPE WebKit architecture will be +evolving and, in the future, the need for a WPE Backend should disappear in +most cases.

+

Ongoing work on WPE WebKit removes the need to provide a WPE backend +implementation for most hardware platforms, with a generic implementation +using DMA-BUF provided as an integral, built-in feature of WebKit. It will +still be possible to provide external implementations for platforms that +might need to use custom buffer sharing mechanisms.

+

From the application developer point of view, in most cases writing +programs that use the WPE WebKit API will be simpler, with the complexity +of the communication among multiple processes handled by WebKit.

+
+
+

Stream Setup

+

The steps needed to set up EGLStream endpoints need to be done in a particular +order:

+
    +
  1. Create the consumer.
  2. +
  3. Get the stream file descriptor for the consumer.
  4. +
  5. Send the stream file descriptor to the producer.
  6. +
  7. Create the producer.
  8. +
+

First, the consumer needs to be created:

+
EGLStream createConsumerStream(EGLDisplay eglDisplay) {
+    static const EGLint s_streamAttribs[] = {
+        EGL_STREAM_FIFO_LENGTH_KHR, 1,
+        EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 1000 * 1000,
+        EGL_NONE
+    };
+    return eglCreateStreamKHR(eglDisplay, s_streamAttribs);
+}
+

The EGL_STREAM_FIFO_LENGTH_KHR parameter defines the length of the EGLStream +queue. If set to zero, the stream will work in “mailbox” mode and each time the +producer has a new frame it will empty the stream content and replace the frame +by the new one. If non-zero, the stream works work in “FIFO” mode, which means that the stream queue can contain up +to EGL_STREAM_FIFO_LENGTH_KHR frames.

+

Here we configure a queue for one frame because in this case the specification +of EGL_KHR_stream_producer_eglsurface guarantees that calling +eglSwapBuffers() on the producer the call will block until the consumer +retires the previous frame from queue. This is used as implicit synchronization +between the UI process side and the web process side without needing to rely on +custom IPC, which would add a small delay between frames.

+

The EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR parameter defines the maximum +timeout in microseconds to wait on the consumer side to acquire a frame when +calling eglStreamConsumerAcquireKHR(). It is only used with the +EGL_KHR_stream_consumer_gltexture extension because the +EGL_NV_stream_consumer_eglimage extension allows setting a timeout on each +call to eglQueryStreamConsumerEventNV() function.

+

Second, to initialize the consumer using the EGL_NV_stream_consumer_eglimage +extension it is enough to call the eglStreamImageConsumerConnectNV() function.

+

Once the consumer has been initialized, you need to send the EGLStream +file descriptor to the producer process. The usual way of achieving this would +be using IPC between the two processes, sending the file descriptor in a +SCM_RIGHTS message through an Unix socket—although with recent kernels +using pidfd_getfd() may be an option if +both processes are related.

+

When the file descriptor is finally received, the producer endpoint can be +created using the EGL_KHR_stream_producer_eglsurface extension:

+
const EGLint surfaceAttribs[] = {
+    EGL_WIDTH, width,
+    EGL_HEIGHT, height,
+    EGL_NONE
+};
+EGLStream eglStream = eglCreateStreamFromFileDescriptorKHR(eglDisplay, consumerFD);
+EGLSurface eglSurface = eglCreateStreamProducerSurfaceKHR(eglDisplay, config, eglStream, surfaceAttribs);
+

As with pbuffer surfaces, the dimensions +need to be specified as surface attributes. When picking a frame buffer +configuration with eglChooseConfig() the EGL_SURFACE_TYPE attribute must +be set to EGL_STREAM_BIT_KHR. From this point onwards, rendering proceeds as +usual: the EGL surface and context are made active, and once the painting is +done a call to eglSwapBuffers() will “present” the frame, which in this case +means sending the buffer with the pixel data down the EGLStream to the +consumer.

+
+ + +
+

Consuming Frames

+

While on the producer side rendering treats the EGLStream surface like any +other, on the consumer some more work is needed to manager the lifetime of +the data received: frames have to be manually acquired and released once +they are not needed anymore.

+

The producer calls eglQueryStreamConsumerEventNV() repeatedly to retire the +next event from the stream:

+
    +
  • EGL_STREAM_IMAGE_ADD_NV indicates that there is a buffer in the stream +that has not yet been bound to an EGLImage, and the application needs to +create a new one to which the actual data will be bound later.
  • +
  • EGL_STREAM_IMAGE_AVAILABLE_NV indicates that a new frame is available +and that it can be bound to the previously created EGLImage.
  • +
  • EGL_STREAM_IMAGE_REMOVE_NV indicates that a buffer has been retired from +the stream, and that its associated EGLImage may be released once the +application has finished using it.
  • +
+

This translates roughly to the following code:

+
static constexpr EGLTime MAX_TIMEOUT_USEC = 1000 * 1000;
+EGLImage eglImage = EGL_NO_IMAGE;
+
+while (true) {
+    EGLenum event = 0;
+    EGLAttrib data = 0;
+
+    // WARNING: The specification states that the timeout is in nanoseconds
+    // (see: https://registry.khronos.org/EGL/extensions/NV/EGL_NV_stream_consumer_eglimage.txt)
+    // but in reality it is in microseconds, at least with the version 535.113.01 of the NVidia drivers.
+    if (!eglQueryStreamConsumerEventNV(display, eglStream, MAX_TIMEOUT_USEC, &event, &data))
+        break;
+
+    switch (event) {
+      case EGL_STREAM_IMAGE_ADD_NV: // Bind an incoming buffer to an EGLImage.
+          if (eglImage) eglDestroyImage(display, eglImage);
+          eglImage = eglCreateImage(display, EGL_NO_CONTEXT, EGL_STREAM_CONSUMER_IMAGE_NV,
+                                    static_cast<EGLClientBuffer>(eglStream), nullptr);
+          continue; // Handle the next event.
+
+      case EGL_STREAM_IMAGE_REMOVE_NV: // Buffer removed, EGLImage may be disposed.
+          if (data) {
+              EGLImage image = reinterpret_cast<EGLImage>(data);
+              eglDestroyImage(display, image);
+              if (image == eglImage)
+                  eglImage = EGL_NO_IMAGE;
+          }
+          continue; // Handle the next event.
+
+      case EGL_STREAM_IMAGE_AVAILABLE_NV: // New frame available.
+          if (eglStreamAcquireImageNV(display, eglStream, &eglImage, EGL_NO_SYNC))
+              break;
+
+      default:
+          continue; // Handle the next event.
+    }
+
+    /*** Use the EGLImage here ***/
+
+    eglStreamReleaseImageNV(display, eglStream, eglImage, EGL_NO_SYNC);
+}
+

The application is free to use each EGLImage as it sees fit. An obvious +example would be to use it as the contents for a texture, which then gets +painted in the “content” area of a web browser; or as the contents of the +screen for an in-game computer that the player can interact with, enabling +display of real, live web content as part of the gaming experience—now +that would be a deeply embedded browser!

+

One Last Thing

+

There is a small showstopper to have EGLStream support working: +currently +when WPE WebKit uses surfaceless EGL contexts it sets the surface type to +EGL_WINDOW_BIT attribute, while EGL_STREAM_BIT_KHR would be needed +instead. A small +patch +is enough to apply this tweak:

+
diff --git a/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp b/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp
+index d5efa070..5f200edc 100644
+--- a/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp
++++ b/Source/WebCore/platform/graphics/egl/GLContextEGL.cpp
+@@ -122,9 +122,11 @@ bool GLContextEGL::getEGLConfig(EGLDisplay display, EGLConfig* config, EGLSurfac
+         attributeList[13] = EGL_PIXMAP_BIT;
+         break;
+     case GLContextEGL::WindowSurface:
+-    case GLContextEGL::Surfaceless:
+         attributeList[13] = EGL_WINDOW_BIT;
+         break;
++    case GLContextEGL::Surfaceless:
++        attributeList[13] = EGL_STREAM_BIT_KHR;
++        break;
+     }
+
+     EGLint count;
+
+ + + + +
+
+
+
+
+
+ Head shot of Loïc Le Page + This article was written by Loïc Le Page. +
+ I have worked in different industries like video games, + cinema, and multimedia—the latter being where my + focus lies at the moment. Did anyone say Web engines need + that, too? +
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/1/index.html b/update-11ty/blog/1/index.html new file mode 100644 index 000000000..15275f1fe --- /dev/null +++ b/update-11ty/blog/1/index.html @@ -0,0 +1,322 @@ + + + + + + + + + + + + Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

Blog

+

Don’t miss any news related to WPE – our blog aims to share information regarding WPE: explainers, how-to articles and general information regarding WPE, WebKit +and the Web platform. Also check out the official WebKit blog regarding news on the engine itself.

+
+
+
  1. + + +

    Use Case: WPE in digital signage

    +

    Digital signage web rendering players have many advantages and are a trend nowadays.

    +
  2. + + +

    Success Story: Metrological

    +

    WPE WebKit brought RDK (Reference Design Kit), a modern, performant web browser, to millions of screens.

    +
  3. + + +

    WPE Networking Overview

    +

    In this post we'll cover the many layers of the networking stack that WPE uses including libsoup and glib.

    +
  4. + + +

    WPE QA and tooling

    +

    In the previous posts, my colleagues Claudio and Miguel wrote respectively about the major components of the project and, specifically, the graphics architecture of WPE. Today, you'll see our efforts to improve the quality of both WPE and the experience of working and using it.

    +
  5. + + +

    WPE Graphics architecture

    +

    Following the previous post in the series about WPE where we talked about the WPE components, this post will explain briefly the WPE graphics architecture, and how the engine is able to render HTML content into the display.

    +
  6. + + +

    An overview of the WPE WebKit project

    +

    In the previous post in this series, we explained that WPE is a WebKit port optimized for embedded devices. In this post, we'll dive into a more technical overview of the different components of WPE, WebKit, and how they all fit together.

    +
  7. + + +

    Happy birthday WPE!

    +

    Welcome to the new Blog section on wpewebkit.org! Let's take some time to celebrate and recap how WPE evolved from the early prototyping days to the product empowering hundreds of millions of devices worldwide today.

    +
+ +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/2022-success-metrological.html b/update-11ty/blog/2022-success-metrological.html new file mode 100644 index 000000000..7b7c1ef33 --- /dev/null +++ b/update-11ty/blog/2022-success-metrological.html @@ -0,0 +1,293 @@ + + + + + + + + + + + + Success Story: Metrological + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Success Story: Metrological

+ + + + + +
+ +
+ +
+Metrological: A Comcast Company +WPE +
+

WPE WebKit brought RDK (Reference Design Kit), a modern, performant web browser, to millions of screens. It enables operators to manage devices and easily customize their UIs and apps and provides analytics to improve the customer experience and drive business results.

+

Delivering a fast and memory-efficient browser for embedded systems is a challenging task, so Igalia helped Metrological build a new full-screen browser engine which stripped away all unnecessary toolkit elements.

+

With years of experience around WebKit platform integration, Igalia worked to produce a new WebKit port, WPE, which interfaced directly with Wayland and the graphics driver. Additionally, Igalia pushed forward the implementation of a multi-platform multi-threaded compositor, enabling better performance on low-end multicore processors. WPE is an official port of WebKit.

+ + + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/2023-success-digital-signage.html b/update-11ty/blog/2023-success-digital-signage.html new file mode 100644 index 000000000..dbbf63a5a --- /dev/null +++ b/update-11ty/blog/2023-success-digital-signage.html @@ -0,0 +1,14 @@ + + + + + + + + +

If you can see this, you should be redirecting this URL to /blog/2023-use-case-digital-signage.html.

+ + \ No newline at end of file diff --git a/update-11ty/blog/2023-use-case-digital-signage.html b/update-11ty/blog/2023-use-case-digital-signage.html new file mode 100644 index 000000000..b428f7e45 --- /dev/null +++ b/update-11ty/blog/2023-use-case-digital-signage.html @@ -0,0 +1,292 @@ + + + + + + + + + + + + Use Case: WPE in digital signage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Use Case: WPE in digital signage

+ + + + + +
+ +
+ +
+WPE WebKit in digital signage +WPE +
+

Digital signage web rendering players have many advantages and are a trend nowadays. They allow to use HTML5 for composing the UI, provisioning and scheduling new contents to the screens from the cloud is simple and effortless, they provide a robust environment with an automatic crash recovery system, etc. They are also a great choice for digital signage kiosk deployments.

+

WPE WebKit is an excellent option to use as web rendering engine for Linux-based digital signage players, specially for the low-end market, where WPE Webkit allows to achieve great graphics and rendering performance in the less powerful devices like the ones based on the Raspberry Pi. As a result, WPE WebKit is naturally compatible with the hardware of the main digital signage manufactures that rely on these kind of lower-powered devices.

+ + + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/2024-use-case-server-side-rendering.html b/update-11ty/blog/2024-use-case-server-side-rendering.html new file mode 100644 index 000000000..c1c08bc49 --- /dev/null +++ b/update-11ty/blog/2024-use-case-server-side-rendering.html @@ -0,0 +1,298 @@ + + + + + + + + + + + + Use Case: Server-side headless rendering + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Use Case: Server-side headless rendering

+ + + + + +
+ +
+ +
+WPE and server-side headless rendering +
+

In many distributed applications, it can be useful to run a light web browser on the server side to render some HTML content or process images, video and/or audio using JavaScript.

+

Some concrete use-cases can be:

+
    +
  • Video post-production using HTML overlays.
  • +
  • Easy 3D rendering with WebGL that can be broadcasted as a video stream.
  • +
  • Reusing the same JavaScript code between a frontend web application and the backend processing.
  • +
+

WPE WebKit is the perfect solution for all those use cases as it offers a lightweight solution which can run on low-end hardware or even within a container. It provides a lot of flexibility at the moment of choosing the backend infrastructure as WPE WebKit can, for instance, run from within a container with a very minimal Linux configuration (no need for any windowing system) and with full hardware acceleration and zero-copy of the video buffers between the GPU and the CPU.

+

Additionally, the fact that WPE WebKit is optimized for lower-powered devices, makes it also the perfect option for server-side rendering when scaling commercial deployments while keeping cost under control, which is yet another important factor to take into account when considering cloud rendering.

+ + + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/2024-wpewebkit-2.44.html b/update-11ty/blog/2024-wpewebkit-2.44.html new file mode 100644 index 000000000..d040e294c --- /dev/null +++ b/update-11ty/blog/2024-wpewebkit-2.44.html @@ -0,0 +1,315 @@ + + + + + + + + + + + + WPE WebKit 2.44 highlights + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE WebKit 2.44 highlights

+ + + + + +
+ +
+ +

The WPE team released WPE WebKit 2.44 a few weeks ago. Let’s have a look at the most significant changes to the port for this release cycle.

+ +

DisplayLink is a WebCore feature that improves resource utilization and improves synchronization with vertical screen retrace. 2.44 adds an implementation of this feature for the WPE port that improves rendering performance.

+

Improved hardware-acceleration video decoding and rendering

+

When WebKit is using GStreamer 1.24 or newer, video playback can use the new support for DRM modifiers in the DMA-BUF sink. This improves video decoding and rendering, as it allows for zero-copy negotiation with the video decoders.

+

WebCodec API supported

+

WPE now supports the WebCodecs API, which allows web developers low-level access to video frames and audio chunks, a feature of importance for multimedia applications that need finer grain control over what gets played on the browser.

+

Other noteworthy changes

+
    +
  • Support for the JPEG2000 image format has been removed. WebKit was the only major engine still supporting the format, which these days is rarely used. As a consequence, OpenJPEG is no longer a dependency. JPEG2000 should not be confused with JPEG-XL, which is still supported.
  • +
  • Support usage of libbacktrace, enabled by default at build time. This library provides quality stacktraces that can help developers and deployers more efficiently debug crashes in WPE-powered browsers.
  • +
  • Many memory and stability improvements, particularly on the multimedia backends.
  • +
+

For a complete list of changes, please check the releases page.

+ + + +
+
+
+
+
+
+ + This article was written by Claudio Saavedra.

Claudio is long-time WebKit contributor from Igalia, working in different areas of WebKit, WPE, and the stack around it. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/2024-wpewebkit-2.46.html b/update-11ty/blog/2024-wpewebkit-2.46.html new file mode 100644 index 000000000..a608f8e57 --- /dev/null +++ b/update-11ty/blog/2024-wpewebkit-2.46.html @@ -0,0 +1,366 @@ + + + + + + + + + + + + WPE WebKit 2.46 highlights + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

WPE WebKit 2.46 highlights

+ + + + + +
+ +
+ +

A couple of weeks ago, the WPE WebKit team released version 2.46. This is an important milestone for the project as, for the first time in a stable series, the Skia backend takes over rendering. Skia brings significant improvements to the graphics stack, so we are very happy for this release. The list of changes goes beyond graphics, and it’s not short of awesome, so let’s have a look to what’s new!

+

Cairo is out, Skia is in

+

We announced some time ago that a new rendering backend with Skia was on the works and that it would eventually replace Cairo. 2.46 the first release series where Skia is used, bringing important improvements in rendering and performance.

+

While Skia can use a GPU for rendering, our testing with common embedded SoCs has shown that the way WPE WebKit works may result in slightly worse performance in some cases than letting Skia use the CPU. Hence, for the 2.46 releases the latter is the default, while development continues to improve GPU usage on low-powered devices with the ultimate goal of making accelerated rendering the default choice in all cases.

+

The Cairo backend is still present and will be selected automatically at build time for big-endian architectures, where Skia is not yet supported. We plan to remove support for Cairo in the near future, and this approach allows us to ship the new renderer while solving the remaining issues. At any rate, the Cairo renderer is no longer receiving active development.

+

It is important to notice that it is recommended to build WPE with Clang instead of GCC. This comes from upstream Skia; see their supported and preferred compilers page for details.

+

Graphics stack revamped

+

Tha switch to Skia has made possible a significant number of changes and improvements in the WebKit graphics stack. These changes relate to accelerated canvas, accelerated CSS filters, color spaces, and more. Carlos García has written extensively about these changes in his blog, we recommend reading his article for more details.

+

Trace point profiling with sysprof

+

Sysprof is a profiling and performance analysis tool for Linux. Thanks to integration with libsysprof-capture, it is now possible to use Sysprof to record trace points to do profiling and performance analysis of WebKit internals. This is a major improvement that will allow us to more effectively analyze the code paths that are more performance-sensitive and find ways to optimize them. It will also allow vendors to profile their specific hardware configurations and specific use-cases as well.

+

For a more in-depth presentation of the integration with Sysprof, please read Georges Stavacras’ blog post on the topic.

+

API changes

+

Additions

+
    +
  • webkit_settings_apply_from_key_file() allows applying WebKit settings directly from a key file
  • +
  • The console message API, which had been previously deprecated, has been brought to the current API
  • +
  • WebKitAutomationSession::will-close signal, which allows clients to perform cleanup tasks before an automation session is closed
  • +
  • enable-2d-canvas-acceleration WebSetting can be used to control 2D-canvas acceleration in Skia-enabled builds
  • +
  • webkit_web_view_toggle_inspector() shows or hides the web inspector for a given webview (only available with the WPE platform API)
  • +
+

Deprecations

+
    +
  • WebKitWebView::insecure-content-detected signal.
  • +
  • WebKitWebContext:use-system-appearance-for-scrollbars property.
  • +
  • webkit_web_context_set_use_system_appearance_for_scrollbars() and webkit_web_context_get_use_system_appearance_for_scrollbars().
  • +
+

GStreamer customizations

+

Compile-time platform-specific GStreamer customizations are now done at runtime, using the WEBKIT_GST_QUIRKS and WEBKIT_GST_HOLE_PUNCH_QUIRK environment variables. Setting their value to help will return a help message with the possible values to stderr. A list of the removed CMake defines:

+
    +
  • USE_GSTREAMER_NATIVE_VIDEO
  • +
  • USE_GSTREAMER_NATIVE_AUDIO
  • +
  • USE_GSTREAMER_TEXT_SINK
  • +
  • USE_GSTREAMER_HOLEPUNCH
  • +
  • USE_WPEWEBKIT_PLATFORM_WESTEROS
  • +
  • USE_WPEWEBKIT_PLATFORM_BCM_NEXUS
  • +
  • USE_WPEWEBKIT_PLATFORM_AMLOGIC
  • +
  • USE_WPEWEBKIT_PLATFORM_RPI
  • +
  • USE_WPEWEBKIT_PLATFORM_BROADCOM
  • +
  • USE_WESTEROS_SINK
  • +
+

Web Platform changes

+

The changes to supported Web Platform features between releases of WebKit are always substantial, and for that reason listing all of those changes here would be a major endeavour. The following is an incomplete list of some of the features that have been enabled, removed, and marked in preview state since 2.44, in no particular order:

+
    +
  • CSS Container/Style Queries
  • +
  • CSS text-wrap-style
  • +
  • CSS background-clip: border-area
  • +
  • CSS text-underline-position: left|right
  • +
  • CSS scrollbar-width
  • +
  • CSS View Transitions
  • +
  • CSS Grid Masonry layout (preview)
  • +
  • CSS ::target-text pseudo element
  • +
  • WebCrypto X25519 algorithm (preview)
  • +
  • AppCache support has been removed
  • +
  • New Promise.try() method
  • +
  • New Observable methods, like .map() and .filter()
  • +
+

Other noteworthy changes

+
    +
  • Suport for the WebP image format is now always enabled.
  • +
  • WebDriver clients may now connect to an already running process, instead of always needing to spawn a new one.
  • +
  • The gst-libav AAC decoders are now disabled due to outstanding bugs. Distributors are encouraged to use the GStreamer FDK AAC decoder (part of gst-plugins-bad) instead.
  • +
+

And much more!

+

WebKit evolves and changes a lot between major stable releases. Listing all changes would not be possible. There are countless bug fixes, performance improvements, new web features supported, and so on. We recommend checking the release notes and the git log for more details.

+

The WPE WebKit team is already working on the 2.48 release, schedule for early next year. Until then!

+ + + +
+
+
+
+
+
+ + This article was written by Claudio Saavedra.

Claudio is long-time WebKit contributor from Igalia, working in different areas of WebKit, WPE, and the stack around it. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/index.html b/update-11ty/blog/index.html new file mode 100644 index 000000000..0ff74001f --- /dev/null +++ b/update-11ty/blog/index.html @@ -0,0 +1,326 @@ + + + + + + + + + + + + Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

Blog

+

Don’t miss any news related to WPE – our blog aims to share information regarding WPE: explainers, how-to articles and general information regarding WPE, WebKit +and the Web platform. Also check out the official WebKit blog regarding news on the engine itself.

+
+
+
  1. + + +

    WPE WebKit 2.46 highlights

    +

    A review of the most significant changes coming in WPE WebKit 2.46.

    +
  2. + + +

    Update on the Layer Based SVG Engine (LBSE) in WebKit

    +

    A look back at the last seven months of development on the Layer Based SVG Engine (LBSE) in WebKit.

    +
  3. + + +

    WPE WebKit 2.44 highlights

    +

    The WPE team released WPE WebKit 2.44 a few weeks ago. Let's have a look at the most significant changes to the port for this release cycle.

    +
  4. + + +

    Use Case: Server-side headless rendering

    +

    Server-side rendering is another interesting use case for WPE that can unleash the potential of the Web platform for different use cases.

    +
  5. + + +

    A New WPE Backend Using EGLStream

    +

    WPE backends allow adapting the web engine to the particularities of +the graphics stack of the devices where it needs to run. In this article +we cover their basics and build a WPE WebKit backend from scratch.

    +
  6. + + +

    Integrating WPE: URI Scheme Handlers and Script Messages

    +

    This post explores some of the ways in which WPE WebKit can integrate with +its surrounding environment, in order to expose platform functionality to +Web content seamlessly.

    +
  7. + + +

    Status of the new SVG engine in WebKit

    +

    Let's take a detour from the previous WPE architecture related posts to other aspects of our work on WebKit at Igalia. Today, the status of the development of WebKits' new SVG engine is presented, along with an introduction to the topic, and an outlook for 2023.

    +
+ +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/blog/status-of-lbse-in-webkit.html b/update-11ty/blog/status-of-lbse-in-webkit.html new file mode 100644 index 000000000..f6358812d --- /dev/null +++ b/update-11ty/blog/status-of-lbse-in-webkit.html @@ -0,0 +1,400 @@ + + + + + + + + + + + + Update on the Layer Based SVG Engine (LBSE) in WebKit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+

Update on the Layer Based SVG Engine (LBSE) in WebKit

+ + + + + +
+ +
+ +

This blog entry gives an update on what we at Igalia have done on upstreaming and development of LBSE in WebKit in the last seven months. For an explanation of +what LBSE is and how it is related to WPE, see this previous entry as a refresher.

+
+
+ The Igalia logo + +
+
+ The Wix logo + +
+
+

Thanks to generous funding by Wix, which extensively uses SVG in their products and has a broad +knowledge of the SVG peculiarities across browser engines, LBSE has made great progress in the past seven months. During this period, several advanced SVG painting +features were implemented (e.g. clip-paths, masks, gradients, patterns), along with important performance improvements that expanded the new engine’s capabilities +and stability. All this was possible thanks to Wix’s decision to address their problems by funding upstream work at the core of WebKit, instead of accepting +the status-quo and implementing case-by-case fixes and workarounds on their end. As a result, WebKit-based browsers now benefit from the results of this fruitful +collaboration, which we’ll try to explain in more detail in this blog post.

+

Project kick off and WebKit Contributors Meeting

+

In October 2023 we started the project mostly by thinking about the design and roadmap. We also did some general SVG bug fixing. For example, visual overflow computation for SVG renderers was corrected, which fixed quite a few SVG pixel tests. Various visual bugs were also fixed, such as unnecessary repainting when viewBox is used on <svg> elements, and incorrect clipping for outermost <svg> elements

+

Also in the same month, we attended the WebKit Contributors Meeting, where we presented a talk on the LBSE (slides are available here). The feedback on LBSE at the meeting was very positive. Giving the talk early on in the process actually helped us since we needed to have a good design in place.

+

Supporting SVG resources

+

The main focus in October 2023 was introducing the SVG resource concept, as already outlined in the WebKit Contributors Meeting talk. Thus, we started with adding a base SVG resource class: RenderSVGResourceContainer . This class was kept as simple as possible, with no support for resource invalidation or repainting logic. The main task of RenderSVGResourceContainer is to take care of registration so that the resource can be looked up by its clients.

+

For the first SVG resource to implement, we chose the SVG <clip-path> element, so we landed RenderSVGResourceClipper. To comply with the specification, the RenderSVGResourceClipper implementation produces 1-bit masks and uses a special rendering mode:

+
    +
  • fill-opacity/stroke-opacity/opacity set to 1
  • +
  • masker/filter not applied when rendering the children
  • +
  • fill set to solid black and stroke set to none
  • +
+

The initial implementation did not use caching of ImageBuffers for clipping, but relied on Porter-Duff DestinationIn/SourceOver compositing operations to achieve the same effect, but faster. By integrating RenderSVGResourceClipper properly into RenderLayer, it aligned SVG clipping with HTML/CSS clipping.

+

Finally, the implementation prefers a pure clipping solution internally, as in legacy rendering, but for more complicated clip-paths (for example when the clip-path involves text content), a fallback to a mask is done.

+

Resource invalidation handling

+

After introducing the first SVG resource (RenderSVGResourceClipper), we noticed some issues with handling invalidations for it, such as adding to clip-path contents. In the legacy engine, invalidations have been handled through layouting. This caused various problems: for one, it could cause calling the setNeedsLayout method from within layout, which meant the invalidation chain depended on the DOM order.

+

In November 2023, an implementation landed that avoided using layout for resource invalidation. Instead, on dynamic updates, the style system is used to determine the appropriate action:

+
    +
  • For non resources, changes that cause renderer geometry changes, like changing the x value of a <rect> element, still require a relayout. For visual changes not affecting geometry, like changing the fill color of a <rect>, a repaint action is enough.
  • +
  • For resources, the resource is invalidated/updated with the change and any of its clients are repainted using the new resource.
  • +
+

Support for masks

+

With improved support for SVG resource invalidation, in late November 2023 we were ready to upstream support for the next SVG resource, RenderSVGResourceMasker.

+

Like the support for clip-path, RenderSVGResourceMasker started out without caching image buffers and relied on creating temporary image buffers at rendering time. Mask content invalidations/changes were supported out of the box since we had improved resource invalidation handling (see above).

+

Support for gradients

+

In early January 2024, support for SVG gradients was upstreamed. Gradients are a kind of SVG resource that is a bit different to the previously implemented clipping paths and masks because it is a paint server, so a helper class for that called SVGPaintServerHandling and a base class RenderSVGResourcePaintServer were introduced. The main difference is in invalidation: paint servers simply need a repaint of all its clients on invalidation, whereas clipping paths/masks may need to do more work; i.e., masks underlying image buffers need to be updated before its clients can be repainted.

+

Support for patterns and markers

+

By the end of January 2024, support for SVG patterns was upstreamed. In the first implementation, no image buffer caching was implemented in order to keep things clean and simple. This implementation is different from the legacy implementation because the pattern contents are being rendered through pattern content layers (see RenderLayer::paintSVGResourceLayer). To make this work, RenderSVGResourcePattern has to set up the graphics context matrix correctly before calling paintSVGResourceLayer.

+

Around that same time, we implemented the next to last SVG resource on our TODO list, namely SVG markers.

+

SVG filters

+

In February 2024, Apple started work on supporting SVG filters in LBSE. A first iteration managed to fix a lot of the official SVG filter tests, but it turned out a filter regression had to be fixed first. Moreover, the initial work uncovered issues with the HTML/CSS filters implementation that need to be fixed in general. Finally, another reason why this support takes more time than some other features is that there is a strong requirement to make the support efficient in both memory usage and overall (re)painting speed. Still, the early results are very promising!

+

Cycle detection

+

It is quite easy in SVG to cause direct circular references:

+
<svg>
+    <defs>
+        <pattern id="p" xlink:href="#p" />
+    </defs>
+    ...
+</svg>
+

It is also possible to cause indirect circular references:

+
<svg>
+    <defs>
+        <mask id="z" />
+            <rect mask="url(#z)" />
+        </mask>
+    </defs>
+    <ellipse mask="url(#z)" />
+</svg>
+

The legacy engine solved this in an ad-hoc way in various places in the engine; it tried to break cycles before rendering, but still needed cycle protections in various places, since the solution was never unified or complete.

+

In February 2024 we provided a unified solution for LBSE by introducing SVGVisitedRendererTracking; see this commit for more. In the new approach, we don’t attempt to remove cycles, but detect them everywhere upon usage and stop processing in well-defined ways, all centralized in SVGVisitedRendererTracking.

+

Nested mask/pattern slowness

+

In April 2024, we addressed the slowness problems with nested masks/patterns. As an example, consider this for nested masks:

+
<svg>
+    <defs>
+        <mask id="z" />
+            <rect id="1" mask="url(#y)" />
+            <rect id="2" mask="url(#y)" />
+            ...
+        </mask>
+        <mask id="y" />
+            <rect id="1" mask="url(#x)" />
+            <rect id="2" mask="url(#x)" />
+            ...
+        </mask>
+        ...
+    </defs>
+    <ellipse mask="url(#z)" />
+</svg>
+

For this example, the complexity can be increased at will by adding more masks and contents per mask.

+

The solution was twofold:

+
    +
  • For masks, we realized bounding box calculations for a mask were not affected by masks used in the mask contents, so we could cut off bounding box calculations for nested masks.
  • +
  • For both masks and patterns, we added caching of image buffers per resource client so nested masks/patterns that are already encountered can reuse the image buffer cache.
  • +
+

See optimizations here for nested masks and patterns.

+

Next steps

+

For the short and mid-term, the plan is to make LBSE at least as good as legacy in regards to test coverage; i.e., all tests that pass in legacy should pass in LBSE. We have made a lot of progress over the +last seven months just because of the amount of SVG resources that were implemented, but for example ,we will need to have SVG filters in place to pass this goal.

+

Another goal is to make sure LBSE passes all security requirements, as failing that would be a blocker to replacing the current engine. Fortunately, we are already taking this into account in several ways, such as adopting a lot of good smart pointer practices.

+

Finally, a big goal will be for LBSE to perform well on certain benchmarks like MotionMark, since WebKit has a golden rule to never ship a performance regression. So far there has not been an explicit focus +on performance, and we know there are likely optimizations possible in RenderLayer usage, both in reducing the number of RenderLayer objects we create in certain situations as well as a possible reduction in complexity of RenderLayer for LBSE usage.

+

All in all, we are very pleased with the results and the progress we made in the last seven months. We at Igalia look forward to finishing the work to get the new engine in a shippable state in the near future!

+ + + +
+
+
+
+
+
+ + This article was written by Rob Buis.

Longtime WebKit/Blink hacker with a preference for open source. +
+
+
+
+
+
+ + + +
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/code/index.html b/update-11ty/code/index.html new file mode 100644 index 000000000..533e699bd --- /dev/null +++ b/update-11ty/code/index.html @@ -0,0 +1,14 @@ + + + + + + + + +

If you can see this, you should be redirecting this URL to /about/get-wpe.html.

+ + \ No newline at end of file diff --git a/update-11ty/css/fonts.css b/update-11ty/css/fonts.css new file mode 100644 index 000000000..4ca2ac07a --- /dev/null +++ b/update-11ty/css/fonts.css @@ -0,0 +1,114 @@ +/* Normal */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-variant: common-ligatures; + font-weight: 400; + src: + local('Source Sans Variable'), + local('Source Sans 3 VF'), + local('Source Sans Pro'), + local('Source Sans Pro Regular'), + local('SourceSansPro-Regular'), + local('Source Sans 3'), + local('SourceSans3-Regular'), + url('../assets/SourceSans3VF-Roman.ttf.woff2') format('woff2'), + url('../assets/SourceSans3VF-Roman.ttf.woff') format('woff'), + url('../assets/SourceSans3VF-Roman.ttf') format('truetype'), + url('../assets/SourceSans3VF-Roman.otf') format('opentype'); +} + +/* Bold */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-variant: common-ligatures; + font-weight: 700; + src: + local('Source Sans Variable'), + local('Source Sans 3 VF'), + local('Source Sans Pro Bold'), + local('SourceSansPro-Bold'), + local('Source Sans 3'), + local('SourceSans3-Bold'), + url('../assets/SourceSans3VF-Roman.ttf.woff2') format('woff2'), + url('../assets/SourceSans3VF-Roman.ttf.woff') format('woff'), + url('../assets/SourceSans3VF-Roman.ttf') format('truetype'), + url('../assets/SourceSans3VF-Roman.otf') format('opentype'); +} + +/* Light */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-variant: common-ligatures; + font-weight: 100; + src: + local('Source Sans Variable'), + local('Source Sans 3 VF'), + local('Source Sans Pro Light'), + local('SourceSansPro-Light'), + local('Source Sans 3'), + local('SourceSans3-Light'), + url('../assets/SourceSans3VF-Roman.ttf.woff2') format('woff2'), + url('../assets/SourceSans3VF-Roman.ttf.woff') format('woff'), + url('../assets/SourceSans3VF-Roman.ttf') format('truetype'), + url('../assets/SourceSans3VF-Roman.otf') format('opentype'); +} + +/* Italic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-variant: common-ligatures; + font-weight: 400; + src: + local('Source Sans Variable'), + local('Source Sans 3 VF'), + local('Source Sans Pro Italic'), + local('SourceSansPro-Italic'), + local('Source Sans 3'), + local('SourceSans3-Italic'), + url('../assets/SourceSans3VF-Italic.ttf.woff2') format('woff2'), + url('../assets/SourceSans3VF-Italic.ttf.woff') format('woff'), + url('../assets/SourceSans3VF-Italic.ttf') format('truetype'), + url('../assets/SourceSans3VF-Italic.otf') format('opentype'); +} + +/* Bold Italic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-variant: common-ligatures; + font-weight: 700; + src: + local('Source Sans Variable'), + local('Source Sans 3 VF'), + local('Source Sans Pro Bold Italic'), + local('SourceSansPro-BoldItalic'), + local('Source Sans 3'), + local('SourceSans3-BoldItalic'), + url('../assets/SourceSans3VF-Italic.ttf.woff2') format('woff2'), + url('../assets/SourceSans3VF-Italic.ttf.woff') format('woff'), + url('../assets/SourceSans3VF-Italic.ttf') format('truetype'), + url('../assets/SourceSans3VF-Italic.otf') format('opentype'); +} + +/* Light Italic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-variant: common-ligatures; + font-weight: 100; + src: + local('Source Sans Variable'), + local('Source Sans 3 VF'), + local('Source Sans Pro Light Italic'), + local('SourceSansPro-LightItalic'), + local('Source Sans 3'), + local('SourceSans3-LightItalic'), + url('../assets/SourceSans3VF-Italic.ttf.woff2') format('woff2'), + url('../assets/SourceSans3VF-Italic.ttf.woff') format('woff'), + url('../assets/SourceSans3VF-Italic.ttf') format('truetype'), + url('../assets/SourceSans3VF-Italic.otf') format('opentype'); +} diff --git a/update-11ty/css/prism.css b/update-11ty/css/prism.css new file mode 100644 index 000000000..4f42c07c4 --- /dev/null +++ b/update-11ty/css/prism.css @@ -0,0 +1,2 @@ +/* PrismJS 1.29.0 theme from https://prismjs.com/download.html */ +code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:0.85rem;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.35;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f1f1f1}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} diff --git a/update-11ty/css/stylish-portfolio.css b/update-11ty/css/stylish-portfolio.css new file mode 100644 index 000000000..94c95d807 --- /dev/null +++ b/update-11ty/css/stylish-portfolio.css @@ -0,0 +1,495 @@ +.navbar-nav { + margin-right: 4rem; +} + +strong { + font-weight: 800; +} + +body, +html { + width: 100%; + height: 100%; +} + +body { + font-family: 'Source Sans Pro'; +} + +ul { margin: 1rem auto; padding-left: 4rem; } +ul li { text-align: left; } + +.btn-xl { + padding: 1.25rem 2.5rem; +} + +.content-section { + padding-top: 7.5rem; + padding-bottom: 7.5rem; +} + +.content-section.small-section { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.content-section-heading h2 { + font-size: 3rem; +} + +.content-section-heading h3 { + font-size: 1rem; + text-transform: uppercase; +} + +.release-heading { + margin-bottom: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 700; +} + +.text-faded { + color: rgba(255, 255, 255, 0.7); +} + +/* Map */ +.map { + height: 30rem; +} + +@media (max-width: 992px) { + .map { + height: 75%; + } +} + +.map iframe { + pointer-events: none; +} + +.scroll-to-top { + position: fixed; + right: 15px; + bottom: 15px; + display: none; + width: 50px; + height: 50px; + text-align: center; + color: white; + background: rgba(52, 58, 64, 0.5); + line-height: 45px; +} + +.scroll-to-top:focus, .scroll-to-top:hover { + color: white; +} + +.scroll-to-top:hover { + background: #343a40; +} + +.scroll-to-top i { + font-weight: 800; +} + +. + +/* Side Menu */ +#sidebar-wrapper { + position: fixed; + z-index: 2; + right: 0; + width: 250px; + height: 100%; + -webkit-transition: all 0.4s ease 0s; + -moz-transition: all 0.4s ease 0s; + -ms-transition: all 0.4s ease 0s; + -o-transition: all 0.4s ease 0s; + transition: all 0.4s ease 0s; + transform: translateX(250px); + background: #1D809F; + border-left: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar-nav { + position: absolute; + top: 0; + width: 250px; + margin: 0; + padding: 0; + list-style: none; +} + +.sidebar-nav li.sidebar-nav-item a { + display: block; + text-decoration: none; + color: #fff; + padding: 15px; +} + +.sidebar-nav li a:hover { + text-decoration: none; + color: #fff; + background: rgba(255, 255, 255, 0.2); +} + +.sidebar-nav li a:active, +.sidebar-nav li a:focus { + text-decoration: none; +} + +.sidebar-nav > .sidebar-brand { + font-size: 1.2rem; + background: rgba(52, 58, 64, 0.1); + height: 80px; + line-height: 50px; + padding-top: 15px; + padding-bottom: 15px; + padding-left: 15px; +} + +.sidebar-nav > .sidebar-brand a { + color: #fff; +} + +.sidebar-nav > .sidebar-brand a:hover { + color: #fff; + background: none; +} + +#sidebar-wrapper.active { + right: 250px; + width: 250px; + -webkit-transition: all 0.4s ease 0s; + -moz-transition: all 0.4s ease 0s; + -ms-transition: all 0.4s ease 0s; + -o-transition: all 0.4s ease 0s; + transition: all 0.4s ease 0s; +} + +.menu-toggle { + position: fixed; + right: 15px; + top: 15px; + width: 50px; + height: 50px; + text-align: center; + color: #fff; + background: rgba(52, 58, 64, 0.5); + line-height: 50px; + z-index: 999; +} + +.menu-toggle:focus, .menu-toggle:hover { + color: #fff; +} + +.menu-toggle:hover { + background: #343a40; +} + +.card { + min-width: 21rem; + max-width: 100%; +} + +.service-icon { + background-color: #fff; + color: #1D809F; + height: 7rem; + width: 7rem; + display: block; + line-height: 7.5rem; + font-size: 2.25rem; + box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.1); +} + +.callout { + padding: 15rem 0; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.1) 100%); + background-position: center center; + background-repeat: no-repeat; + background-size: cover; +} + +.callout h2 { + font-size: 3.5rem; + font-weight: 700; + display: block; + max-width: 30rem; +} + +.portfolio-item { + display: block; + position: relative; + overflow: hidden; + max-width: 530px; + margin: auto auto 1rem; +} + +.portfolio-item .caption { + display: flex; + height: 100%; + width: 100%; + background-color: rgba(33, 37, 41, 0.2); + position: absolute; + top: 0; + bottom: 0; + z-index: 1; +} + +.portfolio-item .caption .caption-content { + color: #fff; + margin: auto 2rem 2rem; +} + +.portfolio-item .caption .caption-content h2 { + font-size: 0.8rem; + text-transform: uppercase; +} + +.portfolio-item .caption .caption-content p { + font-weight: 300; + font-size: 1.2rem; +} + +@media (min-width: 1300px) { + .card { + min-width: 21rem !important; + } + +} +@media (min-width: 992px) { + .portfolio-item { + max-width: none; + margin: 0; + } + .portfolio-item .caption { + -webkit-transition: -webkit-clip-path 0.25s ease-out, background-color 0.7s; + -webkit-clip-path: inset(0px); + clip-path: inset(0px); + } + .portfolio-item .caption .caption-content { + transition: opacity 0.25s; + margin-left: 5rem; + margin-right: 5rem; + margin-bottom: 5rem; + } + .portfolio-item img { + -webkit-transition: -webkit-clip-path 0.25s ease-out; + -webkit-clip-path: inset(-1px); + clip-path: inset(-1px); + } + .portfolio-item:hover img { + -webkit-clip-path: inset(2rem); + clip-path: inset(2rem); + } + .portfolio-item:hover .caption { + background-color: rgba(29, 128, 159, 0.9); + -webkit-clip-path: inset(2rem); + clip-path: inset(2rem); + } + + #webkit-powers-embedded { + padding: 2rem; + background: url(https://www.igalia.com/assets/i/headers/header-industries-tv.jpg); + background-size: contain; + background-repeat: no-repeat; + background-position-y: center + } + + + .footer.footer { + grid-template-columns: 300px auto; + grid-template-rows: repeat(2,auto); + } + .footer.footer a.navbar-brand { + grid-row: 1 / -1; + } + .footer.footer nav.social { + grid-column: 2; + align-self: end; + } + .footer.footer nav.social ul { + margin: 0; + padding: 0; + } + .footer.footer li { + display: inline; + } + .footer.footer li a { + display: inline; + } +} + +table { + margin: 1em auto; + border: 1px solid #eaeaea; + background: #fafafa; +} + +table td, table th { + padding: 0.2em 0.75em; +} + +table thead, table th { + background: #eaeaea; +} + +table tr:nth-child(even) { + background: #efefef; +} + +footer.footer { + padding: 1rem; + +} + +footer.footer .social-link { + display: block; + height: 4rem; + width: 4rem; + line-height: 4.3rem; + font-size: 1.5rem; + background-color: #1D809F; + transition: background-color 0.15s ease-in-out; + box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.1); +} + +footer.footer .social-link:hover { + background-color: #155d74; + text-decoration: none; +} + +a { + color: #1D809F; +} + +a:hover, a:focus, a:active { + color: #155d74; +} + +.text-faded > a { + color: rgba(255, 255, 255, 0.7); +} + +.text-faded > a:hover, .text-faded > a:focus, .text-faded > a:active { + color: rgba(230, 230, 230, 0.7); +} + +.btn-primary { + background-color: #1c77c7 !important; + border-color: #1D809F !important; + color: #fff !important; +} + +.btn-primary:hover, .btn-primary:focus, .btn-primary:active { + background-color: #155d74 !important; + border-color: #155d74 !important; +} + +.btn-secondary { + background-color: #ecb807 !important; + border-color: #ecb807 !important; + color: #fff !important; +} + +.btn-secondary:hover, .btn-secondary:focus, .btn-secondary:active { + background-color: #ba9106 !important; + border-color: #ba9106 !important; +} + +.btn-dark { + color: #fff !important; +} + +.btn { + box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.1); + font-weight: 700; +} + +.bg-primary { + background-color: #1593ED !important; +} + +.bg-tertiary { + background-color: black !important; +} + +.text-primary { + color: #1D809F !important; +} + +.text-secondary { + color: #ecb807 !important; +} + + +#contact { + padding-top: 1.5rem; + padding-bottom: 0.5rem; +} + +footer.footer { + padding: 1rem; + display: grid; +} + +footer .container { + display: flex; + align-self: center; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-top: 1rem; + margin-bottom: 1rem; +} + +#webkit-powers-embedded { + padding: 0 !important; +} + +#about-code-section .badge { + margin-left: 1rem; +} + +.text-light a { + color: white; +} + +blockquote { + background-color: rgba(230, 230, 230, 0.7); +} + +.author-line { + margin-top: -2rem; + border: 2px solid lightgray; + width: 100%; +} + +hr.author-line + div { + margin-top: 1.5rem; + color: #666; +} +hr.author-line + div > img.circle-mask { + margin-top: -1rem; +} + +.circle-mask { + width: 8rem; + height: auto; + float: left; + margin-right: 2rem; + margin-bottom: 2rem; + border-radius: 50%; +} diff --git a/update-11ty/css/stylish-portfolio.min.css b/update-11ty/css/stylish-portfolio.min.css new file mode 100644 index 000000000..e4bb538fc --- /dev/null +++ b/update-11ty/css/stylish-portfolio.min.css @@ -0,0 +1 @@ +body,html{width:100%;height:100%}body{font-family:'Source Sans Pro'}.btn-xl{padding:1.25rem 2.5rem}.content-section{padding-top:7.5rem;padding-bottom:7.5rem}.content-section.small-section{padding-top:2rem;padding-bottom:2rem}.content-section-heading h2{font-size:3rem}.content-section-heading h3{font-size:1rem;text-transform:uppercase}h1,h2,h3,h4,h5,h6{font-weight:700}.text-faded{color:rgba(255,255,255,.7)}.map{height:30rem}@media (max-width:992px){.map{height:75%}}.map iframe{pointer-events:none}.scroll-to-top{position:fixed;right:15px;bottom:15px;display:none;width:50px;height:50px;text-align:center;color:#fff;background:rgba(52,58,64,.5);line-height:45px}.scroll-to-top:focus,.scroll-to-top:hover{color:#fff}.scroll-to-top:hover{background:#343a40}.scroll-to-top i{font-weight:800}.masthead{position:relative;display:table;width:100%;height:auto;padding-top:4rem;padding-bottom:4rem;background:linear-gradient(90deg,rgba(255,255,255,.1) 0,rgba(255,255,255,.1) 100%);background-position:center center;background-repeat:no-repeat;background-size:cover}.masthead h1{font-size:4rem;margin:0;padding:0}@media (min-width:992px){.masthead h1{font-size:5.5rem}}.masthead.small-header{padding-top:2rem;padding-bottom:2rem;min-height:10rem;height:15rem}#sidebar-wrapper{position:fixed;z-index:2;right:0;width:250px;height:100%;-webkit-transition:all .4s ease 0s;-moz-transition:all .4s ease 0s;-ms-transition:all .4s ease 0s;-o-transition:all .4s ease 0s;transition:all .4s ease 0s;transform:translateX(250px);background:#1d809f;border-left:1px solid rgba(255,255,255,.1)}.sidebar-nav{position:absolute;top:0;width:250px;margin:0;padding:0;list-style:none}.sidebar-nav li.sidebar-nav-item a{display:block;text-decoration:none;color:#fff;padding:15px}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2)}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none}.sidebar-nav>.sidebar-brand{font-size:1.2rem;background:rgba(52,58,64,.1);height:80px;line-height:50px;padding-top:15px;padding-bottom:15px;padding-left:15px}.sidebar-nav>.sidebar-brand a{color:#fff}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:0 0}#sidebar-wrapper.active{right:250px;width:250px;-webkit-transition:all .4s ease 0s;-moz-transition:all .4s ease 0s;-ms-transition:all .4s ease 0s;-o-transition:all .4s ease 0s;transition:all .4s ease 0s}.menu-toggle{position:fixed;right:15px;top:15px;width:50px;height:50px;text-align:center;color:#fff;background:rgba(52,58,64,.5);line-height:50px;z-index:999}.menu-toggle:focus,.menu-toggle:hover{color:#fff}.menu-toggle:hover{background:#343a40}.service-icon{background-color:#fff;color:#1d809f;height:7rem;width:7rem;display:block;line-height:7.5rem;font-size:2.25rem;box-shadow:0 3px 3px 0 rgba(0,0,0,.1)}.callout{padding:15rem 0;background:linear-gradient(90deg,rgba(255,255,255,.1) 0,rgba(255,255,255,.1) 100%);background-position:center center;background-repeat:no-repeat;background-size:cover}.callout h2{font-size:3.5rem;font-weight:700;display:block;max-width:30rem}.portfolio-item{display:block;position:relative;overflow:hidden;max-width:530px;margin:auto auto 1rem}.portfolio-item .caption{display:flex;height:100%;width:100%;background-color:rgba(33,37,41,.2);position:absolute;top:0;bottom:0;z-index:1}.portfolio-item .caption .caption-content{color:#fff;margin:auto 2rem 2rem}.portfolio-item .caption .caption-content h2{font-size:.8rem;text-transform:uppercase}.portfolio-item .caption .caption-content p{font-weight:300;font-size:1.2rem}@media (min-width:992px){.portfolio-item{max-width:none;margin:0}.portfolio-item .caption{-webkit-transition:-webkit-clip-path .25s ease-out,background-color .7s;-webkit-clip-path:inset(0);clip-path:inset(0)}.portfolio-item .caption .caption-content{transition:opacity .25s;margin-left:5rem;margin-right:5rem;margin-bottom:5rem}.portfolio-item img{-webkit-transition:-webkit-clip-path .25s ease-out;-webkit-clip-path:inset(-1px);clip-path:inset(-1px)}.portfolio-item:hover img{-webkit-clip-path:inset(2rem);clip-path:inset(2rem)}.portfolio-item:hover .caption{background-color:rgba(29,128,159,.9);-webkit-clip-path:inset(2rem);clip-path:inset(2rem)}}table{margin:1em auto;border:1px solid #eaeaea;background:#fafafa}table td,table th{padding:.2em .75em}table th,table thead{background:#eaeaea}table tr:nth-child(even){background:#efefef}footer.footer{padding-top:5rem;padding-bottom:5rem}footer.footer .social-link{display:block;height:4rem;width:4rem;line-height:4.3rem;font-size:1.5rem;background-color:#1d809f;transition:background-color .15s ease-in-out;box-shadow:0 3px 3px 0 rgba(0,0,0,.1)}footer.footer .social-link:hover{background-color:#155d74;text-decoration:none}a{color:#1d809f}a:active,a:focus,a:hover{color:#155d74}.text-faded>a{color:rgba(255,255,255,.7)}.text-faded>a:active,.text-faded>a:focus,.text-faded>a:hover{color:rgba(230,230,230,.7)}.btn-primary{background-color:#1d809f!important;border-color:#1d809f!important;color:#fff!important}.btn-primary:active,.btn-primary:focus,.btn-primary:hover{background-color:#155d74!important;border-color:#155d74!important}.btn-secondary{background-color:#ecb807!important;border-color:#ecb807!important;color:#fff!important}.btn-secondary:active,.btn-secondary:focus,.btn-secondary:hover{background-color:#ba9106!important;border-color:#ba9106!important}.btn-dark{color:#fff!important}.btn{box-shadow:0 3px 3px 0 rgba(0,0,0,.1);font-weight:700}.bg-primary{background-color:#1d809f!important}.text-primary{color:#1d809f!important}.text-secondary{color:#ecb807!important} \ No newline at end of file diff --git a/update-11ty/css/v2.css b/update-11ty/css/v2.css new file mode 100755 index 000000000..8b1ec6438 --- /dev/null +++ b/update-11ty/css/v2.css @@ -0,0 +1,764 @@ +img[src*="placeholder"] { +max-height: 4em; +} + +html { + --mainColMax: 75rem; +/* + --colorMain: #1593ED; +*/ + --colorMain: #0D71B9; + --dashH: linear-gradient(90deg, currentColor 25%, + transparent 25% 75%, + currentColor 75%) 0 100% / 8px 1px repeat-x; + --dashV: linear-gradient(180deg, currentColor 25%, + transparent 25% 75%, + currentColor 75%) 0 0 / 1px 8px repeat-y; + --colorCodeBg: #F1F1F1; + height: auto; + min-height: 100%; + max-width: 100vw; +} + +:target { + scroll-margin-top: 7rem; +} + +@supports not (-ms-ime-align: auto) { + /* + * Resets for
/, see: + * https://css-tricks.com/two-issues-styling-the-details-element-and-how-to-solve-them/ + */ + details > summary { + cursor: pointer; + } + details > summary > * { + display: inline; + } +} + +details { + border: 2px solid #f1f1f1; + background: #f1f1f1; + border-radius: 0.5em; +} +details > summary { + color: #555; +} +details > summary, details > div { + padding: 0.5rem 1rem; +} +details > div { + background: #fff; + border-top: 1px solid #eeeeff; +} +details > div > pre { + margin: 0; + padding: 0; + background: none; + white-space: pre-wrap; +} + +body { + min-height: 100%; + margin: 0; + padding: 5rem 0 0; +} + +:is(nav, footer).global, header.page { + grid-column: 1 / -1; +} +main { + grid-column: main; +} +main > * { + padding: 0 1rem; +} + +@media (min-width: 75rem) { + body:not(.home) { + display: grid; + grid-template-columns: 1fr [main] minmax(30rem,var(--mainColMax)) 1fr; + grid-template-rows: [main] auto [footer] min-content; + } + body:not(.home) main { + padding-inline: 5rem; + background: url(../assets/img/dash-v.svg) 0 0 / 1px 8px repeat-y; + } +} + +img { + box-sizing: border-box; + max-width: 100%; +} +a[href] { + color: var(--colorMain); + text-decoration-thickness: 0.05em; + text-underline-offset: 0.15em; +} +main a[href^="http"]:not(.btn, [href*="youtube"]) { + background: url(../assets/img/link_ext_icon.svg) 100% 50% / auto 0.9em no-repeat; + padding-right: 0.9em; +} + + +a.igalia.logo.home img { + width: 100px; + padding: 0.75em 0 0.25em; +} + +figure > img.picture { + display: block; + margin: 0 auto; + border-radius: 5px; + box-shadow: 0 4px 8px #aaa; +} + +figure > figcaption { + margin-top: 0.5rem; + text-align: center; + font-size: 80%; + font-style: italic; + color: #555; +} + +h1 {font-size: 3em; line-height: 1.2;} +h2 {font-size: 2.5em; line-height: 1.1;} +h3 {font-size: 1.6em; line-height: 1.2;} +p > big, p.leadin { + font-size: 1.67em; +} + +nav.sitemap ul, +div.arrow-lists > ul { + font-size: 130%; + margin: 0; + padding: 0; + list-style: none; + padding-inline-start: 3em; +} +nav.sitemap ul li, +div.arrow-lists > ul > li { + text-indent: -2em; +} +nav.sitemap > ul li:before, +div.arrow-lists > ul > li::before { + display: inline-block; + content: url(../assets/img/list-arrow.svg); + width: 1em; + text-indent: 0; + margin-inline-end: 1em; +} + +/* Sitemap specific styles */ +nav.sitemap ul li { + font-size: 1.15rem; + line-height: 2rem; +} +nav.sitemap > ul { + margin: 0.5em 1.5em; +} + +.btn-l { + border-radius: 4px; + padding: 0.5rem 1rem; +} + +.cta.btn { + display: inline-block; + background: var(--colorMain); + color: #FFF; + padding: 0.67em 1.33em; + border-radius: 10em; + font-weight: 700; + font-size: 1.5em; + text-decoration: none; + filter: none; +} +.currentPage .cta.btn { + box-shadow: none; +} + + +.global a.igalia.logo.home { + transform: translateX(10px); +} +@media (min-width: 80rem) { + .global a.igalia.logo.home { + transform: translateX(-30px); + } +} + +main > * { + position: relative; + border-width: 1px 0 0 1px; +} +main::before { + content: ''; + grid-column: dash; + grid-row: 1 / -1; + height: 100%; + width: 2px; +} + + + +.full-bleed { + padding-block: 2em 3em; + border-image-outset: 0 100vw; + border-image-slice: 0 fill; + border-image-width: 0; + border-image-repeat: stretch; + mix-blend-mode: multiply; + border-image-source: linear-gradient(0deg,#EAFEFE,#EAFEFE); +} +.dotsep { + padding-block: 2rem; + position: relative; +} +.full-width { + border-image: none; +} + +.dotsep::before, .full-width::before { + content: ''; + background: var(--dashH); + height: 1px; + width: 100%; +} +.full-width::after, .dotsep::after, h2:has(+slide-show)::after { + content: url(../assets/img/border-dot-black.svg); + position: absolute; + z-index: 1234; + width: 11px; + height: 11px; + left: -5px; + top: -5px; + font-size: 11px; +} +.dotsep::before { + position: absolute; + z-index: 1224; + top: 0; + left: 0; +} +.full-width::before { + display: block; + grid-column: 1 / -1; + grid-row: 1; + background: var(--dashH); + height: 1px; + width: 100%; +} +.full-width::after { + grid-column: dash; + grid-row: 1 / -1; + height: 100%; + background: var(--dashV); + background-position: 5px 0; +} + + +nav.global { + position: fixed; + top: 0; + left: 0; + right: 0; + width: 100%; + background: hsla(0deg,0%,100%,0.9); + z-index: 12345; + backdrop-filter: blur(10px); + height: 5rem; +} +nav.global div { + display: flex; + justify-content: space-between; + gap: 1em; + max-width: var(--mainColMax); + margin: 0 auto; + height: 100%; + background: var(--dashH); +} +nav.global ul { + margin: 0; + padding: 0; + list-style: none; +} +nav.global ul li a { + display: block; + padding: 0.5em 1em; + text-decoration: none; +} + +nav.global .cta.btn { + font-size: 1em; +} +nav.global .burger { + z-index: 9999; + height: 5em; + width: 5em; + background: url(../assets/img/menu-x.svg) center no-repeat; + background-color: hsl(205,85.7%,50.6%); + border-left: 1px solid; + color: #FFF; + text-indent: -20220930px; +} + +@media (max-width: 719px) { + nav.global ul.off { + display: none; + } + nav.global ul.off + .burger { + background-image: url(../assets/img/menu.svg); + } + nav.global ul { + font-size: 1.25em; + position: fixed; + top: 4em; + right: 0; + max-width: 67vw; + min-width: min-content; + box-shadow: 0 0 0.5em 0.5em #FFF, 0 0 1em 0.25em #FFF; + background: #FFF; + } + nav.global ul li a { + background: hsl(205,85.7%,50.6%); + color: #FFF; + border-right: 1px solid #FFF; + } + nav.global ul li + li a { + border-top: 1px solid #FFF; + } + nav.global ul li.currentPage a { + background: hsl(205,85.7%,80.6%); + color: initial; + text-indent: -1.5em; + } + nav.global ul li.currentPage a::before { + content: "➤"; + margin-inline-end: 0.5em; + text-shadow: 0 0 0.25em hsl(205,85.7%,80.6%), 0 0 0.25em hsl(205,85.7%,80.6%), 0 0 0.25em hsl(205,85.7%,80.6%); + } + nav.global ul li a.btn.cta { + display: block; + margin: 0; + padding: 0.5em 1em; + border-radius: 0; + box-shadow: none; + font-weight: normal; + } +} + +@media (min-width: 720px) { + nav.global .burger { + display: none; + } + nav.global div { + padding: 0; + } + nav.global ul { + display: flex; + } + nav.global ul li { + display: flex; + align-items: center; + } + nav.global ul li.currentPage::before { + content: ''; + position: absolute; + z-index: 1; + top: 50%; + bottom: 0; + left: 50%; + right: 0; + background: + var(--dashV), + linear-gradient(0deg, #FFFF 2px, transparent 2px) + ; + background-size: 1px 0.5em, auto; + } + nav.global ul li.currentPage { + position: relative; + } + nav.global ul li.currentPage a { + position: relative; + z-index: 2; + padding: 0; + padding-block: 0.25em; + margin: 1em; + background: var(--dashH); + background-size: 0.5em 1px; + background-position: 50% 100%; + background-color: #FFF; + color: inherit; + } + nav.global ul li.currentPage a::before { + content: ''; + } + nav.global ul li.currentPage ~ li { + background: linear-gradient(0deg, #FFFF 2px, transparent 2px); + } + nav.global .cta.btn { + padding: 0.33em 0.75em 0.25em; + margin-inline-end: 1rem; + } +} + + +nav.sidebar { + position: sticky; + z-index: 10000; + top: 5.66rem; + background: #FFFC; + backdrop-filter: blur(2rem); +} +nav.sidebar ul { + margin: 0; + padding: 0; + list-style: none; +} +nav.sidebar ul { + display: flex; + justify-content: center; + gap: 0.33em; +} +nav.sidebar a[href] { + padding: 0.5em; + margin-block: 0.5em; + border: 1px solid currentColor; + text-decoration-thickness: 1px; + text-underline-offset: 0.125em; + background: #EAFEFE; +} +@media (max-width: 30rem) { + nav.sidebar a[href] { + font-size: smaller; + white-space: pre; + } +} +nav.sidebar .currentPage a[href] { + color: #FFF; + background: #000; + text-decoration: none; +} + +@media (min-width: 86rem) { + nav.sidebar { + position: sticky; + top: 8em; + margin-left: -11rem; + height: 0; + z-index: 100; + } + nav.sidebar ul { + display: block; + width: max-content; + } + nav.sidebar a[href] { + display: block; + max-width: 15ch; + border-style: dashed; + background: #FFF; + } +} + +header.page { + position: relative; + font-size: 0.75em; +} +header.page h1 { + padding-bottom: 0.5em; + font-size: 4.5em; + background: url(../assets/img/graphic-title-blue.svg) 0 100% / 7rem auto no-repeat; +} +header.page p { + font-size: 1.6em; +} +header.page > time { + margin: 1rem 0; + display: block; +} + +pre, code { + background: var(--colorCodeBg); +} + +pre { + white-space: pre-wrap; + border-radius: 0.5em; + padding-block: 1em; + padding-inline: 1.5em; +} + +code { + color: #333; + padding: 0.1em 0.3em; + border-radius: 0.25em; +} + +main section div h3 { + grid-column: 1; + grid-row: 1; +} +main section div:not(.text) > h3 { + align-self: end; +} +main section div p { + grid-column: 1; + grid-row: 2; + margin-top: 0; +} +main section div:not(.text) > p { + align-self: start; +} +main section div > :last-child { + grid-column: 2; + grid-row: 1 / -1; + justify-self: center; +} + +.explore-embedded div.text > * { + align-self: baseline; +} +.explore-embedded header { + padding-block: 4rem; +} +@media (max-width: 65rem) { + .explore-embedded header { + display: block; + } +} + + +@media (min-width: 45rem) { + .c2, .c4, .c5, + .gallery + { + display: grid; + } +} + +.c2, .c4, .c5 { + grid-template-columns: 40% 45%; + grid-template-rows: repeat(2,min-content); + gap: 0 15%; + align-items: first baseline; +} +.c2 { + grid-template-columns: repeat(2,1fr); +} +.c4 { + grid-template-columns: repeat(4,1fr); + grid-template-rows: min-content; + gap: 0 1em; +} +.c5 { + grid-template-columns: repeat(5,1fr); + gap: 2em; +} + +.gallery { + gap: 10%; + margin: 0; + padding-block: 1.5em; + padding-inline: 0; + list-style: none; + align-items: center; +} +main > .resources .gallery { + align-items: start; + padding-left: 0; +} + +@media (max-width: 45rem) { + .c4 { + display: grid; + grid-template-columns: repeat(2,1fr); + } +} + +:is(.community, .developers, .resources) header { + padding: 5em 0 2.5em; + position: relative; +} +:is(.community, .resources) header h2 { + margin-bottom: 0; +} + +header .decoration { + position: absolute; + bottom: -3px; + right: -5rem; + width: 10rem; +} +@media (max-width: 45rem) { + header .decoration { + display: none; + } +} + +.cards li { + display: flex; + flex-direction: column; + align-items: start; +} +.cards li h3 { + align-self: auto; + margin: 0.25em 0; + font-size: 1.1em; +} +.cards li > time, +.cards li > span { + font-size: smaller; +} +.cards li p { + margin-top: 0.75em; +} +.blog.cards li img { + order: -5; + height: 4em; + width: 4em; + border-radius: 9999px; +} +.blog.cards li time { + order: -4; +} +.blog.cards li h3 { + order: -3; +} +.blog.cards li span { + order: -2; +} +.blog.cards li p { + order: -1; +} + +.success-top { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 0.5rem 4rem; + padding-block: 3rem 1rem; +} +.success-top img { + height: 7rem; +} +.success-top img[alt*="WPE"] { + height: 5rem; +} + + +footer.global { + position: relative; + padding: 4rem 1rem; + background: url(../assets/img/dash-h.svg) 0 0 / 8px 1px repeat-x; + background-color: var(--colorMain); + color: #FFF; + font: 1.1em monospace; +} + +footer.global div { + display: grid; + grid-template-columns: 100px 1fr; + gap: 1em; +} +footer.global div > :last-child { + grid-column: span 2; +} + +nav.pagination { + display: block; + color: #88b; + padding: 0; + margin: 0; + text-align: center; + font-feature-settings: "pnum" 0; +} +nav.pagination > ol { + display: inline-block; + padding: 0; + margin: 0; +} +nav.pagination > ol > li { + display: inline-block; + padding: 0; + margin: 0; +} +nav.pagination > ol > li > a, +nav.pagination > ol > li > span { + display: inline; + text-decoration: none; + margin: 0; + padding: 0.35rem 0.75rem; + font-weight: normal; +} +nav.pagination > ol > li > a:hover { + background-color: #eaeaee; +} +nav.pagination > ol > li > a[aria-current="page"] { + background-color: var(--colorMain); + color: #eef; +} + +@media (min-width: 50rem) { + footer.global div { + grid-template-columns: minmax(125px, auto) repeat(2,max-content); + max-width: var(--mainColMax); + margin: 0 auto; + } + footer.global ul { + background: var(--dashV); + } + footer.global div > :last-child { + grid-column: span 1; + } +} +@media (min-width: 65rem) { + footer.global div { + grid-template-columns: repeat(2,1fr) max-content; + } +} + +footer.global a[href] { + color: inherit; +} +footer.global ul { + margin: 0; + padding: 1em; + list-style: none; +} +footer.global ul li { + margin-block: 0.33em; +} +footer.global > b { + position: absolute; + top: 0; + left: 0; + width: 100%; + display: grid; + grid-template-columns: 1fr [dash] 1px [main] minmax(30rem,var(--mainColMax)) 1px 1fr; +} +footer.global > b::before { + content: url(../assets/img/border-dot-black.svg); + grid-column: dash; + width: 11px; + height: 11px; + position: relative; + left: -5px; + top: -5px; + font-size: 11px; +} + +div.sidebar { + color: #555; + font-style: italic; + margin: 0 0 1em 2em; + padding: 0.15em 1em; + width: 33%; + min-width: 12em; + border: 1px solid #d0d0d0; + border-left: 0.25em solid #d0d0d0; + background: #f2f2f2; + float: right; +} diff --git a/update-11ty/developers/index.html b/update-11ty/developers/index.html new file mode 100644 index 000000000..3f2a5e61b --- /dev/null +++ b/update-11ty/developers/index.html @@ -0,0 +1,305 @@ + + + + + + + + + + + + Developers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

Developers

+
+
+

API Documentation

+

API documentation for the latest stable release is available here:

+ +

Documentation is also available for a number of other releases.

+
+
+

WPE Builds

+

While there are several simple ways for developers to experiment with and explore WPE, none are tuned for performance. Generally, shipping products for embedded systems are performance-tuned custom builds. To make this easier, there is also meta-webkit, which provides build recipes, WebKit based runtimes, and browsers for use with OpenEmbedded and/or Yocto.

+

There are also pre-built packages available for many Linux distributions.

+
+
+

Release Schedule

+

WPE WebKit follows a 6-month development cycle:

+
    +
  • There are two feature releases every year, typically in March and September.
  • +
  • Within feature releases, there may be any number of bug-fixes.
  • +
  • Development releases are the base for the feature releases that follow them. They do not follow a fixed schedule in the release cycle.
  • +
+

WPE WebKit and WebKitGTK share a fair amount of code. Therefore, both projects produce their feature releases simultaneously, and share the same release branches. For bug-fix releases, the release teams for both projects try to sync their version numbers as well as they can.

+
+
+
+

WPE Design

+

WPE is the official WebKit port for Linux-based embedded platforms. WPE is uniquely designed for embedded systems in that it doesn’t depend on any user-interface toolkits such as the traditional Cocoa, GTK, etc toolkits.

+
+ +
+
+

WPE’s Frequently Asked Questions

+

We've been collecting answers to lots of common questions we've been asked. If you've got questions, you might just find a ready answer in the FAQ.

+
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ + +
+ + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/feed.xml b/update-11ty/feed.xml new file mode 100644 index 000000000..8b9bbac83 --- /dev/null +++ b/update-11ty/feed.xml @@ -0,0 +1,485 @@ + + + WPEwebkit.org + Release announcements and security advisories from WPEwebkit.org. + + + 2024-11-26T00:00:00Z + https://wpewebkit.org/ + + + WPE WebKit 2.46.4 released + + 2024-11-26T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpewebkit-2.46.4.html + <p>This is a bug fix release in the stable 2.46 series.</p> +<h3 id="what%E2%80%99s-new-in-wpe-webkit-2.46.4%3F" tabindex="-1">What’s new in WPE WebKit 2.46.4?</h3> +<ul> +<li>Improve memory consumption and performance of Canvas <code>getImageData()</code>.</li> +<li>Improve reliability and latency of audio/video MediaStream pipeplines.</li> +<li>Improve detection of accelerated H.264 decoders.</li> +<li>Add WebM audio capture bitrate configuration with GStreamer 1.24.9 and newer.</li> +<li>Fix multi-touch input event handling.</li> +<li>Fix <code>getDisplayMedia()</code> buffer format negotiation with PipeWire.</li> +<li>Fix wrong video dimensions with GStreamer 1.34.9 and newer.</li> +<li>Fix intersection and filter rendering for scenes with CSS <code>transform-tyle: preserve-3d</code>.</li> +<li>Fix the HTTP-based remote Web Inspector not loading in Chromium.</li> +<li>Fix Office 365 using an <code>User-Agent</code> quirk.</li> +<li>Fix content filters not working on <code>about:blank</code> iframes.</li> +<li>Fix logging in the memory pressure handler monitor.</li> +<li>Fix the <code>JSCOnly</code> build with the GLib event loop enabled.</li> +<li>Fix hole-punching media player when using fixed HTML body position.</li> +<li>Fix the build with GCC 12.x when libwebrtc support is enabled.</li> +<li>Fix the build with <code>libsoup2</code>.</li> +<li>Fix several crashes and rendering issues.</li> +</ul> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpewebkit-2.46.4.tar.xz (38.8 MiB) + md5sum: 2d6659a6bc266bdfacb945028463a85f + sha1sum: 9db1c55784d8a441dd8475d38cb46fcdc826fc71 + sha256sum: a22f6acf5574f9415f5007c6e79c5f8527363d1ae1dbd8d786e67a935ef56d8b +</pre> + + + + + WPE WebKit 2.47.1 released + + 2024-11-04T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpewebkit-2.47.1.html + <p>This is the first development release leading towards the 2.48 series.</p> +<h3 id="what%E2%80%99s-new-in-wpe-webkit-2.47.1%3F" tabindex="-1">What’s new in WPE WebKit 2.47.1?</h3> +<ul> +<li>Flatten layers to a plane when the <code>preseve-3d</code> style is set.</li> +<li>Build GPU process by default, but keeping WebGL in the web process by default for now.</li> +<li>Enable WebGL support when targeting Android.</li> +<li>Add experimental support for speech synthesis using libspiel, which may be +enabled at build time instead of Flite (with <code>USE_SPIEL=ON</code> and <code>USE_FLITE=OFF</code>).</li> +<li>Add support for building Skia when targeting Android.</li> +<li>Add close button and handling of the URL entry <code>onAccepted</code> event to the Qt6 <code>qt-wpe-mini-browser</code>.</li> +<li>Add new settings API to the WPEPlatform library.</li> +<li>Add new connect methods to use a custom device file with <code>WPEDisplayDRM</code>. and <code>WPEDisplayHeadless</code> in the WPEPlatform API.</li> +<li>Web Inspector resources are now shipped in a <code>GResource</code> bundle file, instead of the <code>libWPEInspectorResources</code> shared library.</li> +<li>Rename class <code>WPEMonitor</code> to <code>WPEScreen</code> in the WPEPlatform API.</li> +<li>Use DMA-BUF buffers for WebGL when available.</li> +<li>Make GStreamer GL sink handle DMA-BUF memory to replace the DMA-BUF sink.</li> +<li>Fix device scaling factor in the WPEPlatform DRM implementation.</li> +<li>Fix input methods when using the WPEPlatform library.</li> +<li>Fix unexpected <code>ENABLE_WPE_PLATFORM</code> guards in installed API headers.</li> +<li>Fix building the Qt6 API.</li> +<li>Fix DuckDuckGo links by adding a user agent quirk.</li> +<li>Fix several crashes and rendering issues.</li> +</ul> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpewebkit-2.47.1.tar.xz (39.5 MiB) + md5sum: 8a734b02eac69ec2ba044e0113ef0753 + sha1sum: 14536db7bd1d8b014da0eef51151de84428e4106 + sha256sum: c1f16b0aca0b349fa6ca615267276efdf5151e1e7554e60fa7d3460c568e8f3c +</pre> + + + + + WebKitGTK and WPE WebKit Security Advisory WSA-2024-0006 + + 2024-10-31T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0006.html + <ul> +<li> +<p>Date Reported: <strong>October 31, 2024</strong></p> +</li> +<li> +<p>Advisory ID: <strong>WSA-2024-0006</strong></p> +</li> +<li> +<p>CVE identifiers: <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0006.html#CVE-2024-44185">CVE-2024-44185</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0006.html#CVE-2024-44244">CVE-2024-44244</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0006.html#CVE-2024-44296">CVE-2024-44296</a></p> +</li> +</ul> +<p>Several vulnerabilities were discovered in WebKitGTK and WPE WebKit.</p> +<ul> +<li> +<p><a name="CVE-2024-44185" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-44185">CVE-2024-44185</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.46.0.</li> +<li>Credit to Gary Kwong.</li> +<li>Impact: Processing maliciously crafted web content may lead to an unexpected process +crash Description: The issue was addressed with improved checks.</li> +<li>WebKit Bugzilla: 276097</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-44244" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-44244">CVE-2024-44244</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.46.3.</li> +<li>Credit to an anonymous researcher, Q1IQ (@q1iqF) and P1umer (@p1umer).</li> +<li>Impact: Processing maliciously crafted web content may lead to an unexpected process +crash Description: A memory corruption issue was addressed with improved input +validation.</li> +<li>WebKit Bugzilla: 279780</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-44296" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-44296">CVE-2024-44296</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.46.3.</li> +<li>Credit to Narendra Bhati, Manager of Cyber Security at Suma Soft Pvt. Ltd, Pune (India).</li> +<li>Impact: Processing maliciously crafted web content may prevent Content Security Policy +from being enforced Description: The issue was addressed with improved checks.</li> +<li>WebKit Bugzilla: 278765</li> +</ul> +</li> +</ul> +<p>We recommend updating to the latest stable versions of WebKitGTK and WPE WebKit. It is the +best way to ensure that you are running safe versions of WebKit. Please check our websites +for information about the latest stable releases.</p> +<p>Further information about WebKitGTK and WPE WebKit security advisories can be found at: +<a href="https://webkitgtk.org/security.html">webkitgtk.org/security.html</a> or +<a href="https://wpewebkit.org/security">wpewebkit.org/security</a>.</p> + + + + + WPE WebKit 2.46.3 released + + 2024-10-30T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpewebkit-2.46.3.html + <p>This is a bug fix release in the stable 2.46 series.</p> +<h3 id="what%E2%80%99s-new-in-wpe-webkit-2.46.3%3F" tabindex="-1">What’s new in WPE WebKit 2.46.3?</h3> +<ul> +<li>Fix DuckDuckGo links by adding a user agent quirk.</li> +<li>Fix flattening layers to a plane when the <code>transform-style: preserve-3d</code> style is set.</li> +<li>Fix H.264 encoding and improve selection of encoder parameters according to the requested profile.</li> +<li>Fix <code>libsoup</code> URLs in the generated reference documentation.</li> +<li>Fix the build with <code>ENABLE_REMOTE_INSPECTOR=OFF</code>.</li> +<li>Fix several crashes and rendering issues.</li> +</ul> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpewebkit-2.46.3.tar.xz (38.8 MiB) + md5sum: 6e60664493eb1e37950bba7660e6b3fc + sha1sum: 3e51201829c1e97f846477145a66efc8a85f487a + sha256sum: 56709f8cf113650d8cc30dc22f2b69cb6f74e651731f12e1db52ecce12ab86f2 +</pre> + + + + + WPE WebKit 2.46.2 released + + 2024-10-22T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpewebkit-2.46.2.html + <p>This is a bug fix release in the stable 2.46 series.</p> +<h3 id="what%E2%80%99s-new-in-wpe-webkit-2.46.2%3F" tabindex="-1">What’s new in WPE WebKit 2.46.2?</h3> +<ul> +<li>Own well-known bus name on the accessibility bus.</li> +<li>Improve memory consumption when <code>putImageData</code> is used repeatedly on accelerated canvas.</li> +<li>Disable cached web process suspension for now to prevent leaks.</li> +<li>Improve text kerning with different combinations of antialias and hinting settings.</li> +<li>Destroy all network sessions on process exit.</li> +<li>Build with Skia and WebGL support enabled when targeting Android.</li> +<li>Improve the last resort font fallback to increase the chances of getting an usable system font with Skia.</li> +<li>Fix visible rectangle calculation when there are animations.</li> +<li>Fix the build with <code>ENABLE_NOTIFICATIONS=OFF</code>.</li> +<li>Fix the build with <code>ENABLE_FULLSCREEN_API=OFF</code>.</li> +<li>Fix the build with <code>ENABLE_WEB_AUDIO=OFF</code>.</li> +<li>Fix the build with <code>ENABLE_VIDEO=OFF</code>.</li> +<li>Fix the build on systems that use the Musl C library.</li> +<li>Fix the build on ppc64le.</li> +<li>Fix the build on ARMv7 when compiling with Clang.</li> +<li>Fix several crashes and rendering issues.</li> +</ul> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpewebkit-2.46.2.tar.xz (38.8 MiB) + md5sum: 92011245ffb056092d9fe77af153f009 + sha1sum: 5cd2351db55298a9afcdfdaba0b643ffd4ca0e40 + sha256sum: 1b0ee5a6c8bfdc2f8d8aaa8be143d87e33a47117591176e41555252432cb13b6 +</pre> + + + + + WPE WebKit 2.46 highlights + + 2024-10-07T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/blog/2024-wpewebkit-2.46.html + <p>A couple of weeks ago, the WPE WebKit team released version 2.46. This is an important milestone for the project as, for the first time in a stable series, the Skia backend takes over rendering. Skia brings significant improvements to the graphics stack, so we are very happy for this release. The list of changes goes beyond graphics, and it’s not short of awesome, so let’s have a look to what’s new!</p> +<h3 id="cairo-is-out%2C-skia-is-in" tabindex="-1">Cairo is out, Skia is in</h3> +<p>We <a href="https://blogs.igalia.com/carlosgc/2024/02/19/webkit-switching-to-skia-for-2d-graphics-rendering/">announced</a> some time ago that a new rendering backend with <a href="https://skia.org/">Skia</a> was on the works and that it would eventually replace Cairo. 2.46 the first release series where Skia is used, bringing important improvements in rendering and performance.</p> +<p>While Skia can use a GPU for rendering, our testing with common embedded SoCs has shown that the way WPE WebKit works may result in slightly worse performance in some cases than letting Skia use the CPU. Hence, for the 2.46 releases the latter is the default, while development continues to improve GPU usage on low-powered devices with the ultimate goal of making accelerated rendering the default choice in all cases.</p> +<p>The Cairo backend is still present and will be selected automatically at build time for big-endian architectures, where Skia is not yet supported. We plan to remove support for Cairo in the near future, and this approach allows us to ship the new renderer while solving the remaining issues. At any rate, the Cairo renderer is no longer receiving active development.</p> +<p>It is important to notice that it is recommended to build WPE with Clang instead of GCC. This comes from upstream Skia; see their <a href="https://skia.org/docs/user/build/#supported-and-preferred-compilers">supported and preferred compilers page</a> for details.</p> +<h3 id="graphics-stack-revamped" tabindex="-1">Graphics stack revamped</h3> +<p>Tha switch to Skia has made possible a significant number of changes and improvements in the WebKit graphics stack. These changes relate to accelerated canvas, accelerated CSS filters, color spaces, and more. <a href="https://blogs.igalia.com/carlosgc/2024/09/27/graphics-improvements-in-webkitgtk-and-wpewebkit-2-46/">Carlos García has written extensively about these changes</a> in his blog, we recommend reading his article for more details.</p> +<h3 id="trace-point-profiling-with-sysprof" tabindex="-1">Trace point profiling with sysprof</h3> +<p>Sysprof is a profiling and performance analysis tool for Linux. Thanks to integration with <code>libsysprof-capture</code>, it is now possible to use Sysprof to record trace points to do profiling and performance analysis of WebKit internals. This is a major improvement that will allow us to more effectively analyze the code paths that are more performance-sensitive and find ways to optimize them. It will also allow vendors to profile their specific hardware configurations and specific use-cases as well.</p> +<p>For a more in-depth presentation of the integration with Sysprof, please read <a href="https://feaneron.com/2024/07/12/profiling-a-web-engine/">Georges Stavacras’ blog post on the topic</a>.</p> +<h3 id="api-changes" tabindex="-1">API changes</h3> +<h4 id="additions" tabindex="-1">Additions</h4> +<ul> +<li><a href="https://webkitgtk.org/reference/webkitgtk/unstable/method.Settings.apply_from_key_file.html"><code>webkit_settings_apply_from_key_file()</code></a> allows applying WebKit settings directly from a key file</li> +<li>The console message API, which had been previously deprecated, has been brought to the current API</li> +<li><a href="https://webkitgtk.org/reference/webkitgtk/2.46.0/signal.AutomationSession.will-close.html"><code>WebKitAutomationSession::will-close</code></a> signal, which allows clients to perform cleanup tasks before an automation session is closed</li> +<li><a href="https://webkitgtk.org/reference/webkitgtk/2.46.0/property.Settings.enable-2d-canvas-acceleration.html"><code>enable-2d-canvas-acceleration</code></a> WebSetting can be used to control 2D-canvas acceleration in Skia-enabled builds</li> +<li><code>webkit_web_view_toggle_inspector()</code> shows or hides the web inspector for a given webview (only available with the WPE platform API)</li> +</ul> +<h4 id="deprecations" tabindex="-1">Deprecations</h4> +<ul> +<li><code>WebKitWebView::insecure-content-detected</code> signal.</li> +<li><code>WebKitWebContext:use-system-appearance-for-scrollbars</code> property.</li> +<li><code>webkit_web_context_set_use_system_appearance_for_scrollbars()</code> and <code>webkit_web_context_get_use_system_appearance_for_scrollbars()</code>.</li> +</ul> +<h3 id="gstreamer-customizations" tabindex="-1">GStreamer customizations</h3> +<p>Compile-time platform-specific GStreamer customizations are now done at runtime, using the <code>WEBKIT_GST_QUIRKS</code> and <code>WEBKIT_GST_HOLE_PUNCH_QUIRK</code> environment variables. Setting their value to <code>help</code> will return a help message with the possible values to <code>stderr</code>. A list of the removed CMake defines:</p> +<ul> +<li><code>USE_GSTREAMER_NATIVE_VIDEO</code></li> +<li><code>USE_GSTREAMER_NATIVE_AUDIO</code></li> +<li><code>USE_GSTREAMER_TEXT_SINK</code></li> +<li><code>USE_GSTREAMER_HOLEPUNCH</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_WESTEROS</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_BCM_NEXUS</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_AMLOGIC</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_RPI</code></li> +<li><code>USE_WPEWEBKIT_PLATFORM_BROADCOM</code></li> +<li><code>USE_WESTEROS_SINK</code></li> +</ul> +<h3 id="web-platform-changes" tabindex="-1">Web Platform changes</h3> +<p>The changes to supported Web Platform features between releases of WebKit are always substantial, and for that reason listing all of those changes here would be a major endeavour. The following is an incomplete list of some of the features that have been enabled, removed, and marked in preview state since 2.44, in no particular order:</p> +<ul> +<li>CSS Container/Style Queries</li> +<li>CSS <code>text-wrap-style</code></li> +<li>CSS <code>background-clip: border-area</code></li> +<li>CSS <code>text-underline-position: left|right</code></li> +<li>CSS <code>scrollbar-width</code></li> +<li>CSS View Transitions</li> +<li>CSS Grid Masonry layout (preview)</li> +<li>CSS <code>::target-text</code> pseudo element</li> +<li>WebCrypto X25519 algorithm (preview)</li> +<li>AppCache support has been removed</li> +<li>New <code>Promise.try()</code> method</li> +<li>New <code>Observable</code> methods, like <code>.map()</code> and <code>.filter()</code></li> +</ul> +<h3 id="other-noteworthy-changes" tabindex="-1">Other noteworthy changes</h3> +<ul> +<li>Suport for the WebP image format is now always enabled.</li> +<li>WebDriver clients may now connect to an already running process, instead of always needing to spawn a new one.</li> +<li>The <code>gst-libav</code> AAC decoders are now disabled due to outstanding bugs. Distributors are encouraged to use the GStreamer FDK AAC decoder (part of <code>gst-plugins-bad</code>) instead.</li> +</ul> +<h3 id="and-much-more!" tabindex="-1">And much more!</h3> +<p>WebKit evolves and changes a lot between major stable releases. Listing all changes would not be possible. There are countless bug fixes, performance improvements, new web features supported, and so on. We recommend checking the <a href="https://wpewebkit.org/release/">release notes</a> and the git log for more details.</p> +<p>The WPE WebKit team is already working on the 2.48 release, schedule for early next year. Until then!</p> + + + + + WPE WebKit 2.46.1 released + + 2024-10-02T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpewebkit-2.46.1.html + <p>This is the first bug fix release in the stable 2.46 series.</p> +<h3 id="what%E2%80%99s-new-in-wpe-webkit-2.46.1%3F" tabindex="-1">What’s new in WPE WebKit 2.46.1?</h3> +<ul> +<li>Use the Skia CPU renderer by default, which at the moment performs better +in a number of embedded devices. The GPU renderer is still available and +may be enabled by setting <code>WEBKIT_SKIA_ENABLE_CPU_RENDERING=0</code> in the +environment.</li> +<li>Add an <code>ENABLE_WPE_PLATFORM</code> build option to CMake. This is disabled by +default because the WPEPlatform API is in development and provided as +part of the 2.46.x releases as a preview feature.</li> +<li>Fix login QR code not shown in WhatsApp web.</li> +<li>Fix processes not spawning when process startup is customized using +the <code>wpe_process_provider</code> API from <code>libwpe</code>.</li> +<li>Fix mouse simulation with WebDriver when using a classic WPE backend +through <code>libwpe</code>.</li> +<li>Disable DMABuf video sink by default to prevent file descriptor leaks.</li> +<li>Fix building the MiniBrowser.</li> +<li>Fix the build with <code>libsysprof-capture</code> version 44 and older.</li> +<li>Fix the build with GCC 13.</li> +<li>Fix several crashes and rendering issues.</li> +</ul> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpewebkit-2.46.1.tar.xz (38.8 MiB) + md5sum: eec67852662a3498680f72041120fc11 + sha1sum: c6458e1aefb74a2609ae0fe4e2d6eab0bf703065 + sha256sum: 1e0aaf870f36001c42b1ce5a2027b4101bed878746e437cc6d6fed0693afe9ad +</pre> + + + + + WebKitGTK and WPE WebKit Security Advisory WSA-2024-0005 + + 2024-09-25T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html + <ul> +<li> +<p>Date Reported: <strong>September 25, 2024</strong></p> +</li> +<li> +<p>Advisory ID: <strong>WSA-2024-0005</strong></p> +</li> +<li> +<p>CVE identifiers: <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-23271">CVE-2024-23271</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-27808">CVE-2024-27808</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-27820">CVE-2024-27820</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-27833">CVE-2024-27833</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-27838">CVE-2024-27838</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-27851">CVE-2024-27851</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-40866">CVE-2024-40866</a>, <a href="https://wpewebkit.org/wpewebkit.org/update-11ty/security/WSA-2024-0005.html#CVE-2024-44187">CVE-2024-44187</a></p> +</li> +</ul> +<p>Several vulnerabilities were discovered in WebKitGTK and WPE WebKit.</p> +<ul> +<li> +<p><a name="CVE-2024-23271" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-23271">CVE-2024-23271</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.42.5.</li> +<li>Credit to James Lee (@Windowsrcer).</li> +<li>Impact: A malicious website may cause unexpected cross-origin behavior. Description: A +logic issue was addressed with improved checks.</li> +<li>WebKit Bugzilla: 265812</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-27808" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-27808">CVE-2024-27808</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.44.2.</li> +<li>Credit to Lukas Bernhard of CISPA Helmholtz Center for Information Security.</li> +<li>Impact: Processing web content may lead to arbitrary code execution. Description: The +issue was addressed with improved memory handling.</li> +<li>WebKit Bugzilla: 268221</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-27820" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-27820">CVE-2024-27820</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.44.2.</li> +<li>Credit to Jeff Johnson of underpassapp.com.</li> +<li>Impact: Processing web content may lead to arbitrary code execution. Description: The +issue was addressed with improved memory handling.</li> +<li>WebKit Bugzilla: 270139</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-27833" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-27833">CVE-2024-27833</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.44.2.</li> +<li>Credit to Manfred Paul (@_manfp) working with Trend Micro Zero Day Initiative.</li> +<li>Impact: Processing maliciously crafted web content may lead to arbitrary code +execution. Description: An integer overflow was addressed with improved input +validation.</li> +<li>WebKit Bugzilla: 271491</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-27838" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-27838">CVE-2024-27838</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.44.3.</li> +<li>Credit to Emilio Cobos of Mozilla.</li> +<li>Impact: A maliciously crafted webpage may be able to fingerprint the user. +Description: The issue was addressed by adding additional logic.</li> +<li>WebKit Bugzilla: 262337</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-27851" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-27851">CVE-2024-27851</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.44.3.</li> +<li>Credit to Nan Wang (@eternalsakura13) of 360 Vulnerability Research Institute.</li> +<li>Impact: Processing maliciously crafted web content may lead to arbitrary code +execution. Description: The issue was addressed with improved bounds checks.</li> +<li>WebKit Bugzilla: 272106</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-40866" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-40866">CVE-2024-40866</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.46.0.</li> +<li>Credit to Hafiizh and YoKo Kho (@yokoacc) of HakTrak.</li> +<li>Impact: Visiting a malicious website may lead to address bar spoofing. Description: +The issue was addressed with improved UI.</li> +<li>WebKit Bugzilla: 279451</li> +</ul> +</li> +<li> +<p><a name="CVE-2024-44187" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-44187">CVE-2024-44187</a></p> +<ul> +<li>Versions affected: WebKitGTK and WPE WebKit before 2.46.0.</li> +<li>Credit to Narendra Bhati, Manager of Cyber Security at Suma Soft Pvt. Ltd, Pune (India).</li> +<li>Impact: A malicious website may exfiltrate data cross-origin. Description: A cross- +origin issue existed with “iframe” elements. This was addressed with improved tracking +of security origins.</li> +<li>WebKit Bugzilla: 279452</li> +</ul> +</li> +</ul> +<p>We recommend updating to the latest stable versions of WebKitGTK and WPE WebKit. It is the +best way to ensure that you are running safe versions of WebKit. Please check our websites +for information about the latest stable releases.</p> +<p>Further information about WebKitGTK and WPE WebKit security advisories can be found at: +<a href="https://webkitgtk.org/security.html">webkitgtk.org/security.html</a> or +<a href="https://wpewebkit.org/security">wpewebkit.org/security</a>.</p> + + + + + WPE WebKit 2.46.0 released + + 2024-09-18T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpewebkit-2.46.0.html + <p>This is the first stable release in the 2.46 series.</p> +<h3 id="highlights-of-the-wpe-webkit-2.46.0-release" tabindex="-1">Highlights of the WPE WebKit 2.46.0 Release</h3> +<ul> +<li>Skia is used instead of Cairo for 2D rendering, and GPU rendering is +enabled by default.</li> +<li>Offscreen Canvas is now enabled by default.</li> +<li>Add support for system tracing using +<a href="https://developer.gnome.org/documentation/tools/sysprof.html">Sysprof</a>.</li> +<li>Add <a href="https://wpewebkit.org/reference/2.46.0/wpe-webkit-2.0/method.Settings.apply_from_key_file.html">new +API</a> +to load settings from a config file.</li> +<li>Add a <a href="https://wpewebkit.org/reference/2.46.0/wpe-webkit-2.0/property.Settings.enable-2d-canvas-acceleration.html">new +setting</a> +to toggle 2D Canvas acceleration (enabled by default).</li> +<li>Undeprecate console messages API and make it available in the 2.0 API.</li> +</ul> +<p>For more details about all the changes included in WPE WebKit 2.46 see +the <code>NEWS</code> file that is included in the tarball.</p> +<p>Thanks to all the contributors who made possible this release.</p> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpewebkit-2.46.0.tar.xz (38.7 MiB) + md5sum: c4df7766023687b5a9688b683dc569d1 + sha1sum: 21fb157aa381f23377391681cea36fd299b46d49 + sha256sum: 301550fbd8703f3ba4c4a65fe596686960569f8a3b0f6668243179cbc77bbc50 +</pre> + + + + + WPEBackend-fdo 1.15.90 released + + 2024-09-13T00:00:00Z + https://wpewebkit.org/wpewebkit.org/update-11ty/release/wpebackend-fdo-1.15.90.html + <p>This is a development release leading towards the 1.16 series.</p> +<h3 id="what%E2%80%99s-new-in-wpebackend-fdo-1.15.90%3F" tabindex="-1">What’s new in WPEBackend-fdo 1.15.90?</h3> +<ul> +<li>Changed minimum Meson version to 0.52.1</li> +<li>Fix build issues in some configurations that require an explicit cast +to <code>EGLNativeWindowType</code>.</li> +<li>Fix WebKit no longer repainting after provisional navigation with +PSON enabled.</li> +<li>Fix graphics buffer leaks by always freeing them in buffer destroy +listener callbacks.</li> +<li>Fix a crash caused by a wrong assertion, which was typically triggered +in debug builds when using the NVidia drivers.</li> +<li>Fix memory leak when the view backend <code>wl_resource</code> is destroyed.</li> +<li>Fix <code>wpe_dmabuf_pool</code> object leak.</li> +</ul> +<h4 id="checksums" tabindex="-1">Checksums</h4> +<pre> +wpebackend-fdo-1.15.90.tar.xz (43.4 KiB) + md5sum: 15dd3a7f9be0db18aa237f4cd1bbbd18 + sha1sum: 3268a9b7d6ed8ebf5198fb92b7063c58627a84bd + sha256sum: 4ac5be554560ed48b13a0745fc779d7f6663904f35f510a6c50e34cb96999083 +</pre> + + + \ No newline at end of file diff --git a/update-11ty/index.html b/update-11ty/index.html new file mode 100644 index 000000000..72e30ba45 --- /dev/null +++ b/update-11ty/index.html @@ -0,0 +1,781 @@ + + + + + + + + + + + + + WPE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+

+Create simple and performant systems based on Web platform technologies. +

+

+An Open Source WebKit port for Linux-based embedded devices designed with flexibility and hardware acceleration in mind, leveraging common 3D graphics APIs for best performance. +

+Get WPE +
+
+ + +
+ +
+ +
+

Explore Embedded Browsers

+
+

The Web Platform is a frequently chosen foundational technology for many reasons, including:

+
    +
  • Web Platform technologies are built on standards, they have great longevity
  • +
  • The standards are open, embedded systems can incorporate them without licensing fees or other worries. +Open standards with great longevity allows lots more things to benefit from the same investments
  • +
  • The number of people with the basic skills to build things with web technologies is massive
  • +
+Learn more about embedded browsers +
+
+ +
+

Supported Hardware

+

WPE is currently running on a wide range of hardware. This page lists configurations which are known to work, sorted by manufacturer:

+ +
+ +
+

Why Choose WPE?

+

WPE WebKit is widely adopted by many industries, including digital signage, professional audio, home appliances, set-top-boxes, automotive, and inflight infotainment. Countless devices deployed around the globe are already using WPE WebKit as their web runtime platform, and use is growing rapidly.

+ +
+ +
+ + +
+ +
+ +

Success Story

+

Metrological

+

WPE WebKit brought RDK, a modern, performant web browser, to millions of screens. It enables operators to manage devices and easily customize their UIs and apps and provides analytics to improve the customer experience and drive business results.

+

Read More

+
+
+
+ + +

Use Cases

+ +
+
+

Digital Signage

+

Digital signage web rendering players have many advantages and are a trend nowadays. They feature use of HTML for composing the user interface, simple and effortless provisioning and scheduling new contents to the screen from the cloud, a robust environment with an automatic crash recovery system, and more.

+ + +
+
+
+
+

Point of Sale

+

Just as the web helps billions of online users make purchases every day, embedded browsers can serve as the basis for building the point of sale systems used by brick and mortar merchants.

+ +
+ +
+
+
+

Infotainment Systems

+

The web’s ability to create interactive interfaces and stream media make embedded browsers an excellent choice for building the infotainment systems in automobiles, airplanes and trains.

+ +
+
+
+
+

Smart TVs and set-top-boxes

+

Television has long used embedded browsers in many ways, from video playback to creating the guides and interactive menus. The Web, with its natural multimedia-related capabilities, is a natural fit for consuming both traditional TV-based content as well as on demand content from industry-leading streaming services.

+ +
+
+
+
+

Home appliances

+

Embedded browsers are increasingly used in home appliances of all kinds: From providing temperature information and settings for climate control systems, to browsing recipes and engaging programs on your favorite cooking machine, to controlling the distribution of sound across smart speakers around your home.

+ +
+ +
+
+
+

Professional video and broadcasting

+

Graphics overlays are everywhere nowadays in the live video broadcasting industry. Embedded browsers can be used to deliver low-latency, web-augmented video broadcasts which provides with expands the capabilities of traditional systems with and added value.

+ + +
+ +
+
+
+
+
+

Developers

+ +
+
+

WPE Design

+

WPE is the official WebKit port for embedded platforms. WPE is uniquely designed for embedded systems in that it doesn’t depend on any user-interface toolkits such as the traditional Cocoa, GTK, etc toolkits.

+ +
+
+

WPE’s Frequently Asked Questions

+

We’ve been collecting answers to lots of common questions we’ve been asked. If you have questions, you might find a ready answer in the FAQ.

+
+
+
+ +
+
+ +
+

Developers

+ +
+ +
+

WPE Design

+

WPE is the official WebKit port for embedded platforms. WPE is uniquely designed for embedded systems in that it doesn’t depend on any user-interface toolkits such as the traditional Cocoa, GTK, etc toolkits.

+ +
+ +
+

WPE’s Frequently Asked Questions

+

We’ve been collecting answers to lots of common questions we’ve been asked. If you have questions, you might find a ready answer in the FAQ.

+
+ +
+
+ +
+
+ +

Brought to you by

+Igalia + +
+Open Source Consultancy +

WPEWebKit is developed by Igalia, an open-source consultancy with a lot of experience working on the Web platform. As the maintainers of this official port of WebKit, Igalia has a great deal of experience with meeting hardware and software challenges in the embedded space and several others. Get in touch!

+igalia.com +
+ +
+
+ +
+

+ If you’re using WPE WebKit, or are considering doing so, please take our brief user survey. Your input will help us make WPE WebKit better for you! +

+
+ +
+ + + + +

If you’re using WPE WebKit, or are considering doing so, please take our brief user survey! Your input will help us make WPE WebKit better for you.

+
+ + + +
+
+ + + + + + + diff --git a/update-11ty/js/SpicySections.js b/update-11ty/js/SpicySections.js new file mode 100644 index 000000000..91f777df1 --- /dev/null +++ b/update-11ty/js/SpicySections.js @@ -0,0 +1,550 @@ +/** + * This work is licensed under the W3C Software and Document License + * (http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). + */ +class MediaAffordancesElement extends HTMLElement { + constructor() { + super() + + this.mqls = [] + this.observers = [] + this.supportedAffordances = new Set() + } + + observeAffordanceChange(cb) { + this.observers.push(cb) + } + + notifyChange() { + let intersection = new Set() + + for (let elem of this.mqls) { + if (elem.matches && this.supportedAffordances.has(elem.__affordance)) { + intersection.add(elem.__affordance) + } + } + + let arr = [...intersection] + + if (arr.length > 0) { + this.setAttribute('mq-matched', arr.join(' ')) + } else { + this.removeAttribute('mq-matched') + } + + let affordance = arr[0] + + if (affordance) { + this.setAttribute('affordance', affordance) + } else { + this.removeAttribute('affordance') + } + + this.observers.forEach((cb) => { + cb(intersection, this.__matching) + }) + } + + static get observedAttributes() { + return ['mq-affordances'] + } + + connectedCallback() { + let newValue = getComputedStyle(this).getPropertyValue('--const-mq-affordances') + + this.connectListeners(newValue) + } + + connectListeners(newValue = '') { + if (newValue.trim().length === 0) { + return + } + + newValue.split('|').forEach((segment) => { + let mq = segment.trim().match(/\[([^\]]*)/)[1] + let names = segment.replace(`[${mq}]`, '').trim().split(' ') + let mql = window.matchMedia(mq) + + mql.__affordance = names[0] // one for now + mql.onchange = () => this.notifyChange() + + this.mqls.push(mql) + }, this) + + this.notifyChange() + } + + attributeChangedCallback(name, oldValue, newValue) { + this.connectListeners(newValue) + } +} + +// ---------------------------------------------------- + +(() => { + let lastUId = 0 + + let nextUId = () => { + return `cp${++lastUId}` + } + + let getLabels = (regionset) => { + return [...regionset.children].filter((el) => /^H\d$/.test(el.tagName) || el.tagName === 'SPICY-H') + } + + let getContentEls = (regionset) => { + return [...regionset.children].filter((el) => !/^H\d$/.test(el.tagName)) + } + + let ensureId = (el) => { + el.id = el.id || nextUId() + return el.id + } + + let style = document.createElement('style') + style.innerHTML = ` + :where(spicy-sections > [affordance*="collapse"])::before { + content: ' '; + display: inline-block; + width: 0.5em; + height: 0.75em; + margin: 0 0.4em 0 0; + transform: rotate(90deg); + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='10px' height='10px' viewBox='0 0 270 240' enable-background='new 0 0 270 240' xml:space='preserve'%3e%3cpolygon fill='black' points='5,235 135,10 265,235 '/%3e%3c/svg%3e "); + background-size: 100% 100%; + } + + :where(spicy-sections > .hide) { + display: none !important; + } + + :where(spicy-sections > [affordance*="collapse"][aria-expanded="true"])::before, + :where(spicy-sections > [affordance*="collapse"][aria-expanded="true"])::after { + transform: rotate(180deg); + } + ` + + document.head.prepend(style) + + const template = ` + +
+ +
+
+ + + + ` + + class RegionSet extends MediaAffordancesElement { + __defaults + __tabListEl + + // tabs and exclusive collapses should have the same affordance object? + __affordanceConf = { + collapse: { + // can take a condition to force, check-like + toggle: (label, condition) => { + let state = typeof condition === 'boolean' ? condition : !label.affordanceState.expanded + + let contentEl = label.nextElementSibling + + label.affordanceState.expanded = state + label.affordanceState.nonExclusiveExpanded = state + + label.setAttribute('aria-expanded', state) + + if (state) { + label.setAttribute('expanded', '') + + contentEl.classList.remove('hide') + } else { + label.removeAttribute('expanded') + + contentEl.classList.add('hide') + } + }, + }, + 'exclusive-collapse': { + // ignores condition, radio-like + toggle: (label) => { + let labels = getLabels(label.parentElement) + let siblings = labels.filter((c) => c !== label) + let index = labels.findIndex((c) => c === label) + + siblings.forEach((sibLabel, i) => { + let relatedContent = sibLabel.nextElementSibling + + relatedContent.classList.add('hide') + + sibLabel.tabIndex = -1 + sibLabel.setAttribute('aria-expanded', 'false') + sibLabel.affordanceState.exclusiveExpanded = false + }) + + label.tabIndex = 0 + label.parentElement.affordanceState.exclusiveSelection.index = index // TODO: nope, fix/remove this? + label.nextElementSibling.classList.remove('hide') + label.setAttribute('aria-expanded', 'true') + label.affordanceState.exclusiveExpanded = true + label.focus({ preventScroll: true }) + }, + }, + 'tab-bar': { + // ignores condition, radio-like + toggle: (label) => { + let labels = getLabels(label.parentElement) + let siblings = labels.filter((c) => c !== label) + let index = labels.findIndex((c) => c === label) + + siblings.forEach((sibLabel, i) => { + let relatedContent = sibLabel.nextElementSibling + + relatedContent.classList.add('hide') + + sibLabel.tabIndex = -1 + sibLabel.setAttribute('aria-selected', 'false') + sibLabel.affordanceState.exclusiveExpanded = false + }) + + label.tabIndex = 0 + label.parentElement.affordanceState.exclusiveSelection.index = index + label.nextElementSibling.classList.remove('hide') + label.setAttribute('aria-selected', 'true') + label.affordanceState.exclusiveExpanded = true + label.focus({ preventScroll: true }) + }, + }, + } + + __setSize = (labelEls, contentEls) => { + this.__size = Math.min(labelEls.length, contentEls.length) + + if (labelEls.length !== this.__size) { + console.warn('mismatch in tab-set label/content pairs...') + } + + labelEls.forEach((labelEl, i) => { + let contentEl = contentEls[i] + + if (!labelEl.initialized) { + labelEl.initialized = true // TODO: this used to be shadow, do i need it? + + let defs = this.__defaults.defaultActive + + // this assumes it is about collapses + labelEl.affordanceState = { + expanded: defs.includes(labelEl), + active: false, + // activate in the current mode + activate: () => { + if (this.affordanceState.current) { + this.__affordanceConf[this.affordanceState.current].toggle(labelEl) + } + }, + } + + let defaultExclusive = defs.length === 0 ? labelEls[0] : defs[defs.length - 1] + + this.affordanceState.exclusiveSelection.index = labelEls.indexOf(defaultExclusive) + } + labelEl.setMode = (mode) => { + if (mode === 'non-exclusive') { + let isExpanded = labelEl.affordanceState.expanded + + labelEl.setAttribute('affordance', 'collapse') + labelEl.setAttribute('tabindex', '0') + labelEl.setAttribute('aria-controls', contentEl.id) + labelEl.setAttribute('role', 'button') + labelEl.setAttribute('aria-expanded', isExpanded) + + labelEl.nextElementSibling.classList.toggle('hide', !isExpanded) + } else if (mode === 'exclusive') { + let isExpanded = labelEls.indexOf(labelEl) === this.affordanceState.exclusiveSelection.index + + labelEl.setAttribute('affordance', 'collapse') + labelEl.setAttribute('tabindex', isExpanded ? 0 : -1) + labelEl.setAttribute('role', 'button') + labelEl.setAttribute('aria-expanded', isExpanded) + labelEl.setAttribute('aria-controls', contentEl.id) + + labelEl.nextElementSibling.classList.toggle('hide', !isExpanded) + } else { + labelEl.removeAttribute('tabIndex') + labelEl.removeAttribute('affordance') + labelEl.removeAttribute('aria-expanded') + labelEl.removeAttribute('role') + } + } + }) + } + + __projectTabBar = () => { + this.__removeProjections() + + getLabels(this).forEach((tabSource, i) => { + let selected = false + let tabIndex = -1 + + tabSource.setMode() + tabSource.slot = 'tabListSlot' + tabSource.setAttribute('role', 'tab') + + let contentSource = tabSource.nextElementSibling + + contentSource.tabIndex = 0 + + tabSource.setAttribute('aria-controls', ensureId(contentSource)) + + contentSource.setAttribute('role', 'tabpanel') + contentSource.setAttribute('aria-labelledby', tabSource.id) + + if (i === this.affordanceState.exclusiveSelection.index) { + tabIndex = 0 + selected = true + } + + tabSource.setAttribute('aria-selected', selected) + tabSource.tabIndex = tabIndex + + contentSource.classList.toggle('hide', !selected) + + // TODO: aria-orientation :( + }) + } + + __projectCollapses = (exclusive) => { + // TODO: remove projections and... ?? + this.__removeProjections() + + getLabels(this).forEach((label) => { + label.setMode(exclusive ? 'exclusive' : 'non-exclusive') + }) + } + + __removeProjections = () => { + Array.from(this.children, (child) => { + child.removeAttribute('slot') + child.removeAttribute('affordance') + child.removeAttribute('role') + child.removeAttribute('aria-selected') + child.removeAttribute('aria-controls') + child.removeAttribute('tabindex') + child.removeAttribute('aria-expanded') + + child.classList.remove('hide') + }) + } + + // matching pairs + __size = 0 + + __configure = () => { + // hmmm + + this.__setSize(getLabels(this), getContentEls(this)) + + if (this.affordanceState.current === 'tab-bar') { + this.affordanceState.currentMode = 'exclusive' + this.__projectTabBar() + } else if (this.affordanceState.current === 'collapse') { + this.affordanceState.currentMode = 'non-exclusive' + this.__projectCollapses() + } else if (this.affordanceState.current === 'exclusive-collapse') { + this.affordanceState.currentMode = 'exclusive' + this.__projectCollapses(true) + } else { + this.affordanceState.currentMode = undefined + this.__removeProjections() + } + + // TODO: hmm, these are DOM changes, we could cache them + let labelEls = getLabels(this) + + for (let i = 0; i < this.__size; i++) { + let label = labelEls[i] + + // probably add one handler that decides + if (!label._inited) { + label.addEventListener('click', (evt) => { + evt.target.affordanceState.activate() + }) + + label._inited = true + } + } + } + + __childListObserver = new MutationObserver((mutationList) => { + // we have to wire up new elements + let labelEls = getLabels(this) + let contentEls = getContentEls(this) + + // what if there is a mismatch? + this.__setSize(labelEls, contentEls) + this.__configure() + }) + + __tabset + + affordanceState = { + exclusiveSelection: { index: undefined }, + current: undefined, + currentMode: undefined, + getLabels: () => { + return getLabels(this) + }, + } + + honourFragmentLink = () => { + let labels = getLabels(this) + + if (location.hash && this.querySelector(location.hash)) { + // try to find a label with this ID, or controlled content + // that contains an element with this ID + for (let i = 0; i < labels.length; i++) { + let relevantContent = labels[i].getAttribute('aria-controls') && this.querySelector(`#${labels[i].getAttribute('aria-controls')}`) + + if (labels[i] === this.querySelector(location.hash) || (relevantContent && relevantContent.querySelector(location.hash))) { + labels[i].affordanceState.activate() + + return + } + } + } + } + + // wires up supported affordances + constructor() { + super() + + this.supportedAffordances.add('tab-bar') + this.supportedAffordances.add('collapse') + this.supportedAffordances.add('exclusive-collapse') + + let checkDefaults = () => { + if (!this.__defaults) { + this.__defaults = { + onMatch: this.hasAttribute('defaults-on-match'), + defaultActive: getLabels(this).filter((l) => l.hasAttribute('default-activate')), + } + } + } + + this.observeAffordanceChange((matching, all) => { + if (!this.__defaults) { + this.__defaults = { + onMatch: this.hasAttribute('defaults-on-match'), + defaultActive: getLabels(this).filter((l) => l.hasAttribute('default-activate')), + } + } + + this.affordanceState.current = this.getAttribute('affordance') + this.__configure() + }) + + this.setActiveAffordance = (matching, all) => { + checkDefaults() + + this.setAttribute('affordance', matching) + + this.affordanceState.current = matching + + this.__configure() + } + + this.attachShadow({ mode: 'open' }) + + this.shadowRoot.innerHTML = template + + this.__tabListEl = this.shadowRoot.querySelector("[part='tab-list']") + + this.addEventListener('keydown', (evt) => { + let labels = getLabels(this) + let size = labels.length + let cur = this.affordanceState.exclusiveSelection.index + let prev = cur === 0 ? size - 1 : cur - 1 + let next = cur === size - 1 ? 0 : cur + 1 + + // don't trap nested handling + if (evt.target.parentElement !== evt.currentTarget) { + return + } + + if (this.affordanceState.current === 'tab-bar' || this.affordanceState.current === 'exclusive-collapse') { + if (evt.key === 'ArrowLeft' || evt.key === 'ArrowUp') { + labels[prev].affordanceState.activate() + evt.preventDefault() + } else if (evt.key === 'ArrowRight' || evt.key === 'ArrowDown') { + labels[next].affordanceState.activate() + evt.preventDefault() + } + } else if (evt.key === ' ' && this.affordanceState.current === 'collapse') { + evt.preventDefault() + } + }, false) + + this.addEventListener('keyup', (evt) => { + if (evt.key === ' ' && this.affordanceState.current === 'collapse') { + evt.target.closest('[affordance]').affordanceState.activate() + + evt.preventDefault() + } + }) + } + + connectedCallback() { + super.connectedCallback() + + // TODO: handle selection + if (location.hash) { + setTimeout(this.honourFragmentLink, 1) + } + + window.addEventListener('hashchange', this.honourFragmentLink) + + // if you append a fragment with a pair, it should work + this.__childListObserver.observe(this, { childList: true }) + } + } + + customElements.define('spicy-sections', RegionSet) +})() diff --git a/update-11ty/js/slide-show-ol.js b/update-11ty/js/slide-show-ol.js new file mode 100644 index 000000000..5c9879b28 --- /dev/null +++ b/update-11ty/js/slide-show-ol.js @@ -0,0 +1,23 @@ +/* slide-show + 1.2.3 + By Stephen Band + Built 2023-02-22 14:59 */ + +var nn=Object.defineProperty;var _t=Object.getOwnPropertySymbols;var on=Object.prototype.hasOwnProperty,rn=Object.prototype.propertyIsEnumerable;var Kt=(t,e)=>{var o={};for(var r in t)on.call(t,r)&&e.indexOf(r)<0&&(o[r]=t[r]);if(t!=null&&_t)for(var r of _t(t))e.indexOf(r)<0&&rn.call(t,r)&&(o[r]=t[r]);return o};var q=(t,e)=>{for(var o in e)nn(t,o,{get:e[o],enumerable:!0})};function U(t){var e=new Map;return function(r){if(e.has(r))return e.get(r);var i=t(r);return e.set(r,i),i}}var sn=Array.prototype;function cn(t,e){return typeof t=="function"?t.apply(null,e):t}function Qt(t,e,o){o=o||t.length;var r=o===1?e?t:U(t):U(function(i){return Qt(function(){var s=[i];return s.push.apply(s,arguments),t.apply(null,s)},e,o-1)});return function i(s){return arguments.length===0?i:arguments.length===1?r(s):arguments.length>=o?t.apply(null,arguments):cn(r(s),sn.slice.call(arguments,1))}}var S=Qt;function an(t,e){return 1-Math.pow(1-e,t)}var Jt=S(an);function E(){}function bt(t,e){return e(t)}function L(t){return t}var ln=Array.prototype;function xt(){let t=arguments;return t.length?e=>ln.reduce.call(t,bt,e):L}function St(t,e,o){return o*(e-t)+t}var un=window.performance,Zt=window.requestAnimationFrame,pn=window.cancelAnimationFrame;function Et(t,e,o){var r=un.now();function i(c){var p=(c-r)/(t*1e3);p<1?(p>0&&e(p),s=Zt(i)):(e(1),o&&o())}var s=Zt(i);return function(){pn(s)}}function et(t,e,o,r,i,s){let c=r[o];return Et(t,xt(e,p=>St(c,i,p),p=>r[o]=p),s)}function fn(){return{x:0,y:0,left:0,top:0,right:window.innerWidth,bottom:window.innerHeight,width:window.innerWidth,height:window.innerHeight}}function k(t){return t===window?fn():t.getClientRects()[0]||t.getBoundingClientRect()}var R={scrollDuration:.3,scrollDurationPerHeight:.125,scrollTransform:Jt(3)},te=E;function dn(t,e){if(e.behavior==="smooth"){let o=t.style.getPropertyValue("scroll-snap-type"),r=()=>{let i=t.scrollLeft,s=t.scrollTop;t.style.setProperty("scroll-snap-type",o),t.scrollLeft=i,t.scrollTop=s};if(t.style.setProperty("scroll-snap-type","none"),e.left!==void 0){let i=t===document.body?window.innerWidth:k(t).width,s=R.scrollDuration+R.scrollDurationPerHeight*Math.abs(e.left-t.scrollLeft)/i;te=et(s,R.scrollTransform,"scrollLeft",t,e.left,r)}else{let i=t===document.body?window.innerHeight:k(t).height,s=R.scrollDuration+R.scrollDurationPerHeight*Math.abs(e.top-t.scrollTop)/i;te=et(s,R.scrollTransform,"scrollTop",t,e.top,r)}}else e.left!==void 0&&(t.scrollLeft=e.left),e.top!==void 0&&(t.scrollTop=e.top)}if(!("scrollBehavior"in document.documentElement.style)){window.console&&console.log("Polyfilling Element.scrollTo(options)");let t="scrollTo"in Element.prototype?Element:HTMLElement,e=t.scrollIntoView;t.prototype.scrollTo=function(o){typeof o=="object"?dn(this,o):e.apply(this,arguments)}}function b(t,e){return function(){let r=t.apply(this,arguments),i=e[r]||e.default;if(!i)throw new Error('overload() no handler for "'+r+'"');return i.apply(this,arguments)}}var mn=b(L,{is:E,tag:E,data:function(t,e,o){Object.assign(e.dataset,o)},html:function(t,e,o){e.innerHTML=o},text:function(t,e,o){e.textContent=o},children:function(t,e,o){e.innerHTML="",e.append.apply(e,o)},points:C,cx:C,cy:C,r:C,transform:C,preserveAspectRatio:C,viewBox:C,default:function(t,e,o){t in e?e[t]=o:e.setAttribute(t,o)}});function C(t,e,o){e.setAttribute(t,o)}function hn(t,e){for(var o=Object.keys(e),r=o.length;r--;)mn(o[r],t,e[o[r]]);return t}var nt=S(hn,!0);var Tt="http://www.w3.org/2000/svg",ee=document.createElement("template"),Lt=(t,e)=>e&&typeof e;function ne(t,e){let o=document.createRange();return o.selectNode(t),o.createContextualFragment(e)}var O=b(Lt,{string:function(t,e){let o=document.createElementNS(Tt,t);return o.innerHTML=e,o},object:function(t,e){let o=document.createElementNS(Tt,t);return typeof e.length=="number"?o.append.apply(o,e):nt(o,e),o},default:t=>document.createElementNS(Tt,t)}),gn=b(Lt,{string:function(t,e){let o=document.createElement(t);return o.innerHTML=e,o},object:function(t,e){let o=document.createElement(t);return typeof e.length=="number"?o.append.apply(o,e):nt(o,e),o},default:t=>document.createElement(t)}),wn=b(L,{comment:function(t,e){return document.createComment(e||"")},fragment:b(Lt,{string:function(t,e,o){return o?ne(o,e):(ee.innerHTML=e,ee.content.cloneNode(!0))},object:function(t,e,o){let r=o?ne(o):document.createDocumentFragment();return typeof e.length=="number"?r.append.apply(r,e):nt(r,e),r},default:()=>document.createDocumentFragment()}),text:function(t,e){return document.createTextNode(e||"")},circle:O,ellipse:O,g:O,glyph:O,image:O,line:O,rect:O,use:O,path:O,pattern:O,polygon:O,polyline:O,svg:O,default:gn}),w=wn;function kt(t,e,o){let r;typeof o!="string"&&o.input!==void 0&&o.index!==void 0&&(r=o,o=r.input.slice(o.index+o[0].length+(o.consumed||0)));let i=t.exec(o);if(!i)return;let s=e(i);return r&&(r.consumed=(r.consumed||0)+i.index+i[0].length+(i.consumed||0)),s}var Pr=S(kt,!0);function vn(t,e,o){throw o.input!==void 0&&o.index!==void 0&&(o=o.input),new Error('Cannot parse string "'+(o.length>128?o.length.slice(0,128)+"…":o)+'"')}function yn(t,e,o){let r=-1;for(;++ryn(e,o,s),r);return i===void 0?e.catch?e.catch(o,r):vn(t,e,r):i}var oe=S(bn,!0);var F=Symbol("internals"),H=Symbol("shadow"),re=Object.defineProperties,xn={a:HTMLAnchorElement,article:HTMLElement,dl:HTMLDListElement,p:HTMLParagraphElement,br:HTMLBRElement,fieldset:HTMLFieldSetElement,hr:HTMLHRElement,img:HTMLImageElement,li:HTMLLIElement,ol:HTMLOListElement,optgroup:HTMLOptGroupElement,q:HTMLQuoteElement,section:HTMLElement,textarea:HTMLTextAreaElement,td:HTMLTableCellElement,th:HTMLTableCellElement,tr:HTMLTableRowElement,tbody:HTMLTableSectionElement,thead:HTMLTableSectionElement,tfoot:HTMLTableSectionElement,ul:HTMLUListElement},Sn={name:{set:function(t){return this.setAttribute("name",t)},get:function(){return this.getAttribute("name")||""}},form:{get:function(){return this[F].form}},labels:{get:function(){return this[F].labels}},validity:{get:function(){return this[F].validity}},validationMessage:{get:function(){return this[F].validationMessage}},willValidate:{get:function(){return this[F].willValidate}},checkValidity:{value:function(){return this[F].checkValidity()}},reportValidity:{value:function(){return this[F].reportValidity()}}},En={},ie={once:!0},se=0,ce=!1;function Tn(t){return xn[t]||window["HTML"+t[0].toUpperCase()+t.slice(1)+"Element"]||(()=>{throw new Error('Constructor not found for tag "'+t+'"')})()}var Ln=oe(/^\s*?\s*$|^\s*?\s*$/,{1:(t,e)=>({name:e[1]}),2:(t,e)=>({name:e[3],tag:e[2]}),catch:function(t,e){throw new SyntaxError(`dom element() – name must be of the form 'element-name' or 'tag is="element-name"' (`+e+")")}},null);function kn(t,e){if(t.hasOwnProperty(e)){let o=t[e];delete t[e],t[e]=o}return t}function ae(t,e,o){t._initialLoad=!0;let r=t.attachShadow({mode:e.mode||"closed",delegatesFocus:e.focusable||!1});if(o){let i=w("link",{rel:"stylesheet",href:o});r.append(i)}return t[H]=r,r}function On(t){var e;if(t.attachInternals){if(e=t.attachInternals(),e.setFormValue)return e}else e={shadowRoot:t.shadowRoot};return e.input=w("input",{type:"hidden",name:t.name}),t.appendChild(e.input),e.setFormValue=function(o){this.input.value=o},e}function Fn(t){return!!t.attribute}function Mn(t){return t.set||t.get||t.hasOwnProperty("value")}function Pn(t,e){return Fn(e[1])&&(t.attributes[e[0]]=e[1].attribute),Mn(e[1])&&(t.properties[e[0]]=e[1]),t}function Ot(t,e,o,r,i=""){let{name:s,tag:c}=Ln(t),p=typeof c=="string"?Tn(c):HTMLElement,{attributes:d,properties:f}=o?Object.entries(o).reduce(Pn,{attributes:{},properties:{}}):En;function g(){let m=Reflect.construct(p,arguments,g),y=e.construct&&e.construct.length>se?ae(m,e,r||e.stylesheet):void 0,u=g.formAssociated?On(m):{};return c&&(ce=!0),e.construct&&e.construct.call(m,y,u),f&&Object.keys(f).reduce(kn,m),m}return g.prototype=Object.create(p.prototype,f),f.value&&(g.formAssociated=!0,re(g.prototype,Sn),(e.enable||e.disable)&&(g.prototype.formDisabledCallback=function(m){return m?e.disable&&e.disable.call(this,this[H],this[F]):e.enable&&e.enable.call(this,this[H],this[F])}),e.reset&&(g.prototype.formResetCallback=function(){return e.reset.call(this,this[H],this[F])}),e.restore&&(g.prototype.formStateRestoreCallback=function(){return e.restore.call(this,this[H],this[F])})),d&&(g.observedAttributes=Object.keys(d),g.prototype.attributeChangedCallback=function(m,y,u){return d[m].call(this,u)}),g.prototype.connectedCallback=function(){let m=this,y=m[H],u=m[F];if(m._initialLoad){let l=y.querySelectorAll('link[rel="stylesheet"]');if(l.length){let G=0,yt=l.length,Yt=function(_o){++G>=l.length&&(delete m._initialLoad,e.load&&e.load.call(m,y))},tn=Yt;for(;yt--;)l[yt].addEventListener("load",Yt,ie),l[yt].addEventListener("error",tn,ie)}else e.load&&Promise.resolve(1).then(()=>e.load.call(this,y,u))}e.connect&&e.connect.call(this,y,u)},e.disconnect&&(g.prototype.disconnectedCallback=function(){return e.disconnect.call(this,this[H],this[F])}),window.console&&window.console.log("%c<"+(c?c+" is="+s:s)+">%c "+i,"color: #3a8ab0; font-weight: 600;","color: #888888; font-weight: 400;"),window.customElements.define(s,g,c&&{extends:c}),c&&!ce&&document.querySelectorAll('[is="'+s+'"]').forEach(m=>{o&&re(m,o);let y=e.construct&&e.construct.length>se?ae(m,e,r||e.stylesheet):void 0;e.construct&&e.construct.call(m,y);let u;for(u in d){let l=m.attributes[u];l&&d[u].call(m,l.value)}e.connect&&e.connect.apply(m)}),g}function le(t,e){if(t===e)return!0;if(t===null||e===null||typeof t!="object"||typeof e!="object")return!1;let o=Object.keys(t),r=Object.keys(e),i=o.length;for(;i--;){if(t[o[i]]===void 0){if(e[o[i]]!==void 0)return!1}else if(!e.hasOwnProperty(o[i])||!le(t[o[i]],e[o[i]]))return!1;let s=r.indexOf(o[i]);s>-1&&r.splice(s,1)}for(i=r.length;i--;)if(e[r[i]]===void 0){if(t[r[i]]!==void 0)return!1}else return!1;return!0}var ue=S(le,!0);function In(t,e){return e[t]}var W=S(In,!0);function Ft(t){return function(){return arguments[t]}}function M(){return this}var jn=Object.assign,Cn=Object.create,Hn=Object.freeze;function An(){return!0}function pe(){return-1}var P=Hn(jn(Cn({shift:E,push:E,forEach:E,join:function(){return""},every:An,filter:M,find:E,findIndex:pe,flat:M,flatMap:M,includes:function(){return!1},indexOf:pe,map:M,reduce:Ft(1),sort:M,each:M,pipe:L,start:M,stop:M,done:M,valueOf:function(){return null}}),{length:0}));function V(t,e){t.remove&&t.remove(e);let o;for(;(o=t.indexOf(e))!==-1;)t.splice(o,1);return t}var Kr=S(V,!0);function Mt(t){return t&&t[Symbol.iterator]}var Bn=Object.assign;function Dn(t){return t.stop?t.stop():t()}function N(){}Bn(N.prototype,{stop:function(){let t=this.stopables;return this.stopables=void 0,t&&t.forEach(Dn),this},done:function(t){return(this.stopables||(this.stopables=[])).push(t),this}});var I=Object.assign,j=Object.create;function $(t,e){t[0]=e,e.done(t)}function fe(t,e){let o=t[e].stopables;o&&V(o,t),t[e]=void 0}function v(t,e){t&&t.push(e)}function T(t){N.prototype.stop.apply(t);let e=-1,o;for(;o=t[++e];)t[e]=void 0,o.stop()}function de(t){return a.prototype.isPrototypeOf(t)}function a(t){this.input=t}I(a.prototype,N.prototype,{push:function(t){v(this[0],t)},pipe:function(t){if(this[0])throw new Error("Stream: Attempt to .pipe() a unicast stream multiple times. Create a multicast stream with .broadcast().");return this[0]=t,t.done(this),this.input.pipe(this),t},map:function(t){return new me(this,t)},filter:function(t){return new he(this,t)},split:function(t){return new we(this,t)},flatMap:function(t){return new ge(this,t)},slice:function(t,e){return new Pt(this,t,e)},take:function(t){return console.warn(".take(a) superseded by .slice(0, a)"),new Pt(this,0,t)},each:function(t){return this.pipe(new be(t))},reduce:function(t,e){return this.pipe(new ve(t,e)).value},scan:function(t,e){return new ye(this,t,e)},stop:function(){return T(this),this}});function me(t,e){this.input=t,this.fn=e}me.prototype=I(j(a.prototype),{push:function(e){let r=this.fn(e);r!==void 0&&v(this[0],r)}});function he(t,e){this.input=t,this.fn=e}he.prototype=I(j(a.prototype),{push:function(e){this.fn(e)&&v(this[0],e)}});function ge(t,e){this.input=t,this.fn=e}ge.prototype=I(j(a.prototype),{push:function(e){let r=this.fn(e);if(r!==void 0)if(Mt(r))for(let i of r)v(this[0],i);else r.pipe&&this.done(r.each(i=>v(this[0],i)))}});function we(t,e){this.input=t,this.chunk=[],typeof n=="number"?this.n=e:this.fn=e}we.prototype=I(j(a.prototype),{fn:function(){return this.chunk.length===this.n},push:function(e){let o=this.chunk;this.fn(e)?(v(this[0],o),this.chunk=[]):o.push(e)}});function Pt(t,e,o=1/0){this.input=t,this.index=-e,this.indexEnd=e+o}Pt.prototype=I(j(a.prototype),{push:function(e){++this.index>0&&this[0].push(e),this.index===this.indexEnd&&this.stop()}});function ve(t,e){this.fn=t,this.value=e,this.i=0}ve.prototype=I(j(a.prototype),{push:function(t){let e=this.fn;this.value=e(this.value,t,this.i++,this)}});function ye(t,e,o){this.input=t,this.fn=e,this.value=o}ye.prototype=I(j(a.prototype),{push:function(t){let e=this.fn;this.value=e(this.value,t),this[0].push(this.value)}});function be(t){this.push=t}be.prototype=I(j(a.prototype),{each:null,reduce:null,pipe:null});var zn=Object.assign,Gn=Object.create;function Un(t,e){if(t[1]){let o=-1;for(;t[++o]&&t[o]!==e;);for(;t[o++];)t[o-1]=t[o]}else t.stop()}function X(t,e){a.apply(this,arguments),this.memory=!!(e&&e.memory),e&&e.hot&&this.pipe(P)}X.prototype=zn(Gn(a.prototype),{push:function(t){if(t===void 0)return;this.memory&&(this.value=t);let e=-1;for(;this[++e];)this[e].push(t)},pipe:function(t){let e=-1;for(;this[++e];);return this.memory&&e===0&&this.input.pipe(this),this[e]=t,t.done(()=>Un(this,t)),this.value!==void 0&&t.push(this.value),!this.memory&&e===0&&this.input.pipe(this),t}});var Rn=Array.prototype,Wn=Object.assign,qn=Object.create;function Vn(t){return t!==void 0}function Y(t){this.buffer=t?t.filter?t.filter(Vn):t:[]}Y.prototype=Wn(qn(a.prototype),{push:function(t){t!==void 0&&v(this.buffer,t)},pipe:function(t){for(t.done(this),this[0]=t;this.buffer.length;)v(this[0],Rn.shift.apply(this.buffer));return this.buffer=t,t},stop:function(){return this.buffer=void 0,T(this),this}});var xe=Object.assign,Nn=Object.create,$n=Promise.resolve(),Xn={schedule:function(){$n.then(this.fire)},unschedule:E},Yn={schedule:function(){this.timer=requestAnimationFrame(this.fire)},unschedule:function(){cancelAnimationFrame(this.timer),this.timer=void 0}},_n={schedule:function(){this.timer=setTimeout(this.fire,this.duration*1e3)},unschedule:function(){clearTimeout(this.timer),this.timer=void 0}};function A(t,e){a.apply(this,arguments),this.duration=e,this.timer=void 0,this.fire=()=>{this.timer=void 0,this.output.stop()},xe(this,e==="tick"?Xn:e==="frame"?Yn:_n)}A.prototype=xe(Nn(a.prototype),{push:function(t){this.timer?(this.unschedule(),this.schedule(),this.output.push(t)):(this.output=a.of(t),this[0].push(this.output),this.schedule())},stop:function(){return this.timer&&this.fire(),a.prototype.stop.apply(this,arguments)}});var It=Object.assign,Kn=Object.create,Se=Object.keys;function jt(t,e,o,r,i){this.stream=t,this.names=e,this.values=o,this.name=r,this.input=i}It(jt.prototype,{push:function(t){let e=this.stream,o=this.values,r=this.name;o[r]=t,(e.active||(e.active=Se(o).length===this.names.length))&&v(e[0],It({},o))},stop:function(){--this.stream.count===0&&T(this.stream)},done:function(t){this.stream.done(t)}});function ot(t){this.inputs=t,this.active=!1}ot.prototype=It(Kn(a.prototype),{push:null,pipe:function(t){let e=this.inputs,o=Se(e),r={};this.count=o.length,this[0]=t,t.done(this);let i;for(i in e){let s=e[i];if(s.pipe){let c=new jt(this,o,r,i,s);s.pipe(c)}else if(s.then){let c=new jt(this,o,r,i,s);s.then(p=>c.push(p)),s.finally(()=>c.stop())}else r[i]=s,--this.count}return t}});var Qn=Object.assign,Jn=Object.create;function rt(t){this.fn=t}rt.prototype=Qn(Jn(a.prototype),{pipe:function(t){return t.done(this),this[0]=t,this.fn(e=>this.push(e),e=>this.stop(e)),t}});var Ee=Object.assign,Zn=Object.create;function Te(t){this.stream=t}Ee(Te.prototype,{push:function(t){v(this.stream[0],t)},stop:function(){--this.stream.count===0&&T(this.stream)},done:function(t){this.stream.done(t)}});function it(t){this.inputs=t}it.prototype=Ee(Zn(a.prototype),{push:null,pipe:function(t){let e=this.inputs;this.count=e.length,this[0]=t,t.done(this);let o=new Te(this),r=-1,i;for(;i=e[++r];)if(i.pipe)i.pipe(o);else if(i.then)i.then(s=>o.push(s)),i.finally(()=>o.stop());else{let s=-1;for(;++sv(this,o)),e.finally(()=>T(this)),t}});var no=Array.prototype,Le=Object.assign;function oo(t){throw new TypeError("Stream cannot be created from "+typeof object)}Le(a,{isStream:de,of:function(){return new Y(no.slice.apply(arguments))},from:function(t){return t.pipe?new a(t):t.then?new st(t):typeof t.length=="number"?typeof t=="function"?new rt(t):new Y(t):oo(t)},batch:t=>new A(P,t),burst:t=>(console.warn("Stream.burst() is now Stream.batch()"),new A(P,t)),broadcast:t=>new X(P,t),combine:t=>new ot(t),merge:function(){return new it(arguments)},writeable:function(t){let e=new a(P);return t(e),e}});Le(a.prototype,{log:M,batch:function(t){return new A(this,t)},burst:function(t){return console.warn("stream.burst() is now stream.batch()"),new A(this,t)},broadcast:function(t){return new X(this,t)}});var ro=Object.assign,io=/\s+/,ct={fullscreenchange:U(()=>"fullscreenElement"in document?"fullscreenchange":"webkitFullscreenElement"in document?"webkitfullscreenchange":"mozFullScreenElement"in document?"mozfullscreenchange":"msFullscreenElement"in document?"MSFullscreenChange":"fullscreenchange")},ke=0;window.addEventListener("click",t=>ke=t.timeStamp);function so(t,e){return t.node.addEventListener(ct[e]?ct[e]():e,t,t.options),t}function co(t,e){return t.node.removeEventListener(ct[e]?ct[e]():e,t),t}function Oe(t,e,o){this.types=t.split(io),this.options=e,this.node=o,this.select=e&&e.select}ro(Oe.prototype,{pipe:function(t){$(this,t),this.types.reduce(so,this)},handleEvent:function(t){if(!(t.type==="click"&&t.timeStamp<=ke)){if(this.select){let e=t.target.closest(this.select);if(!e)return;t.selectedTarget=e}v(this[0],t)}},stop:function(){this.types.reduce(co,this),T(this[0])}});function x(t,e){let o;return typeof t=="object"&&(o=t,t=o.type),new a(new Oe(t,o,e))}function Ct(t){return typeof t}var ao=/^\s*([+-]?\d*\.?\d+)([^\s\d]*)\s*$/;function _(t){return function(o){if(typeof o=="number")return o;var r=ao.exec(o);if(!r||!t[r[2]||""]){if(!t.catch)throw new Error('Cannot parse value "'+o+'" (accepted units '+Object.keys(t).join(", ")+")");return r?t.catch(parseFloat(r[1]),r[2]):t.catch(parseFloat(o))}return t[r[2]||""](parseFloat(r[1]))}}var lo=/px$/,Fe={"transform:translateX":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e);return parseFloat(o[4])},"transform:translateY":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e);return parseFloat(o[5])},"transform:scale":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e),r=parseFloat(o[0]),i=parseFloat(o[1]);return Math.sqrt(r*r+i*i)},"transform:rotate":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e),r=parseFloat(o[0]),i=parseFloat(o[1]);return Math.atan2(i,r)}};function at(t){return t.split("(")[1].split(")")[0].split(/\s*,\s*/)}function K(t,e){return window.getComputedStyle?window.getComputedStyle(e,null).getPropertyValue(t):0}function lt(t,e){if(Fe[t])return Fe[t](e);var o=K(t,e);return typeof o=="string"&&lo.test(o)?parseFloat(o):o}var ut,pt;function uo(){if(!ut){let t=document.documentElement.style.fontSize;document.documentElement.style.fontSize="100%",ut=lt("font-size",document.documentElement),document.documentElement.style.fontSize=t||""}return ut}function po(){return pt||(pt=lt("font-size",document.documentElement)),pt}window.addEventListener("resize",()=>{ut=void 0,pt=void 0});var B=b(Ct,{number:L,string:_({px:L,em:t=>uo()*t,rem:t=>po()*t,vw:t=>window.innerWidth*t/100,vh:t=>window.innerHeight*t/100,vmin:t=>window.innerWidthwindow.innerWidth=t*t}function Pe(t,e,o){if(this.stream=t,this.events=[e],this.options=o,this.pointerId=e.pointerId,typeof o.threshold=="function")this.checkThreshold=o.threshold;else{let r=Me(o.threshold);this.checkThreshold=(i,s,c)=>mo(r,i,s,c)}document.addEventListener("pointermove",this),document.addEventListener("pointerup",this),document.addEventListener("pointercancel",this)}At(Pe.prototype,{handleEvent:b(W("type"),{pointermove:function(t){if(this.pointerId===t.pointerId){if(this.pointerId in ft&&this!==ft[this.pointerId]){this.stop();return}if(this.events.push(t),!this.isGesture){let e=this.events[0],o=t.clientX-e.clientX,r=t.clientY-e.clientY,i=(t.timeStamp-e.timeStamp)/1e3;this.checkThreshold(o,r,i)&&this.createGesture()}}},default:function(t){if(this.pointerId!==t.pointerId){console.log("Not the same pointer");return}this.events.push(t),this.stop()}}),createGesture:function(){this.isGesture=!0,this.userSelectState=document.body.style[Ht],document.body.style[Ht]="none",ft[this.pointerId]=this,this.stream.push(new a(this))},pipe:function(t){for($(this,t);this.events.length;)v(this[0],fo.shift.apply(this.events));this.events=t},stop:function(){if(document.removeEventListener("pointermove",this),document.removeEventListener("pointerup",this),document.removeEventListener("pointercancel",this),this.isGesture&&(document.body.style[Ht]=this.userSelectState,delete ft[this.pointerId]),this[0]){let t=this[0];fe(this,0),T(t)}}});function ho(t){var e=t.target.tagName;return e&&(!!dt.ignoreTags[e.toLowerCase()]||t.target.draggable)}function Ie(t,e){this.node=t,this.options=e}At(Ie.prototype,{pipe:function(t){return this[0]=t,this.node.addEventListener("pointerdown",this),t},handleEvent:function(t){if(t.button===0&&!(this.options.device&&!this.options.device.includes(t.pointerType))&&!ho(t)&&!(this.options.select&&!t.target.closest(this.options.select))){var e={type:t.type,target:t.target,currentTarget:t.currentTarget,clientX:t.clientX,clientY:t.clientY,timeStamp:t.timeStamp,pointerId:t.pointerId};new Pe(this[0],e,this.options)}},stop:function(){return this[0]&&(this.node.removeEventListener("pointerdown",this),T(this[0])),this}});function Bt(t,e){return t=e&&t?At({},dt,t):dt,e=e||t,new a(new Ie(e,t))}function Dt(t){return t.which===1&&!t.ctrlKey&&!t.altKey&&!t.shiftKey}var go=Object.assign,Q={bubbles:!0,cancelable:!0};function zt(t,e){var f;let o=Q,r,i,s,c,p,d;return typeof t=="object"?(f=t,{type:t,detail:i,bubbles:s,cancelable:c,composed:p}=f,r=Kt(f,["type","detail","bubbles","cancelable","composed"]),d=go(new CustomEvent(t,{detail:i,bubbles:s||Q.bubbles,cancelable:c||Q.cancelable,composed:p||Q.composed}),r)):d=new CustomEvent(t,Q),e.dispatchEvent(d)}var ns=S(zt,!0);var h=Symbol("data"),J={minScrollInterval:.0375,maxScrollInterval:.18},je=J.maxScrollInterval;function Ce(t){let e=t.length,o=0;for(;--e;){let r=t[e]-t[e-1];o=r>o?r:o}o=oJ.maxScrollInterval?J.maxScrollInterval:1.4*o}function mt(){return je}var Gt="MozAppearance"in document.documentElement.style;var He=!1;Gt&&document.addEventListener("DOMContentLoaded",t=>He=!0);function Ae(t){let e=k(t),o=window.getComputedStyle(t,null),r=B(o.getPropertyValue("padding-left")),i=B(o.getPropertyValue("padding-right"));return e.leftPadding=e.left+r,e.rightPadding=e.left+e.width-i,e.centrePadding=e.leftPadding+(e.width-r-i)/2,e}function Be(t){let e=window.getComputedStyle(t,null).getPropertyValue("scroll-snap-align");return e.endsWith("start")?"left":e.endsWith("end")?"right":"centre"}function De(t,e,o){let r=Ae(t),i=k(e),s=Be(e),c={top:t.scrollTop,left:t.scrollLeft+(s==="left"?i.left-r.leftPadding:s==="right"?i.right-r.rightPadding:i.left+i.width/2-r.centrePadding),behavior:o};t.scrollTo(c),Gt&&!He&&document.addEventListener("DOMContentLoaded",()=>t.scrollTo(c))}function ze(t,e){return De(t,e,"smooth"),e}function D(t,e){return t.style.setProperty("scroll-behavior","auto","important"),De(t,e,"auto"),t.style.setProperty("scroll-behavior",""),e}function Ge(t,e){let{leftPadding:o,rightPadding:r,centrePadding:i}=Ae(t),s=e.length,c;for(;c=e[--s];){let p=k(c);if(!p)continue;let d=Be(c),f=p.width/2+(d==="left"?o:d==="right"?r:i);if((d==="left"?p.left:d==="right"?p.right:p.left+p.width/2)<=f)break}return c}function Ue(t){return!!t.dataset.slideIndex}function Re(t){let{scroller:e,elements:o,children:r}=t,i=Ge(e,o);return Ue(i)?r[i.dataset.slideIndex]:i}function We(t){let{scroller:e,children:o,elements:r}=t,i=Ge(e,r),s;!i||(Ue(i)?(s=o[i.dataset.slideIndex],D(e,s)):s=i,t.activations.push(s))}function ht(t,e,o){let r=e[o];!r||(t.active=r)}function qe(t,e,o){let r=e.indexOf(o)+1;ht(t,e,r)}function Ve(t,e,o){let r=e.indexOf(o)-1;ht(t,e,r)}function wo(t,e){t.style.setProperty("scroll-snap-type",""),e.stop()}var Ne=b((t,e)=>e.type,{pointerdown:function(t,e){return t.e0=e,t.x0=e.clientX,t.y0=e.clientY,t},pointermove:function(t,e){let o=e.clientX,r=e.clientY;if(!t.gesturing){if(Math.abs(o-t.x0)(clearTimeout(c),setTimeout(wo,mt()*1e3,o,f)))}return t.gesturing=!1,t.e0=void 0,t.x0=void 0,t.y0=void 0,t.pointers=void 0,t.scrollLeft0=void 0,t}});var vo=Object.assign;function Z(){}vo(Z.prototype,{pipe:function(t){return this[0]=t,t},stop:function(){return this[0]&&a(this[0]),this}});var yo=Object.assign,bo={capture:!0,passive:!0};function xo(t,e){t.timer=void 0,t.stream.push(e);let o=t.times;o.length>1&&Ce(o),o.length=0}function $e(t){this.element=t,this.times=[]}yo($e.prototype,Z.prototype,{pipe:function(t){this.stream=t,this.element.addEventListener("scroll",this,bo)},handleEvent:function(t){let e=t.timeStamp/1e3;this.times.push(e),this.timer&&clearTimeout(this.timer),this.timer=setTimeout(xo,mt()*1e3,this,t)},stop:function(){this.element.removeEventListener("scroll",this),Z.prototype.stop.apply(this,arguments)}});function Ut(t){return new a(new $e(t))}function So(t,e,o){let r=o.length,i=-1/0;for(;r--;){let f=k(o[r]),g=f.x+f.width;i=g>i?g:i}let s=k(e),c=getComputedStyle(t),p=B(c.paddingLeft||0),d=B(c.paddingRight||0);return p+d+i-s.x}function Eo(t,e,o){let r=So(t,e,o);t.style.setProperty("--scroll-width",r+"px")}function To(t){return!t.dataset.slideIndex}var Xe={mode:"open",construct:function(t){let e=w("slot",{part:"slides"}),o=w("div",{class:"scroller",children:[e]}),r=w("nav",{part:"controls",children:[w("slot",{name:"controls"})]});t.append(o,r);let i=a.broadcast(),s=a.broadcast(),c=a.combine({host:s,elements:x("slotchange",e).map(l=>u.elements=e.assignedElements())}).broadcast({memory:!0}),p=c.map(l=>{let G=l.elements.filter(To);return ue(u.children,G)?void 0:u.children=G}).broadcast({memory:!0,hot:!0}),d=a.of(),f=a.of(),g=f.map(l=>l.dataset.slideIndex?u.children[l.dataset.slideIndex]:l).filter(l=>u.active!==l&&zt("slide-active",l)).map(l=>u.active=l).broadcast({memory:!0,hot:!0}),m=x("click",t).filter(Dt).broadcast(),y=Ut(o).filter(l=>u.connected&&!u.gesturing).broadcast(),u=this[h]={clickSuppressTime:-1/0,connected:!1,host:this,style:window.getComputedStyle(this),elements:P,children:P,device:void 0,shadow:t,scroller:o,slides:e,controls:r,connects:i,load:s,views:d,activations:f,actives:g,slotchanges:c,mutations:p,clicks:m,scrolls:y};a.merge(c,x("resize",window)).filter(l=>e.offsetWidth&&e.offsetHeight).each(l=>Eo(o,e,u.elements)),a.combine({slotchanges:c,connects:i}).map(l=>u.elements.includes(u.active)?u.active:u.children[0]).map(l=>u.connected?D(o,l):l).pipe(f),a.combine({host:s,child:d}).map(l=>u.elements.includes(l.child)&&u.active!==l.child?l.child:void 0).map(l=>u.connected?u.active?ze(o,l):D(o,l):l).pipe(f),y.each(l=>We(u)),Bt({threshold:"0.25rem",device:"mouse"},t).filter(()=>u.children.length>1).each(l=>{u.pointers=l,l.reduce(Ne,u)}),m.each(l=>{l.timeStamp-u.clickSuppressTime<120&&(l.preventDefault(),l.stopPropagation())}),x("fullscreenchange",window).filter(l=>u.active&&e.offsetWidth&&e.offsetHeight).each(l=>{(l.target===this||l.target.contains(this))&&D(o,u.active)}),a.merge(x("pointerdown",this),x("keydown",this)).each(l=>u.device=l.type==="keydown"?"keyboard":l.pointerType),x("focusin",this).filter(l=>u.device==="keyboard").map(l=>u.children.indexOf(l.target)!==-1?l.target:u.children.find(G=>G.contains(l.target))).pipe(d),x("keydown",this).filter(()=>document.activeElement===this||this.contains(document.activeElement)).map(b(W("keyCode"),{37:l=>(l.preventDefault(),u.elements[u.elements.indexOf(u.active)-1]),39:l=>(l.preventDefault(),u.elements[u.elements.indexOf(u.active)+1]),default:E})).pipe(d)},load:function(t){this[h].load.push(this)},connect:function(t){let e=this[h];e.connected=!0,e.connects.push(!0)},disconnect:function(t){let e=this[h];e.connected=!1}};function gt(t){function e(o,r){if(t.getState(o)!==r)return t[r?"enable":"disable"](o)}return{attribute:function(o){return e(this,o!==null)},set:function(o){return e(this,!!o)},get:function(){return t.getState(this)},enumerable:!0}}var Lo=Object.assign;function tt(t,e){this.element=t,this.definitions=e,this.tokens=[]}Lo(tt.prototype,{contains:function(t){return this.tokens.includes(t)},add:function(){let t=arguments.length;for(;t--;){let e=arguments[t];this.tokens.includes(e)||(this.tokens.push(e),this.supports(e)&&this.definitions[e].enable(this.element))}},remove:function(){let t=arguments.length;for(;t--;){let e=arguments[t];this.tokens.includes(e)&&(V(this.tokens,e),this.supports(e)&&this.definitions[e].disable(this.element))}},supports:function(t){return!!this.definitions&&!!this.definitions[t]}});var ko=Array.prototype;function Rt(t,e){let o=t.tokens.slice(),r=ko.slice.apply(e),i=o.length;for(;i--;)r.includes(o[i])&&o.splice(i,1);t.remove.apply(t,o),t.add.apply(t,r)}function Wt(t){let e=Symbol("TokenList");function o(r,i){let s=r[e]||(r[e]=new tt(r,t));Rt(s,i.trim().split(/\s+/))}return{attribute:function(r){o(this,r||"")},set:function(r){o(this,r+"")},get:function(){return this[e]||(this[e]=new tt(this,t))},enumerable:!0}}var qt={};q(qt,{disable:()=>Io,enable:()=>Po,getState:()=>jo});var Oo=_({s:L,ms:t=>t/1e3});function Fo(t){let{active:e,children:o,elements:r,host:i}=t,s=r.indexOf(e),c=r[s+1]||o[0];t.autoplay.timer=null,!!c&&(i.active=c)}function Mo(t){let{active:e,style:o}=t,r=Oo(window.getComputedStyle(e).getPropertyValue("--slide-duration")||o.getPropertyValue("--slide-duration"));clearTimeout(t.autoplay.timer),t.autoplay.timer=setTimeout(Fo,r*1e3,t)}function Ye(t){clearTimeout(t.autoplay.timer),t.autoplay.timer=null}function Po(t){let e=t[h],{actives:o}=e,r=e.autoplay={},i=a.merge([!1],x("pointerenter pointerleave",t).map(c=>c.type==="pointerenter")),s=a.merge([t.contains(document.activeElement)],x("focusin focusout",t).map(b(W("type"),{focusin:c=>!0,focusout:c=>t.contains(c.relatedTarget)}))).map((c=>p=>c===p?void 0:c=p)());r.updates=a.combine({active:o,hover:i,focus:s}).each(c=>c.hover||c.focus?Ye(e):Mo(e))}function Io(t){let e=t[h];Ye(e),e.autoplay.updates.stop(),e.autoplay=void 0}function jo(t){return!!t[h].autoplay}var Vt={};q(Vt,{disable:()=>Ao,enable:()=>Ho,getState:()=>Bo});function _e(t,e){let o=t.cloneNode(!0);return o.dataset.slideIndex=e,o.removeAttribute("id"),o.setAttribute("aria-hidden","true"),o.tabIndex="-1",o}function Co(t){let{active:e,children:o,host:r,scroller:i}=t;if(t.loop.prepends&&(t.loop.prepends.forEach(y=>y.remove()),t.loop.appends.forEach(y=>y.remove()),t.loop.prepends=void 0,t.loop.appends=void 0),o.length<2){t.elements=t.slides.assignedElements();return}let s=r.clientWidth,c=o.map(k),p=c[1].left,d=c[c.length-2].right,f=1;for(;c[++f]&&c[f].leftd-s;);let m=o.slice(++f).map((y,u)=>_e(y,f+u));r.prepend.apply(r,m),r.append.apply(r,g),t.loop.prepends=m,t.loop.appends=g,t.elements=t.slides.assignedElements(),D(i,e||o[0])}function Ho(t){let e=t[h],{mutations:o}=e,r=e.loop={};r.renders=o.each(i=>Co(e))}function Ao(t){let e=t[h];e.loop&&(e.loop.prepends&&e.loop.prepends.forEach(o=>o.remove()),e.loop.appends&&e.loop.appends.forEach(o=>o.remove()),e.loop.renders.stop(),e.loop=void 0)}function Bo(t){return!!t[h].loop}var Nt={};q(Nt,{disable:()=>Go,enable:()=>zo,getState:()=>Uo});function z(t){if(typeof t!="object"||arguments.length>1)throw new Error("delegate() now takes an object of selector:fn pairs.");return function(o){let r=o.target,i;for(i in t){let s=r.closest(i);if(s)return t[i](s,...arguments)}}}function Do(t,e,o,r,i){i===0||t.scrollLeft===0?e.hidden=!0:e.hidden=!1,i===r.length-1||t.scrollLeft>=t.scrollWidth-t.clientWidth?o.hidden=!0:o.hidden=!1}function zo(t){let e=t[h],{actives:o,clicks:r,slotchanges:i,scroller:s,scrolls:c}=e,p=e.navigation={prev:w("button",{part:"prev-button",type:"button",name:"navigation",value:"-1",children:[w("slot",{name:"prev-button",html:` + + Previous + `})]}),next:w("button",{part:"next-button",type:"button",name:"navigation",value:"1",children:[w("slot",{name:"next-button",html:` + + Next + `})]})};e.controls.prepend(p.prev,p.next),p.updates=a.combine({active:o,changes:i,scroll:c}).each(d=>Do(s,p.prev,p.next,d.changes.elements,d.changes.elements.indexOf(d.active))),p.clicks=r.each(z({'[slot="prev-button"]':(d,f)=>{Ve(t,e.elements,e.active)},'[slot="next-button"]':(d,f)=>{qe(t,e.elements,e.active)},'[name="navigation"]':(d,f)=>{let g=e.elements.indexOf(e.active)+parseFloat(d.value);ht(t,e.elements,g)}}))}function Go(t){let e=t[h];e.navigation.prev.remove(),e.navigation.next.remove(),e.navigation.updates.stop(),e.navigation.clicks.stop(),e.navigation=void 0}function Uo(t){return!!t[h].navigation}var $t={};q($t,{disable:()=>qo,enable:()=>Wo,getState:()=>Vo});function Ke(t,e,o){let{active:r,buttons:i,index:s}=t;if(r===o)return;s>-1&&(t.activeSpan.remove(),i.children[s].part.remove("page-button-active"));let c=e.indexOf(o);c!==-1&&(i.children[c].part.add("page-button-active"),i.children[c].append(t.activeSpan),t.index=c,t.active=o)}function Ro(t,e,o,r){return e.buttons&&(e.buttons.remove(),e.buttons=void 0),r.length<2||(e.buttons=w("div",{part:"pagination",children:r.map((i,s)=>w("button",{part:"page-button",type:"button",name:"pagination",value:s}))}),t.append(e.buttons)),r.length}function Wo(t){let e=t[h],{shadow:o,actives:r,clicks:i,mutations:s}=e,c=e.pagination={activeSpan:w("span",{class:"invisible",text:"(Current slide)"})};c.mutations=s.each(()=>Ro(e.controls,c,o,e.children)),c.updates=a.combine({active:r,children:s}).filter(p=>p.children.length>1).each(p=>Ke(c,e.children,e.active)),c.clicks=i.each(z({'[name="pagination"]':function(p,d){let{host:f}=e,g=e.children,m=g[p.value];!m||(f.active=m,Ke(c,g,m))}}))}function qo(t){let e=t[h];e.pagination.buttons.remove(),e.pagination.mutations.stop(),e.pagination.updates.stop(),e.pagination.clicks.stop(),e.pagination=void 0}function Vo(t){return!!t[h].pagination}var Xt={};q(Xt,{disable:()=>$o,enable:()=>No,getState:()=>Xo});var Qe=document.fullscreenEnabled||document.mozFullscreenEnabled||document.webkitFullscreenEnabled||document.msFullscreenEnabled;function wt(){return document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement}function Je(t){return t.requestFullscreen?t.requestFullscreen():t.webkitRequestFullscreen?t.webkitRequestFullscreen():t.mozRequestFullScreen?t.mozRequestFullScreen():t.msRequestFullscreen?t.msRequestFullscreen():void 0}function vt(){document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?document.webkitExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen()}function No(t){let e=t[h];if(!Qe)return;let o=e.fullscreen={button:w("button",{part:"fullscreen-button",type:"button",name:"fullscreen",children:[w("slot",{name:"fullscreen-button",html:` + + Open in fullscreen + Close fullscreen + `})]})};e.controls.append(o.button),o.changes=x("fullscreenchange",t).filter(r=>wt()===t).each(r=>{document.activeElement!==t&&(o.tabIndex=t.tabIndex,t.tabIndex<0&&(t.tabIndex=0),t.focus());let i=x("fullscreenchange",t).each(s=>{t.tabIndex=o.tabIndex,o.tabIndex=void 0,i.stop()})}),o.clicks=e.clicks.each(z({'[slot="fullscreen-button"], [name="fullscreen"]':(r,i)=>{let s=wt();if(s===t){vt();return}s&&vt(),Je(t)}}))}function $o(t){let e=t[h];wt()===t&&vt(),e.fullscreen.button.remove(),e.fullscreen.clicks.stop(),e.fullscreen.changes.stop(),e.fullscreen=void 0}function Xo(t){return!!t[h].fullscreen}var Ze={active:{attribute:function(t){this.active=t},set:function(t){let e=this[h],o=typeof t=="object"?t:/^\d/.test(t+"")?this.querySelector("#\\3"+(t+"")[0]+" "+(t+"").slice(1)):/^\#/.test(t+"")?this.querySelector(t):this.querySelector("#"+t);e.views.push(o)},get:function(){return this[h].active}},activateNext:{value:function(){let{elements:t,views:e,active:o}=this[h];return e.push(t[t.indexOf(o)+1]),this}},activatePrevious:{value:function(){let{elements:t,views:e,active:o}=this[h];return e.push(t[t.indexOf(o)-1]),this}},autoplay:gt(qt,"autoplay"),controls:Wt({navigation:Nt,pagination:$t,fullscreen:Xt}),loop:gt(Vt,"loop")};var Yo=import.meta.url.replace(/\/[^\/]*\.js/,"/slide-show-shadow.css"),kc=Ot('
    ',Xe,Ze,Yo);export{kc as default}; diff --git a/update-11ty/js/slide-show-shadow.css b/update-11ty/js/slide-show-shadow.css new file mode 100644 index 000000000..b96a2827a --- /dev/null +++ b/update-11ty/js/slide-show-shadow.css @@ -0,0 +1,379 @@ +/* slide-show + 1.2.3 + By Stephen Band + Built 2023-02-22 14:59 */ + +:host, +*, +*:before, +*:after { + box-sizing: border-box; + background-origin: padding-box; + background-repeat: no-repeat; + padding: 0; + margin: 0 +} + +button { + font-size: inherit; + color: inherit +} + +[hidden] { + display: none !important +} + +:host { + --slide-duration: 8s; + --padding-left: 0; + --padding-right: 0; + position: relative; + display: flex; + flex-direction: column; + padding-left: 0 !important; + padding-right: 0 !important; + align-content: center !important; + align-items: stretch; + justify-content: center !important; + justify-items: stretch; + grid-auto-flow: row !important; + grid-auto-columns: 100%; + grid-template-columns: none; + column-gap: 0; + grid-auto-rows: auto; + row-gap: 0; + scroll-snap-type: none !important; + scroll-snap-stop: none !important; + -webkit-scroll-behavior: smooth; + scroll-behavior: smooth; + overscroll-behavior-x: none !important; + -ms-overflow-style: none !important; + scrollbar-width: none !important; + overflow: visible +} + +:host([hidden]) { + display: none +} + +:host>.scroller, +:host>[part=controls] { + align-self: stretch +} + +:host>.scroller { + height: 100%; + justify-self: stretch +} + +:host>[part=controls] { + justify-self: auto +} + +.invisible { + position: absolute; + clip: rect(0 0 0 0); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + width: 1px; + height: 1px; + padding: 0; + margin: 0 -1px -1px 0; + overflow: hidden; + white-space: nowrap; + border-width: 0 +} + +svg { + stroke: currentcolor; + stroke-width: inherit; + stroke-linecap: inherit; + fill: inherit; + vector-effect: non-scaling-stroke +} + +.scroller { + display: block; + position: relative; + padding-left: var(--padding-left, 0); + padding-right: var(--padding-right, 0); + scroll-padding-left: var(--padding-left, 0); + scroll-padding-right: var(--padding-right, 0); + overflow: auto; + overflow-y: hidden; + overscroll-behavior-x: none; + overscroll-behavior-x: contain; + scrollbar-width: none; + scroll-snap-type: x mandatory; + scroll-snap-stop: always; + -webkit-scroll-behavior: inherit; + scroll-behavior: inherit; + will-change: -webkit-scroll-behavior; + will-change: scroll-behavior; + grid-template-columns: inherit; + grid-auto-columns: inherit; + column-gap: inherit; + justify-content: inherit; + justify-items: inherit; + grid-template-rows: inherit; + grid-auto-rows: inherit; + row-gap: inherit; + align-content: inherit; + align-items: inherit +} + +.scroller:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: var(--scroll-width, 5000%); + height: 1px; + z-index: -1 +} + +.scroller::-webkit-scrollbar { + -webkit-appearance: none; + display: var(--webkit-scrollbar-display, none); + width: var(--webkit-scrollbar-width); + height: var(--webkit-scrollbar-width); + background: var(--webkit-scrollbar-background); + border: var(--webkit-scrollbar-border); + border-radius: var(--webkit-scrollbar-border-radius) +} + +.scroller::-webkit-scrollbar-thumb:vertical, +.scroller::-webkit-scrollbar-thumb:horizontal { + background: var(--webkit-scrollbar-thumb-background); + border: var(--webkit-scrollbar-thumb-border); + border-radius: var(--webkit-scrollbar-thumb-border-radius) +} + +.scroller>[part=slides] { + position: relative +} + +[part=slides] { + display: grid; + height: 100%; + margin-left: auto; + margin-right: auto; + grid-auto-flow: column; + grid-template-columns: inherit; + grid-auto-columns: inherit; + column-gap: inherit; + justify-content: start; + justify-items: inherit; + grid-template-rows: inherit; + grid-auto-rows: inherit; + row-gap: inherit; + align-content: inherit; + align-items: inherit +} + +[part=slides]::slotted(*) { + scroll-snap-align: center +} + +[part=controls] { + display: grid; + grid-template-columns: [left] 1fr [center] auto [right] 1fr; + height: auto; + padding-left: var(--padding-left); + padding-right: var(--padding-right); + align-content: center; + align-items: center; + justify-items: center; + justify-content: stretch +} + +[part=controls]:empty { + display: none +} + +@media print { + :host>nav { + display: none !important + } +} + +:host(:-webkit-full-screen) { + width: 100vw !important; + height: 100vh !important; + margin: 0 !important; + opacity: 1 !important; + visibility: visible !important; + color: #fff; + background-color: #121212 +} + +:host(:fullscreen) { + width: 100vw !important; + height: 100vh !important; + margin: 0 !important; + opacity: 1 !important; + visibility: visible !important; + color: #fff; + background-color: #121212 +} + +:host(:-webkit-full-screen) [part=fullscreen-button] { + grid-column: auto; + grid-row: auto; + position: absolute; + top: .75em; + right: .75em; + margin: 0; + bottom: auto +} + +:host(:fullscreen) [part=fullscreen-button] { + grid-column: auto; + grid-row: auto; + position: absolute; + top: .75em; + right: .75em; + margin: 0; + bottom: auto +} + +:host(:-webkit-full-screen) .fullscreen-hidden { + display: none !important +} + +:host(:fullscreen) .fullscreen-hidden { + display: none !important +} + +:host(:not(:-webkit-full-screen)) .fullscreen-shown { + display: none !important +} + +:host(:not(:fullscreen)) .fullscreen-shown { + display: none !important +} + +[part=fullscreen-button] { + vertical-align: baseline; + overflow: hidden; + z-index: 2; + grid-column: 3; + grid-row: 1; + justify-self: end; + margin: .375em 0; + padding: 0; + width: 1.875em; + height: 1.875em; + min-width: 0; + line-height: inherit; + text-decoration: none; + cursor: pointer; + background-size: contain; + background-position: 50% 50%; + background-repeat: no-repeat; + background-color: transparent; + border-width: 0; + border-radius: .3125em; + color: inherit; + transition: background-color .2s linear; + stroke-width: 2; + stroke-linecap: round; + fill: none +} + +[part=fullscreen-button]:hover { + transition: background-color .1s linear; + background-color: #ffffff4d +} + +[part=prev-button], +[part=next-button] { + vertical-align: baseline; + overflow: hidden; + position: absolute; + top: 50%; + z-index: 2; + padding: 0; + width: 1.875rem; + min-width: 0; + line-height: inherit; + text-decoration: none; + height: 2.5rem; + margin-top: -1.25rem; + background-size: contain; + background-position: 50% 50%; + background-repeat: no-repeat; + background-color: transparent; + border-width: 0; + border-radius: .3125rem; + color: inherit; + transition: background-color .2s linear; + cursor: pointer; + stroke-width: 2; + stroke-linecap: round; + fill: none +} + +[part=prev-button] { + left: .1875rem +} + +[part=next-button] { + right: .1875rem +} + +[part=prev-button]:hover, +[part=next-button]:hover { + transition: background-color .1s linear; + background-color: #ffffff4d +} + +slot[name=prev-button]>svg, +slot[name=next-button]>svg, +slot[name=prev-button]::slotted(svg), +slot[name=next-button]::slotted(svg) { + width: 100%; + height: 100% +} + +[part=pagination] { + display: flex; + justify-content: center; + grid-column: 2; + grid-row: 1; + justify-self: center; + margin: 0; + padding: .875em 0 +} + +[part*=page-button] { + display: inline-block; + padding: 0 0 0 1.25em; + height: 1.25em; + cursor: pointer; + border-width: 0; + background-color: transparent; + background-size: 100% auto; + background-position: 50% 50%; + background-image: url('data:image/svg+xml;utf8, ') +} + +[part*=page-button-active] { + background-image: url('data:image/svg+xml;utf8, ') +} + +:host(:fullscreen) [part*=page-button-active] { + background-image: url('data:image/svg+xml;utf8, ') +} + +:host(:-webkit-full-screen) [part*=page-button-active] { + background-image: url('data:image/svg+xml;utf8, ') +} + +slot[name=optional]::slotted(*) { + grid-column: 1; + grid-row: 1 +} + + + diff --git a/update-11ty/js/slide-show-ul.js b/update-11ty/js/slide-show-ul.js new file mode 100644 index 000000000..dd65c0c0e --- /dev/null +++ b/update-11ty/js/slide-show-ul.js @@ -0,0 +1,23 @@ +/* slide-show + 1.2.3 + By Stephen Band + Built 2023-02-22 14:59 */ + +var nn=Object.defineProperty;var _t=Object.getOwnPropertySymbols;var on=Object.prototype.hasOwnProperty,rn=Object.prototype.propertyIsEnumerable;var Kt=(t,e)=>{var o={};for(var r in t)on.call(t,r)&&e.indexOf(r)<0&&(o[r]=t[r]);if(t!=null&&_t)for(var r of _t(t))e.indexOf(r)<0&&rn.call(t,r)&&(o[r]=t[r]);return o};var q=(t,e)=>{for(var o in e)nn(t,o,{get:e[o],enumerable:!0})};function U(t){var e=new Map;return function(r){if(e.has(r))return e.get(r);var i=t(r);return e.set(r,i),i}}var sn=Array.prototype;function cn(t,e){return typeof t=="function"?t.apply(null,e):t}function Qt(t,e,o){o=o||t.length;var r=o===1?e?t:U(t):U(function(i){return Qt(function(){var s=[i];return s.push.apply(s,arguments),t.apply(null,s)},e,o-1)});return function i(s){return arguments.length===0?i:arguments.length===1?r(s):arguments.length>=o?t.apply(null,arguments):cn(r(s),sn.slice.call(arguments,1))}}var S=Qt;function an(t,e){return 1-Math.pow(1-e,t)}var Jt=S(an);function E(){}function bt(t,e){return e(t)}function L(t){return t}var ln=Array.prototype;function xt(){let t=arguments;return t.length?e=>ln.reduce.call(t,bt,e):L}function St(t,e,o){return o*(e-t)+t}var un=window.performance,Zt=window.requestAnimationFrame,pn=window.cancelAnimationFrame;function Et(t,e,o){var r=un.now();function i(c){var p=(c-r)/(t*1e3);p<1?(p>0&&e(p),s=Zt(i)):(e(1),o&&o())}var s=Zt(i);return function(){pn(s)}}function et(t,e,o,r,i,s){let c=r[o];return Et(t,xt(e,p=>St(c,i,p),p=>r[o]=p),s)}function fn(){return{x:0,y:0,left:0,top:0,right:window.innerWidth,bottom:window.innerHeight,width:window.innerWidth,height:window.innerHeight}}function k(t){return t===window?fn():t.getClientRects()[0]||t.getBoundingClientRect()}var R={scrollDuration:.3,scrollDurationPerHeight:.125,scrollTransform:Jt(3)},te=E;function dn(t,e){if(e.behavior==="smooth"){let o=t.style.getPropertyValue("scroll-snap-type"),r=()=>{let i=t.scrollLeft,s=t.scrollTop;t.style.setProperty("scroll-snap-type",o),t.scrollLeft=i,t.scrollTop=s};if(t.style.setProperty("scroll-snap-type","none"),e.left!==void 0){let i=t===document.body?window.innerWidth:k(t).width,s=R.scrollDuration+R.scrollDurationPerHeight*Math.abs(e.left-t.scrollLeft)/i;te=et(s,R.scrollTransform,"scrollLeft",t,e.left,r)}else{let i=t===document.body?window.innerHeight:k(t).height,s=R.scrollDuration+R.scrollDurationPerHeight*Math.abs(e.top-t.scrollTop)/i;te=et(s,R.scrollTransform,"scrollTop",t,e.top,r)}}else e.left!==void 0&&(t.scrollLeft=e.left),e.top!==void 0&&(t.scrollTop=e.top)}if(!("scrollBehavior"in document.documentElement.style)){window.console&&console.log("Polyfilling Element.scrollTo(options)");let t="scrollTo"in Element.prototype?Element:HTMLElement,e=t.scrollIntoView;t.prototype.scrollTo=function(o){typeof o=="object"?dn(this,o):e.apply(this,arguments)}}function b(t,e){return function(){let r=t.apply(this,arguments),i=e[r]||e.default;if(!i)throw new Error('overload() no handler for "'+r+'"');return i.apply(this,arguments)}}var mn=b(L,{is:E,tag:E,data:function(t,e,o){Object.assign(e.dataset,o)},html:function(t,e,o){e.innerHTML=o},text:function(t,e,o){e.textContent=o},children:function(t,e,o){e.innerHTML="",e.append.apply(e,o)},points:C,cx:C,cy:C,r:C,transform:C,preserveAspectRatio:C,viewBox:C,default:function(t,e,o){t in e?e[t]=o:e.setAttribute(t,o)}});function C(t,e,o){e.setAttribute(t,o)}function hn(t,e){for(var o=Object.keys(e),r=o.length;r--;)mn(o[r],t,e[o[r]]);return t}var nt=S(hn,!0);var Tt="http://www.w3.org/2000/svg",ee=document.createElement("template"),Lt=(t,e)=>e&&typeof e;function ne(t,e){let o=document.createRange();return o.selectNode(t),o.createContextualFragment(e)}var O=b(Lt,{string:function(t,e){let o=document.createElementNS(Tt,t);return o.innerHTML=e,o},object:function(t,e){let o=document.createElementNS(Tt,t);return typeof e.length=="number"?o.append.apply(o,e):nt(o,e),o},default:t=>document.createElementNS(Tt,t)}),gn=b(Lt,{string:function(t,e){let o=document.createElement(t);return o.innerHTML=e,o},object:function(t,e){let o=document.createElement(t);return typeof e.length=="number"?o.append.apply(o,e):nt(o,e),o},default:t=>document.createElement(t)}),wn=b(L,{comment:function(t,e){return document.createComment(e||"")},fragment:b(Lt,{string:function(t,e,o){return o?ne(o,e):(ee.innerHTML=e,ee.content.cloneNode(!0))},object:function(t,e,o){let r=o?ne(o):document.createDocumentFragment();return typeof e.length=="number"?r.append.apply(r,e):nt(r,e),r},default:()=>document.createDocumentFragment()}),text:function(t,e){return document.createTextNode(e||"")},circle:O,ellipse:O,g:O,glyph:O,image:O,line:O,rect:O,use:O,path:O,pattern:O,polygon:O,polyline:O,svg:O,default:gn}),w=wn;function kt(t,e,o){let r;typeof o!="string"&&o.input!==void 0&&o.index!==void 0&&(r=o,o=r.input.slice(o.index+o[0].length+(o.consumed||0)));let i=t.exec(o);if(!i)return;let s=e(i);return r&&(r.consumed=(r.consumed||0)+i.index+i[0].length+(i.consumed||0)),s}var Pr=S(kt,!0);function vn(t,e,o){throw o.input!==void 0&&o.index!==void 0&&(o=o.input),new Error('Cannot parse string "'+(o.length>128?o.length.slice(0,128)+"…":o)+'"')}function yn(t,e,o){let r=-1;for(;++ryn(e,o,s),r);return i===void 0?e.catch?e.catch(o,r):vn(t,e,r):i}var oe=S(bn,!0);var F=Symbol("internals"),H=Symbol("shadow"),re=Object.defineProperties,xn={a:HTMLAnchorElement,article:HTMLElement,dl:HTMLDListElement,p:HTMLParagraphElement,br:HTMLBRElement,fieldset:HTMLFieldSetElement,hr:HTMLHRElement,img:HTMLImageElement,li:HTMLLIElement,ol:HTMLOListElement,optgroup:HTMLOptGroupElement,q:HTMLQuoteElement,section:HTMLElement,textarea:HTMLTextAreaElement,td:HTMLTableCellElement,th:HTMLTableCellElement,tr:HTMLTableRowElement,tbody:HTMLTableSectionElement,thead:HTMLTableSectionElement,tfoot:HTMLTableSectionElement,ul:HTMLUListElement},Sn={name:{set:function(t){return this.setAttribute("name",t)},get:function(){return this.getAttribute("name")||""}},form:{get:function(){return this[F].form}},labels:{get:function(){return this[F].labels}},validity:{get:function(){return this[F].validity}},validationMessage:{get:function(){return this[F].validationMessage}},willValidate:{get:function(){return this[F].willValidate}},checkValidity:{value:function(){return this[F].checkValidity()}},reportValidity:{value:function(){return this[F].reportValidity()}}},En={},ie={once:!0},se=0,ce=!1;function Tn(t){return xn[t]||window["HTML"+t[0].toUpperCase()+t.slice(1)+"Element"]||(()=>{throw new Error('Constructor not found for tag "'+t+'"')})()}var Ln=oe(/^\s*?\s*$|^\s*?\s*$/,{1:(t,e)=>({name:e[1]}),2:(t,e)=>({name:e[3],tag:e[2]}),catch:function(t,e){throw new SyntaxError(`dom element() – name must be of the form 'element-name' or 'tag is="element-name"' (`+e+")")}},null);function kn(t,e){if(t.hasOwnProperty(e)){let o=t[e];delete t[e],t[e]=o}return t}function ae(t,e,o){t._initialLoad=!0;let r=t.attachShadow({mode:e.mode||"closed",delegatesFocus:e.focusable||!1});if(o){let i=w("link",{rel:"stylesheet",href:o});r.append(i)}return t[H]=r,r}function On(t){var e;if(t.attachInternals){if(e=t.attachInternals(),e.setFormValue)return e}else e={shadowRoot:t.shadowRoot};return e.input=w("input",{type:"hidden",name:t.name}),t.appendChild(e.input),e.setFormValue=function(o){this.input.value=o},e}function Fn(t){return!!t.attribute}function Mn(t){return t.set||t.get||t.hasOwnProperty("value")}function Pn(t,e){return Fn(e[1])&&(t.attributes[e[0]]=e[1].attribute),Mn(e[1])&&(t.properties[e[0]]=e[1]),t}function Ot(t,e,o,r,i=""){let{name:s,tag:c}=Ln(t),p=typeof c=="string"?Tn(c):HTMLElement,{attributes:d,properties:f}=o?Object.entries(o).reduce(Pn,{attributes:{},properties:{}}):En;function g(){let m=Reflect.construct(p,arguments,g),y=e.construct&&e.construct.length>se?ae(m,e,r||e.stylesheet):void 0,u=g.formAssociated?On(m):{};return c&&(ce=!0),e.construct&&e.construct.call(m,y,u),f&&Object.keys(f).reduce(kn,m),m}return g.prototype=Object.create(p.prototype,f),f.value&&(g.formAssociated=!0,re(g.prototype,Sn),(e.enable||e.disable)&&(g.prototype.formDisabledCallback=function(m){return m?e.disable&&e.disable.call(this,this[H],this[F]):e.enable&&e.enable.call(this,this[H],this[F])}),e.reset&&(g.prototype.formResetCallback=function(){return e.reset.call(this,this[H],this[F])}),e.restore&&(g.prototype.formStateRestoreCallback=function(){return e.restore.call(this,this[H],this[F])})),d&&(g.observedAttributes=Object.keys(d),g.prototype.attributeChangedCallback=function(m,y,u){return d[m].call(this,u)}),g.prototype.connectedCallback=function(){let m=this,y=m[H],u=m[F];if(m._initialLoad){let l=y.querySelectorAll('link[rel="stylesheet"]');if(l.length){let G=0,yt=l.length,Yt=function(_o){++G>=l.length&&(delete m._initialLoad,e.load&&e.load.call(m,y))},tn=Yt;for(;yt--;)l[yt].addEventListener("load",Yt,ie),l[yt].addEventListener("error",tn,ie)}else e.load&&Promise.resolve(1).then(()=>e.load.call(this,y,u))}e.connect&&e.connect.call(this,y,u)},e.disconnect&&(g.prototype.disconnectedCallback=function(){return e.disconnect.call(this,this[H],this[F])}),window.console&&window.console.log("%c<"+(c?c+" is="+s:s)+">%c "+i,"color: #3a8ab0; font-weight: 600;","color: #888888; font-weight: 400;"),window.customElements.define(s,g,c&&{extends:c}),c&&!ce&&document.querySelectorAll('[is="'+s+'"]').forEach(m=>{o&&re(m,o);let y=e.construct&&e.construct.length>se?ae(m,e,r||e.stylesheet):void 0;e.construct&&e.construct.call(m,y);let u;for(u in d){let l=m.attributes[u];l&&d[u].call(m,l.value)}e.connect&&e.connect.apply(m)}),g}function le(t,e){if(t===e)return!0;if(t===null||e===null||typeof t!="object"||typeof e!="object")return!1;let o=Object.keys(t),r=Object.keys(e),i=o.length;for(;i--;){if(t[o[i]]===void 0){if(e[o[i]]!==void 0)return!1}else if(!e.hasOwnProperty(o[i])||!le(t[o[i]],e[o[i]]))return!1;let s=r.indexOf(o[i]);s>-1&&r.splice(s,1)}for(i=r.length;i--;)if(e[r[i]]===void 0){if(t[r[i]]!==void 0)return!1}else return!1;return!0}var ue=S(le,!0);function In(t,e){return e[t]}var W=S(In,!0);function Ft(t){return function(){return arguments[t]}}function M(){return this}var jn=Object.assign,Cn=Object.create,Hn=Object.freeze;function An(){return!0}function pe(){return-1}var P=Hn(jn(Cn({shift:E,push:E,forEach:E,join:function(){return""},every:An,filter:M,find:E,findIndex:pe,flat:M,flatMap:M,includes:function(){return!1},indexOf:pe,map:M,reduce:Ft(1),sort:M,each:M,pipe:L,start:M,stop:M,done:M,valueOf:function(){return null}}),{length:0}));function V(t,e){t.remove&&t.remove(e);let o;for(;(o=t.indexOf(e))!==-1;)t.splice(o,1);return t}var Kr=S(V,!0);function Mt(t){return t&&t[Symbol.iterator]}var Bn=Object.assign;function Dn(t){return t.stop?t.stop():t()}function N(){}Bn(N.prototype,{stop:function(){let t=this.stopables;return this.stopables=void 0,t&&t.forEach(Dn),this},done:function(t){return(this.stopables||(this.stopables=[])).push(t),this}});var I=Object.assign,j=Object.create;function $(t,e){t[0]=e,e.done(t)}function fe(t,e){let o=t[e].stopables;o&&V(o,t),t[e]=void 0}function v(t,e){t&&t.push(e)}function T(t){N.prototype.stop.apply(t);let e=-1,o;for(;o=t[++e];)t[e]=void 0,o.stop()}function de(t){return a.prototype.isPrototypeOf(t)}function a(t){this.input=t}I(a.prototype,N.prototype,{push:function(t){v(this[0],t)},pipe:function(t){if(this[0])throw new Error("Stream: Attempt to .pipe() a unicast stream multiple times. Create a multicast stream with .broadcast().");return this[0]=t,t.done(this),this.input.pipe(this),t},map:function(t){return new me(this,t)},filter:function(t){return new he(this,t)},split:function(t){return new we(this,t)},flatMap:function(t){return new ge(this,t)},slice:function(t,e){return new Pt(this,t,e)},take:function(t){return console.warn(".take(a) superseded by .slice(0, a)"),new Pt(this,0,t)},each:function(t){return this.pipe(new be(t))},reduce:function(t,e){return this.pipe(new ve(t,e)).value},scan:function(t,e){return new ye(this,t,e)},stop:function(){return T(this),this}});function me(t,e){this.input=t,this.fn=e}me.prototype=I(j(a.prototype),{push:function(e){let r=this.fn(e);r!==void 0&&v(this[0],r)}});function he(t,e){this.input=t,this.fn=e}he.prototype=I(j(a.prototype),{push:function(e){this.fn(e)&&v(this[0],e)}});function ge(t,e){this.input=t,this.fn=e}ge.prototype=I(j(a.prototype),{push:function(e){let r=this.fn(e);if(r!==void 0)if(Mt(r))for(let i of r)v(this[0],i);else r.pipe&&this.done(r.each(i=>v(this[0],i)))}});function we(t,e){this.input=t,this.chunk=[],typeof n=="number"?this.n=e:this.fn=e}we.prototype=I(j(a.prototype),{fn:function(){return this.chunk.length===this.n},push:function(e){let o=this.chunk;this.fn(e)?(v(this[0],o),this.chunk=[]):o.push(e)}});function Pt(t,e,o=1/0){this.input=t,this.index=-e,this.indexEnd=e+o}Pt.prototype=I(j(a.prototype),{push:function(e){++this.index>0&&this[0].push(e),this.index===this.indexEnd&&this.stop()}});function ve(t,e){this.fn=t,this.value=e,this.i=0}ve.prototype=I(j(a.prototype),{push:function(t){let e=this.fn;this.value=e(this.value,t,this.i++,this)}});function ye(t,e,o){this.input=t,this.fn=e,this.value=o}ye.prototype=I(j(a.prototype),{push:function(t){let e=this.fn;this.value=e(this.value,t),this[0].push(this.value)}});function be(t){this.push=t}be.prototype=I(j(a.prototype),{each:null,reduce:null,pipe:null});var zn=Object.assign,Gn=Object.create;function Un(t,e){if(t[1]){let o=-1;for(;t[++o]&&t[o]!==e;);for(;t[o++];)t[o-1]=t[o]}else t.stop()}function X(t,e){a.apply(this,arguments),this.memory=!!(e&&e.memory),e&&e.hot&&this.pipe(P)}X.prototype=zn(Gn(a.prototype),{push:function(t){if(t===void 0)return;this.memory&&(this.value=t);let e=-1;for(;this[++e];)this[e].push(t)},pipe:function(t){let e=-1;for(;this[++e];);return this.memory&&e===0&&this.input.pipe(this),this[e]=t,t.done(()=>Un(this,t)),this.value!==void 0&&t.push(this.value),!this.memory&&e===0&&this.input.pipe(this),t}});var Rn=Array.prototype,Wn=Object.assign,qn=Object.create;function Vn(t){return t!==void 0}function Y(t){this.buffer=t?t.filter?t.filter(Vn):t:[]}Y.prototype=Wn(qn(a.prototype),{push:function(t){t!==void 0&&v(this.buffer,t)},pipe:function(t){for(t.done(this),this[0]=t;this.buffer.length;)v(this[0],Rn.shift.apply(this.buffer));return this.buffer=t,t},stop:function(){return this.buffer=void 0,T(this),this}});var xe=Object.assign,Nn=Object.create,$n=Promise.resolve(),Xn={schedule:function(){$n.then(this.fire)},unschedule:E},Yn={schedule:function(){this.timer=requestAnimationFrame(this.fire)},unschedule:function(){cancelAnimationFrame(this.timer),this.timer=void 0}},_n={schedule:function(){this.timer=setTimeout(this.fire,this.duration*1e3)},unschedule:function(){clearTimeout(this.timer),this.timer=void 0}};function A(t,e){a.apply(this,arguments),this.duration=e,this.timer=void 0,this.fire=()=>{this.timer=void 0,this.output.stop()},xe(this,e==="tick"?Xn:e==="frame"?Yn:_n)}A.prototype=xe(Nn(a.prototype),{push:function(t){this.timer?(this.unschedule(),this.schedule(),this.output.push(t)):(this.output=a.of(t),this[0].push(this.output),this.schedule())},stop:function(){return this.timer&&this.fire(),a.prototype.stop.apply(this,arguments)}});var It=Object.assign,Kn=Object.create,Se=Object.keys;function jt(t,e,o,r,i){this.stream=t,this.names=e,this.values=o,this.name=r,this.input=i}It(jt.prototype,{push:function(t){let e=this.stream,o=this.values,r=this.name;o[r]=t,(e.active||(e.active=Se(o).length===this.names.length))&&v(e[0],It({},o))},stop:function(){--this.stream.count===0&&T(this.stream)},done:function(t){this.stream.done(t)}});function ot(t){this.inputs=t,this.active=!1}ot.prototype=It(Kn(a.prototype),{push:null,pipe:function(t){let e=this.inputs,o=Se(e),r={};this.count=o.length,this[0]=t,t.done(this);let i;for(i in e){let s=e[i];if(s.pipe){let c=new jt(this,o,r,i,s);s.pipe(c)}else if(s.then){let c=new jt(this,o,r,i,s);s.then(p=>c.push(p)),s.finally(()=>c.stop())}else r[i]=s,--this.count}return t}});var Qn=Object.assign,Jn=Object.create;function rt(t){this.fn=t}rt.prototype=Qn(Jn(a.prototype),{pipe:function(t){return t.done(this),this[0]=t,this.fn(e=>this.push(e),e=>this.stop(e)),t}});var Ee=Object.assign,Zn=Object.create;function Te(t){this.stream=t}Ee(Te.prototype,{push:function(t){v(this.stream[0],t)},stop:function(){--this.stream.count===0&&T(this.stream)},done:function(t){this.stream.done(t)}});function it(t){this.inputs=t}it.prototype=Ee(Zn(a.prototype),{push:null,pipe:function(t){let e=this.inputs;this.count=e.length,this[0]=t,t.done(this);let o=new Te(this),r=-1,i;for(;i=e[++r];)if(i.pipe)i.pipe(o);else if(i.then)i.then(s=>o.push(s)),i.finally(()=>o.stop());else{let s=-1;for(;++sv(this,o)),e.finally(()=>T(this)),t}});var no=Array.prototype,Le=Object.assign;function oo(t){throw new TypeError("Stream cannot be created from "+typeof object)}Le(a,{isStream:de,of:function(){return new Y(no.slice.apply(arguments))},from:function(t){return t.pipe?new a(t):t.then?new st(t):typeof t.length=="number"?typeof t=="function"?new rt(t):new Y(t):oo(t)},batch:t=>new A(P,t),burst:t=>(console.warn("Stream.burst() is now Stream.batch()"),new A(P,t)),broadcast:t=>new X(P,t),combine:t=>new ot(t),merge:function(){return new it(arguments)},writeable:function(t){let e=new a(P);return t(e),e}});Le(a.prototype,{log:M,batch:function(t){return new A(this,t)},burst:function(t){return console.warn("stream.burst() is now stream.batch()"),new A(this,t)},broadcast:function(t){return new X(this,t)}});var ro=Object.assign,io=/\s+/,ct={fullscreenchange:U(()=>"fullscreenElement"in document?"fullscreenchange":"webkitFullscreenElement"in document?"webkitfullscreenchange":"mozFullScreenElement"in document?"mozfullscreenchange":"msFullscreenElement"in document?"MSFullscreenChange":"fullscreenchange")},ke=0;window.addEventListener("click",t=>ke=t.timeStamp);function so(t,e){return t.node.addEventListener(ct[e]?ct[e]():e,t,t.options),t}function co(t,e){return t.node.removeEventListener(ct[e]?ct[e]():e,t),t}function Oe(t,e,o){this.types=t.split(io),this.options=e,this.node=o,this.select=e&&e.select}ro(Oe.prototype,{pipe:function(t){$(this,t),this.types.reduce(so,this)},handleEvent:function(t){if(!(t.type==="click"&&t.timeStamp<=ke)){if(this.select){let e=t.target.closest(this.select);if(!e)return;t.selectedTarget=e}v(this[0],t)}},stop:function(){this.types.reduce(co,this),T(this[0])}});function x(t,e){let o;return typeof t=="object"&&(o=t,t=o.type),new a(new Oe(t,o,e))}function Ct(t){return typeof t}var ao=/^\s*([+-]?\d*\.?\d+)([^\s\d]*)\s*$/;function _(t){return function(o){if(typeof o=="number")return o;var r=ao.exec(o);if(!r||!t[r[2]||""]){if(!t.catch)throw new Error('Cannot parse value "'+o+'" (accepted units '+Object.keys(t).join(", ")+")");return r?t.catch(parseFloat(r[1]),r[2]):t.catch(parseFloat(o))}return t[r[2]||""](parseFloat(r[1]))}}var lo=/px$/,Fe={"transform:translateX":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e);return parseFloat(o[4])},"transform:translateY":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e);return parseFloat(o[5])},"transform:scale":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e),r=parseFloat(o[0]),i=parseFloat(o[1]);return Math.sqrt(r*r+i*i)},"transform:rotate":function(t){var e=K("transform",t);if(!e||e==="none")return 0;var o=at(e),r=parseFloat(o[0]),i=parseFloat(o[1]);return Math.atan2(i,r)}};function at(t){return t.split("(")[1].split(")")[0].split(/\s*,\s*/)}function K(t,e){return window.getComputedStyle?window.getComputedStyle(e,null).getPropertyValue(t):0}function lt(t,e){if(Fe[t])return Fe[t](e);var o=K(t,e);return typeof o=="string"&&lo.test(o)?parseFloat(o):o}var ut,pt;function uo(){if(!ut){let t=document.documentElement.style.fontSize;document.documentElement.style.fontSize="100%",ut=lt("font-size",document.documentElement),document.documentElement.style.fontSize=t||""}return ut}function po(){return pt||(pt=lt("font-size",document.documentElement)),pt}window.addEventListener("resize",()=>{ut=void 0,pt=void 0});var B=b(Ct,{number:L,string:_({px:L,em:t=>uo()*t,rem:t=>po()*t,vw:t=>window.innerWidth*t/100,vh:t=>window.innerHeight*t/100,vmin:t=>window.innerWidthwindow.innerWidth=t*t}function Pe(t,e,o){if(this.stream=t,this.events=[e],this.options=o,this.pointerId=e.pointerId,typeof o.threshold=="function")this.checkThreshold=o.threshold;else{let r=Me(o.threshold);this.checkThreshold=(i,s,c)=>mo(r,i,s,c)}document.addEventListener("pointermove",this),document.addEventListener("pointerup",this),document.addEventListener("pointercancel",this)}At(Pe.prototype,{handleEvent:b(W("type"),{pointermove:function(t){if(this.pointerId===t.pointerId){if(this.pointerId in ft&&this!==ft[this.pointerId]){this.stop();return}if(this.events.push(t),!this.isGesture){let e=this.events[0],o=t.clientX-e.clientX,r=t.clientY-e.clientY,i=(t.timeStamp-e.timeStamp)/1e3;this.checkThreshold(o,r,i)&&this.createGesture()}}},default:function(t){if(this.pointerId!==t.pointerId){console.log("Not the same pointer");return}this.events.push(t),this.stop()}}),createGesture:function(){this.isGesture=!0,this.userSelectState=document.body.style[Ht],document.body.style[Ht]="none",ft[this.pointerId]=this,this.stream.push(new a(this))},pipe:function(t){for($(this,t);this.events.length;)v(this[0],fo.shift.apply(this.events));this.events=t},stop:function(){if(document.removeEventListener("pointermove",this),document.removeEventListener("pointerup",this),document.removeEventListener("pointercancel",this),this.isGesture&&(document.body.style[Ht]=this.userSelectState,delete ft[this.pointerId]),this[0]){let t=this[0];fe(this,0),T(t)}}});function ho(t){var e=t.target.tagName;return e&&(!!dt.ignoreTags[e.toLowerCase()]||t.target.draggable)}function Ie(t,e){this.node=t,this.options=e}At(Ie.prototype,{pipe:function(t){return this[0]=t,this.node.addEventListener("pointerdown",this),t},handleEvent:function(t){if(t.button===0&&!(this.options.device&&!this.options.device.includes(t.pointerType))&&!ho(t)&&!(this.options.select&&!t.target.closest(this.options.select))){var e={type:t.type,target:t.target,currentTarget:t.currentTarget,clientX:t.clientX,clientY:t.clientY,timeStamp:t.timeStamp,pointerId:t.pointerId};new Pe(this[0],e,this.options)}},stop:function(){return this[0]&&(this.node.removeEventListener("pointerdown",this),T(this[0])),this}});function Bt(t,e){return t=e&&t?At({},dt,t):dt,e=e||t,new a(new Ie(e,t))}function Dt(t){return t.which===1&&!t.ctrlKey&&!t.altKey&&!t.shiftKey}var go=Object.assign,Q={bubbles:!0,cancelable:!0};function zt(t,e){var f;let o=Q,r,i,s,c,p,d;return typeof t=="object"?(f=t,{type:t,detail:i,bubbles:s,cancelable:c,composed:p}=f,r=Kt(f,["type","detail","bubbles","cancelable","composed"]),d=go(new CustomEvent(t,{detail:i,bubbles:s||Q.bubbles,cancelable:c||Q.cancelable,composed:p||Q.composed}),r)):d=new CustomEvent(t,Q),e.dispatchEvent(d)}var ns=S(zt,!0);var h=Symbol("data"),J={minScrollInterval:.0375,maxScrollInterval:.18},je=J.maxScrollInterval;function Ce(t){let e=t.length,o=0;for(;--e;){let r=t[e]-t[e-1];o=r>o?r:o}o=oJ.maxScrollInterval?J.maxScrollInterval:1.4*o}function mt(){return je}var Gt="MozAppearance"in document.documentElement.style;var He=!1;Gt&&document.addEventListener("DOMContentLoaded",t=>He=!0);function Ae(t){let e=k(t),o=window.getComputedStyle(t,null),r=B(o.getPropertyValue("padding-left")),i=B(o.getPropertyValue("padding-right"));return e.leftPadding=e.left+r,e.rightPadding=e.left+e.width-i,e.centrePadding=e.leftPadding+(e.width-r-i)/2,e}function Be(t){let e=window.getComputedStyle(t,null).getPropertyValue("scroll-snap-align");return e.endsWith("start")?"left":e.endsWith("end")?"right":"centre"}function De(t,e,o){let r=Ae(t),i=k(e),s=Be(e),c={top:t.scrollTop,left:t.scrollLeft+(s==="left"?i.left-r.leftPadding:s==="right"?i.right-r.rightPadding:i.left+i.width/2-r.centrePadding),behavior:o};t.scrollTo(c),Gt&&!He&&document.addEventListener("DOMContentLoaded",()=>t.scrollTo(c))}function ze(t,e){return De(t,e,"smooth"),e}function D(t,e){return t.style.setProperty("scroll-behavior","auto","important"),De(t,e,"auto"),t.style.setProperty("scroll-behavior",""),e}function Ge(t,e){let{leftPadding:o,rightPadding:r,centrePadding:i}=Ae(t),s=e.length,c;for(;c=e[--s];){let p=k(c);if(!p)continue;let d=Be(c),f=p.width/2+(d==="left"?o:d==="right"?r:i);if((d==="left"?p.left:d==="right"?p.right:p.left+p.width/2)<=f)break}return c}function Ue(t){return!!t.dataset.slideIndex}function Re(t){let{scroller:e,elements:o,children:r}=t,i=Ge(e,o);return Ue(i)?r[i.dataset.slideIndex]:i}function We(t){let{scroller:e,children:o,elements:r}=t,i=Ge(e,r),s;!i||(Ue(i)?(s=o[i.dataset.slideIndex],D(e,s)):s=i,t.activations.push(s))}function ht(t,e,o){let r=e[o];!r||(t.active=r)}function qe(t,e,o){let r=e.indexOf(o)+1;ht(t,e,r)}function Ve(t,e,o){let r=e.indexOf(o)-1;ht(t,e,r)}function wo(t,e){t.style.setProperty("scroll-snap-type",""),e.stop()}var Ne=b((t,e)=>e.type,{pointerdown:function(t,e){return t.e0=e,t.x0=e.clientX,t.y0=e.clientY,t},pointermove:function(t,e){let o=e.clientX,r=e.clientY;if(!t.gesturing){if(Math.abs(o-t.x0)(clearTimeout(c),setTimeout(wo,mt()*1e3,o,f)))}return t.gesturing=!1,t.e0=void 0,t.x0=void 0,t.y0=void 0,t.pointers=void 0,t.scrollLeft0=void 0,t}});var vo=Object.assign;function Z(){}vo(Z.prototype,{pipe:function(t){return this[0]=t,t},stop:function(){return this[0]&&a(this[0]),this}});var yo=Object.assign,bo={capture:!0,passive:!0};function xo(t,e){t.timer=void 0,t.stream.push(e);let o=t.times;o.length>1&&Ce(o),o.length=0}function $e(t){this.element=t,this.times=[]}yo($e.prototype,Z.prototype,{pipe:function(t){this.stream=t,this.element.addEventListener("scroll",this,bo)},handleEvent:function(t){let e=t.timeStamp/1e3;this.times.push(e),this.timer&&clearTimeout(this.timer),this.timer=setTimeout(xo,mt()*1e3,this,t)},stop:function(){this.element.removeEventListener("scroll",this),Z.prototype.stop.apply(this,arguments)}});function Ut(t){return new a(new $e(t))}function So(t,e,o){let r=o.length,i=-1/0;for(;r--;){let f=k(o[r]),g=f.x+f.width;i=g>i?g:i}let s=k(e),c=getComputedStyle(t),p=B(c.paddingLeft||0),d=B(c.paddingRight||0);return p+d+i-s.x}function Eo(t,e,o){let r=So(t,e,o);t.style.setProperty("--scroll-width",r+"px")}function To(t){return!t.dataset.slideIndex}var Xe={mode:"open",construct:function(t){let e=w("slot",{part:"slides"}),o=w("div",{class:"scroller",children:[e]}),r=w("nav",{part:"controls",children:[w("slot",{name:"controls"})]});t.append(o,r);let i=a.broadcast(),s=a.broadcast(),c=a.combine({host:s,elements:x("slotchange",e).map(l=>u.elements=e.assignedElements())}).broadcast({memory:!0}),p=c.map(l=>{let G=l.elements.filter(To);return ue(u.children,G)?void 0:u.children=G}).broadcast({memory:!0,hot:!0}),d=a.of(),f=a.of(),g=f.map(l=>l.dataset.slideIndex?u.children[l.dataset.slideIndex]:l).filter(l=>u.active!==l&&zt("slide-active",l)).map(l=>u.active=l).broadcast({memory:!0,hot:!0}),m=x("click",t).filter(Dt).broadcast(),y=Ut(o).filter(l=>u.connected&&!u.gesturing).broadcast(),u=this[h]={clickSuppressTime:-1/0,connected:!1,host:this,style:window.getComputedStyle(this),elements:P,children:P,device:void 0,shadow:t,scroller:o,slides:e,controls:r,connects:i,load:s,views:d,activations:f,actives:g,slotchanges:c,mutations:p,clicks:m,scrolls:y};a.merge(c,x("resize",window)).filter(l=>e.offsetWidth&&e.offsetHeight).each(l=>Eo(o,e,u.elements)),a.combine({slotchanges:c,connects:i}).map(l=>u.elements.includes(u.active)?u.active:u.children[0]).map(l=>u.connected?D(o,l):l).pipe(f),a.combine({host:s,child:d}).map(l=>u.elements.includes(l.child)&&u.active!==l.child?l.child:void 0).map(l=>u.connected?u.active?ze(o,l):D(o,l):l).pipe(f),y.each(l=>We(u)),Bt({threshold:"0.25rem",device:"mouse"},t).filter(()=>u.children.length>1).each(l=>{u.pointers=l,l.reduce(Ne,u)}),m.each(l=>{l.timeStamp-u.clickSuppressTime<120&&(l.preventDefault(),l.stopPropagation())}),x("fullscreenchange",window).filter(l=>u.active&&e.offsetWidth&&e.offsetHeight).each(l=>{(l.target===this||l.target.contains(this))&&D(o,u.active)}),a.merge(x("pointerdown",this),x("keydown",this)).each(l=>u.device=l.type==="keydown"?"keyboard":l.pointerType),x("focusin",this).filter(l=>u.device==="keyboard").map(l=>u.children.indexOf(l.target)!==-1?l.target:u.children.find(G=>G.contains(l.target))).pipe(d),x("keydown",this).filter(()=>document.activeElement===this||this.contains(document.activeElement)).map(b(W("keyCode"),{37:l=>(l.preventDefault(),u.elements[u.elements.indexOf(u.active)-1]),39:l=>(l.preventDefault(),u.elements[u.elements.indexOf(u.active)+1]),default:E})).pipe(d)},load:function(t){this[h].load.push(this)},connect:function(t){let e=this[h];e.connected=!0,e.connects.push(!0)},disconnect:function(t){let e=this[h];e.connected=!1}};function gt(t){function e(o,r){if(t.getState(o)!==r)return t[r?"enable":"disable"](o)}return{attribute:function(o){return e(this,o!==null)},set:function(o){return e(this,!!o)},get:function(){return t.getState(this)},enumerable:!0}}var Lo=Object.assign;function tt(t,e){this.element=t,this.definitions=e,this.tokens=[]}Lo(tt.prototype,{contains:function(t){return this.tokens.includes(t)},add:function(){let t=arguments.length;for(;t--;){let e=arguments[t];this.tokens.includes(e)||(this.tokens.push(e),this.supports(e)&&this.definitions[e].enable(this.element))}},remove:function(){let t=arguments.length;for(;t--;){let e=arguments[t];this.tokens.includes(e)&&(V(this.tokens,e),this.supports(e)&&this.definitions[e].disable(this.element))}},supports:function(t){return!!this.definitions&&!!this.definitions[t]}});var ko=Array.prototype;function Rt(t,e){let o=t.tokens.slice(),r=ko.slice.apply(e),i=o.length;for(;i--;)r.includes(o[i])&&o.splice(i,1);t.remove.apply(t,o),t.add.apply(t,r)}function Wt(t){let e=Symbol("TokenList");function o(r,i){let s=r[e]||(r[e]=new tt(r,t));Rt(s,i.trim().split(/\s+/))}return{attribute:function(r){o(this,r||"")},set:function(r){o(this,r+"")},get:function(){return this[e]||(this[e]=new tt(this,t))},enumerable:!0}}var qt={};q(qt,{disable:()=>Io,enable:()=>Po,getState:()=>jo});var Oo=_({s:L,ms:t=>t/1e3});function Fo(t){let{active:e,children:o,elements:r,host:i}=t,s=r.indexOf(e),c=r[s+1]||o[0];t.autoplay.timer=null,!!c&&(i.active=c)}function Mo(t){let{active:e,style:o}=t,r=Oo(window.getComputedStyle(e).getPropertyValue("--slide-duration")||o.getPropertyValue("--slide-duration"));clearTimeout(t.autoplay.timer),t.autoplay.timer=setTimeout(Fo,r*1e3,t)}function Ye(t){clearTimeout(t.autoplay.timer),t.autoplay.timer=null}function Po(t){let e=t[h],{actives:o}=e,r=e.autoplay={},i=a.merge([!1],x("pointerenter pointerleave",t).map(c=>c.type==="pointerenter")),s=a.merge([t.contains(document.activeElement)],x("focusin focusout",t).map(b(W("type"),{focusin:c=>!0,focusout:c=>t.contains(c.relatedTarget)}))).map((c=>p=>c===p?void 0:c=p)());r.updates=a.combine({active:o,hover:i,focus:s}).each(c=>c.hover||c.focus?Ye(e):Mo(e))}function Io(t){let e=t[h];Ye(e),e.autoplay.updates.stop(),e.autoplay=void 0}function jo(t){return!!t[h].autoplay}var Vt={};q(Vt,{disable:()=>Ao,enable:()=>Ho,getState:()=>Bo});function _e(t,e){let o=t.cloneNode(!0);return o.dataset.slideIndex=e,o.removeAttribute("id"),o.setAttribute("aria-hidden","true"),o.tabIndex="-1",o}function Co(t){let{active:e,children:o,host:r,scroller:i}=t;if(t.loop.prepends&&(t.loop.prepends.forEach(y=>y.remove()),t.loop.appends.forEach(y=>y.remove()),t.loop.prepends=void 0,t.loop.appends=void 0),o.length<2){t.elements=t.slides.assignedElements();return}let s=r.clientWidth,c=o.map(k),p=c[1].left,d=c[c.length-2].right,f=1;for(;c[++f]&&c[f].leftd-s;);let m=o.slice(++f).map((y,u)=>_e(y,f+u));r.prepend.apply(r,m),r.append.apply(r,g),t.loop.prepends=m,t.loop.appends=g,t.elements=t.slides.assignedElements(),D(i,e||o[0])}function Ho(t){let e=t[h],{mutations:o}=e,r=e.loop={};r.renders=o.each(i=>Co(e))}function Ao(t){let e=t[h];e.loop&&(e.loop.prepends&&e.loop.prepends.forEach(o=>o.remove()),e.loop.appends&&e.loop.appends.forEach(o=>o.remove()),e.loop.renders.stop(),e.loop=void 0)}function Bo(t){return!!t[h].loop}var Nt={};q(Nt,{disable:()=>Go,enable:()=>zo,getState:()=>Uo});function z(t){if(typeof t!="object"||arguments.length>1)throw new Error("delegate() now takes an object of selector:fn pairs.");return function(o){let r=o.target,i;for(i in t){let s=r.closest(i);if(s)return t[i](s,...arguments)}}}function Do(t,e,o,r,i){i===0||t.scrollLeft===0?e.hidden=!0:e.hidden=!1,i===r.length-1||t.scrollLeft>=t.scrollWidth-t.clientWidth?o.hidden=!0:o.hidden=!1}function zo(t){let e=t[h],{actives:o,clicks:r,slotchanges:i,scroller:s,scrolls:c}=e,p=e.navigation={prev:w("button",{part:"prev-button",type:"button",name:"navigation",value:"-1",children:[w("slot",{name:"prev-button",html:` + + Previous + `})]}),next:w("button",{part:"next-button",type:"button",name:"navigation",value:"1",children:[w("slot",{name:"next-button",html:` + + Next + `})]})};e.controls.prepend(p.prev,p.next),p.updates=a.combine({active:o,changes:i,scroll:c}).each(d=>Do(s,p.prev,p.next,d.changes.elements,d.changes.elements.indexOf(d.active))),p.clicks=r.each(z({'[slot="prev-button"]':(d,f)=>{Ve(t,e.elements,e.active)},'[slot="next-button"]':(d,f)=>{qe(t,e.elements,e.active)},'[name="navigation"]':(d,f)=>{let g=e.elements.indexOf(e.active)+parseFloat(d.value);ht(t,e.elements,g)}}))}function Go(t){let e=t[h];e.navigation.prev.remove(),e.navigation.next.remove(),e.navigation.updates.stop(),e.navigation.clicks.stop(),e.navigation=void 0}function Uo(t){return!!t[h].navigation}var $t={};q($t,{disable:()=>qo,enable:()=>Wo,getState:()=>Vo});function Ke(t,e,o){let{active:r,buttons:i,index:s}=t;if(r===o)return;s>-1&&(t.activeSpan.remove(),i.children[s].part.remove("page-button-active"));let c=e.indexOf(o);c!==-1&&(i.children[c].part.add("page-button-active"),i.children[c].append(t.activeSpan),t.index=c,t.active=o)}function Ro(t,e,o,r){return e.buttons&&(e.buttons.remove(),e.buttons=void 0),r.length<2||(e.buttons=w("div",{part:"pagination",children:r.map((i,s)=>w("button",{part:"page-button",type:"button",name:"pagination",value:s}))}),t.append(e.buttons)),r.length}function Wo(t){let e=t[h],{shadow:o,actives:r,clicks:i,mutations:s}=e,c=e.pagination={activeSpan:w("span",{class:"invisible",text:"(Current slide)"})};c.mutations=s.each(()=>Ro(e.controls,c,o,e.children)),c.updates=a.combine({active:r,children:s}).filter(p=>p.children.length>1).each(p=>Ke(c,e.children,e.active)),c.clicks=i.each(z({'[name="pagination"]':function(p,d){let{host:f}=e,g=e.children,m=g[p.value];!m||(f.active=m,Ke(c,g,m))}}))}function qo(t){let e=t[h];e.pagination.buttons.remove(),e.pagination.mutations.stop(),e.pagination.updates.stop(),e.pagination.clicks.stop(),e.pagination=void 0}function Vo(t){return!!t[h].pagination}var Xt={};q(Xt,{disable:()=>$o,enable:()=>No,getState:()=>Xo});var Qe=document.fullscreenEnabled||document.mozFullscreenEnabled||document.webkitFullscreenEnabled||document.msFullscreenEnabled;function wt(){return document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement}function Je(t){return t.requestFullscreen?t.requestFullscreen():t.webkitRequestFullscreen?t.webkitRequestFullscreen():t.mozRequestFullScreen?t.mozRequestFullScreen():t.msRequestFullscreen?t.msRequestFullscreen():void 0}function vt(){document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?document.webkitExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen()}function No(t){let e=t[h];if(!Qe)return;let o=e.fullscreen={button:w("button",{part:"fullscreen-button",type:"button",name:"fullscreen",children:[w("slot",{name:"fullscreen-button",html:` + + Open in fullscreen + Close fullscreen + `})]})};e.controls.append(o.button),o.changes=x("fullscreenchange",t).filter(r=>wt()===t).each(r=>{document.activeElement!==t&&(o.tabIndex=t.tabIndex,t.tabIndex<0&&(t.tabIndex=0),t.focus());let i=x("fullscreenchange",t).each(s=>{t.tabIndex=o.tabIndex,o.tabIndex=void 0,i.stop()})}),o.clicks=e.clicks.each(z({'[slot="fullscreen-button"], [name="fullscreen"]':(r,i)=>{let s=wt();if(s===t){vt();return}s&&vt(),Je(t)}}))}function $o(t){let e=t[h];wt()===t&&vt(),e.fullscreen.button.remove(),e.fullscreen.clicks.stop(),e.fullscreen.changes.stop(),e.fullscreen=void 0}function Xo(t){return!!t[h].fullscreen}var Ze={active:{attribute:function(t){this.active=t},set:function(t){let e=this[h],o=typeof t=="object"?t:/^\d/.test(t+"")?this.querySelector("#\\3"+(t+"")[0]+" "+(t+"").slice(1)):/^\#/.test(t+"")?this.querySelector(t):this.querySelector("#"+t);e.views.push(o)},get:function(){return this[h].active}},activateNext:{value:function(){let{elements:t,views:e,active:o}=this[h];return e.push(t[t.indexOf(o)+1]),this}},activatePrevious:{value:function(){let{elements:t,views:e,active:o}=this[h];return e.push(t[t.indexOf(o)-1]),this}},autoplay:gt(qt,"autoplay"),controls:Wt({navigation:Nt,pagination:$t,fullscreen:Xt}),loop:gt(Vt,"loop")};var Yo=import.meta.url.replace(/\/[^\/]*\.js/,"/slide-show-shadow.css"),kc=Ot('