Teaching the
Web new Tricks
When we introduced our support for Web Tiles we explained that they are automatically subject to a restrictive CSP in order to prevent data leaks to 3rd parties. That make Web Tiles safe to use what are considered "dangerous" APIs, eg. those accessing hardware APIs or other privacy sensitive data sets on your devices like your address book.
That makes Web Tiles an ideal build block for composable, self contained components that can fulfill tasks for a regular page even if they require evelated privileges.
What we are aiming at is in fact a generalization of what is already done
by some Web features: when you use an
<input type="file"> element, the page won't have access
to the full filesystem: this is delegated to the browser to allow the user
to pick the data to return.
The principles of Web Tasks are not really a new thing. Several projects have explored the space already, including:
The details are not that important. In our task implementation, we mostly used the MozActivity model with a few tweaks, as it seems to be flexible enough.
We support two ways to register tasks:
<link rel="webtasks" href="tasks.json" > link
element with the "webtasks" relationship. Our implementation currently
registers tasks as you navigate pages. We may change that and gate the
registration on an explicit user action in the future.
navigator.embedder.registerTaskProvider(). This is mostly
useful for internal registration that we want to run without navigating
to a page.
Triggering a task is simple. We'll show a demo of an image editor and all it takes for the caller is:
const result = await navigator.requestTask("edit-image", {
image: currentBlob,
type: "image/png",
});
This will match the task registered by the image editor:
{
"name": "edit-image",
"title": "Image Editor",
"description": "Annotate and edit images",
"href": "beaver://imageeditor/index.html",
"filters": {
"type": ["image/png", "image/jpeg", "image/webp"]
},
"display": "window",
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...+PC9zdmc+"
}
Once the user has chosen a task provider, the page is opened and the provider can access the data and send a response with:
const task = await window.acceptTask();
console.log(`Will process task ${task.name}`);
const blob = task.data.image;
...
task.port.postMessage(editedBlob);
The provider receives an object with the task name and data as set in
requestTask, and a message port. The result can be sent back
by a regular call to postMessage on the message port.
The system itself takes care of rejecting the
acceptTask() promise in various situations like the provider
being closed before sending a response, or if the user doesn't pick a
provider at all.
Building on the existing support for communication among paired devices, we can enable cross device tasks. This is achieved by sending to other peers local registrations and bridging message ports between the devices. That makes it user friendly to leverage multiple devices and use the one with the most appropriate capabilities.
Here's a screencast showing how we can use our image editing provider. In this case, we pick a provider on the laptop since it's a better experience than editing on the smaller phone screen!
Updated Apr 11 2026