Build a browser extension with Blazor.
This package imports two other packages, which are:
- WebExtension.Net - Provides interop for WebExtension standard API.
- Blazor.BrowserExtension.Build (in this repository) - Adds build target and tasks to the project.
- Create a new Blazor WebAssembly App project (skip to step 3 if you have an existing Blazor WebAssembly project).
- Target Framework should be at least .Net 5.0 and uncheck/deselect other options such as authentication, HTTPS support, ASP.Net Core hosting support and PWA.
- Install NuGet package
Blazor.BrowserExtension - Add a new file
manifest.jsonunder thewwwrootfolder. An example of minimalmanifest.jsonfile:
{
"manifest_version": 2,
"name": "My Blazor Extension",
"description": "My browser extension built with Blazor",
"version": "1.0",
"background": {
"page": "index.html?path=background",
"persistent": true
},
"content_security_policy": "script-src 'self' 'unsafe-eval' 'wasm-eval' 'sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA='; object-src 'self'",
"web_accessible_resources": [
"framework/*",
"BrowserExtensionScripts/*",
"WebExtensionScripts/*"
],
"permissions": [
"*://*/*",
"webRequest",
"webRequestBlocking"
]
}- Add the following to the
.csprojfile to make sure that all the files underwwwrootwill always be copied to the output.
<ItemGroup>
<None Include="wwwroot\**\*" CopyToOutputDirectory="Always" />
</ItemGroup>- In
wwwroot/index.htmlreplace the script tag<script src="_framework/blazor.webassembly.js"></script>with<script src="BrowserExtensionScripts/Core.js"></script> - In
Pages/Index.razorreplace the first line@page "/"with the following lines:
@page "/"
@page "/index.html"
@inherits Blazor.BrowserExtension.Pages.IndexPage- Add a
Background.razorfile underPagesfolder (Right click on thePagesfolder and select Add -> Razor Component), with the following content:
@page "/background"
@inherits Blazor.BrowserExtension.Pages.BackgroundPage- Add the following into
Program.csfile.
using Blazor.BrowserExtension;
...
public static async Task Main(string[] args)
{
...
builder.Services.AddBrowserExtensionServices(options =>
{
options.ProjectNamespace = typeof(Program).Namespace;
});
...
}In Google Chrome, go to the Extensions page (Menu -> More tools -> Extensions) and switch on Developer mode.
Click on Load unpacked button and navigate to %ProjectDir%\bin\Debug\net5.0\ and select the foler wwwroot
Add the following to the manifest.json
"browser_action": {
"default_popup": "index.html?path=popup"
}Add a Popup.razor Razor component under Pages folder with the following content.
@page "/popup"
@inherits Blazor.BrowserExtension.Pages.BasePage
<h1>My popup page</h1>Add the following to the manifest.json
"options_page": "index.html?path=options"Add a Options.razor Razor component under Pages folder with the following content.
@page "/options"
@inherits Blazor.BrowserExtension.Pages.BasePage
<h1>My options page</h1>Add the following to the manifest.json
"content_scripts": [
{
"matches": [ "*://*/*" ],
"js": [ "BrowserExtensionScripts/ContentScript.js" ]
}
]Add a ContentScript.razor Razor component under Pages folder with the following content.
@page "/contentscript"
@inherits Blazor.BrowserExtension.Pages.BasePage
<h1>My content script</h1>Additional changes are required for content scripts to not have conflict of the element ID of the Blazor root component with any other elements in any pages.
- In
index.htmlchange the IDappof line<div id="app">Loading...</div>to%SafeProjectName%_app. - In
Program.cschange the ID#appof linebuilder.RootComponents.Add<App>("#app");to#%SafeProjectName%_app.
Example:
| Project Name | Safe Project Name | Element Name |
|---|---|---|
| MyProject | MyProject | MyProject_app |
| My.Project | My_Project | My_Project_app |
| My Project | My_Project | My_Project_app |
| My-Project | My_Project | My_Project_app |
In App.razor, add the following if statement to opt out of routing only for content scripts.
@using My.Project.Pages;
@inject NavigationManager Navigation
@if (Navigation.Uri.StartsWith("chrome-extension://"))
{
<Router ...>
...
</Router>
}
else
{
<ContentScript></ContentScript>
}When you inherit from the BasePage, a few properties are injected for you to consume.
This includes the WebExtension API, Logger, JavaScript interop, etc.
The WebExtension API is provided by the package WebExtension.Net, and you can consume the API in a page:
@inherits Blazor.BrowserExtension.Pages.BasePage;
<button @onclick="GetPluginIndexUrl">Get plugin Index page URL</button>
<p>@pluginIndexUrl</p>
@code {
string pluginIndexUrl = null;
async Task GetPluginIndexUrl()
{
pluginIndexUrl = await WebExtension.Runtime.GetURL("index.html");
}
}You can use the @page directive to add route attribute to a Razor page, for example @page "/options".
Usually in a server hosted application, you can access the route by just going to domain.com/options.
However in a browser extension, for example in Google Chrome, if you are try to access the route directly from the URL, e.g. chrome-extension://extesion_id/options, the background page will automatically intercept the request and redirect to index.html?page=options. This is because the Blazor application is not hosted on a server, therefore only the static files are served in a browser extension.
The following MSBuild properties can be specified in your project file or when running dotnet build command.
| Property | Default value | Description |
|---|---|---|
| BrowserExtensionAssetsPath | wwwroot | The root folder where the JavaScript files should be added as link. |
| BuildBlazorToBrowserExtension | true | If set to false, the Blazor to Browser Extension build target will be skipped. |
| IncludeBrowserExtensionAssets | true | If set to false, the JavaScript files will not be added as to the project. |
Find out how to build a cross browser extension with the links below: