A package for consuming WebExtensions API in a browser extension.
These API classes are generated based on the Mozilla documentation for WebExtensions API.
This package can be consumed in two methods.
Create a browser extension using Blazor. Refer to the package Blazor.BrowserExtension to get started.
Create a standard browser extension using JavaScript and load the WebAssembly manually. The .Net source code can be compiled into wasm using Mono.
- Install
WebExtensions.Net
(if you intend to use the API without dependency injection) orWebExtensions.Net.Extensions.DependencyInjection
from Nuget. - Import the
JsBind.Net
scripts with<script src="_content/JsBind.Net/JsBindNet.js"></script>
. If your project does not support Razor Class Library contents, add the property<LinkJsBindAssets>true</LinkJsBindAssets>
to your project. - Import the WebExtensions polyfill by Mozilla for cross browser compatibility. This polyfill helps to convert the callback based Chrome extensions API to a Promise based API for asynchronous functions.
- Consume the WebExtensions API by creating an instance of
WebExtensionsApi
as shown below.
using JsBind.Net;
using JsBind.Net.Configurations;
using WebExtensions.Net;
...
var options = new JsBindOptionsConfigurator()
.UseInProcessJsRuntime()
.Options;
// iJsRuntime is an instance of MonoWebAssemblyJSRuntime
var jsRuntimeAdapter = new JsRuntimeAdapter(iJsRuntime, options);
var webExtensionsApi = new WebExtensionsApi(jsRuntimeAdapter);
// Use the WebExtensions API
var manifest = await webExtensionsApi.Runtime.GetManifest();
For the purpose of debugging and testing outside of the browser extension environment, there is a MockJsRuntimeAdapter
class under the WebExtensions.Net.Mock
namespace.
Initialize an instance of the mock API with:
using WebExtensions.Net;
using WebExtensions.Net.Mock;
...
var jsRuntimeAdapter = new MockJsRuntimeAdapter();
var webExtensionsApi = new WebExtensionsApi(jsRuntimeAdapter);
To configure the behaviour of the mock API, you may use any combination of the following:
MockResolvers.Configure(configure =>
{
// configure a method without any argument
configure.Api.Property(api => api.Runtime.Id).Returns(() => "MyExtensionId");
// or
configure.Api.Property(api => api.Runtime.Id).ReturnsForAnyArgs("MyExtensionId");
// configure a method with one argument
configure.Api.Method<string, string>(api => api.Runtime.GetURL).Returns(path => builder.HostEnvironment.BaseAddress + path);
// configure a method that returns the same object regardless of the arguments
configure.Api.Method<string, NotificationOptions, string>(api => api.Notifications.Create).ReturnsForAnyArgs("NotificationId");
// configure an action to be invoked when an API is called
configure.Api.Method<int?>(api => api.Tabs.GoForward).Invokes(tabId => { /* Do something with tabId */ });
// configure a method on an object reference
using var emptyJson = JsonDocument.Parse("{}");
configure.ObjectReference(DefaultMockObjects.LocalStorage).Method<StorageAreaGetKeys, JsonElement>(storage => storage.Get).ReturnsForAnyArgs(emptyJson.RootElement.Clone());
// configure an action to be invoked when a method on an object reference is called
configure.ObjectReference(DefaultMockObjects.LocalStorage).Method(storage => storage.Clear).Invokes(() => { /* Do something */ });
// configure a generic delegate to handle all the API calls
bool apiHandler(string targetPath, object[] arguments, out object result)
{
if (targetPath == "runtime.id")
{
result = "MyExtensionId";
return true;
}
result = null;
return false;
}
configure.ApiHandler(apiHandler);
// configure a generic delegate to handle all the invocations to object references
bool objectReferenceHandler(object objectReference, string targetPath, object[] arguments, out object result)
{
if (objectReference == DefaultMockObjects.LocalStorage && targetPath == "get")
{
using var emptyJson = JsonDocument.Parse("{}");
result = emptyJson.RootElement.Clone();
return true;
}
result = null;
return false;
}
configure.ObjectReferenceHandler(objectReferenceHandler);
});
Note: The sequence of mock registration matters.
Overall the more specific method Returns
and ReturnsForAnyArgs
is prioritized over the generic delegate ApiHandler
and ObjectReferenceHandler
.
If there exists registration that handles the same API or object reference call, the last registered handler will be used.
For example:
MockResolvers.Configure(configure =>
{
// when api.Runtime.GetId is called it will return "MyExtensionId2"
configure.Api.Property(api => api.Runtime.Id).Returns(() => "MyExtensionId1");
configure.Api.Property(api => api.Runtime.Id).Returns(() => "MyExtensionId2");
});
MockResolvers.Configure(configure =>
{
// when api.Runtime.GetId is called it will return "MyExtensionId1", even though the generic API handler is registered last, the more specific method registration is prioritized.
configure.Api.Property(api => api.Runtime.Id).Returns(() => "MyExtensionId1");
bool apiHandler(string targetPath, object[] arguments, out object result)
{
if (targetPath == "runtime.id")
{
result = "MyExtensionId2";
return true;
}
result = null;
return false;
}
configure.ApiHandler(apiHandler);
});