A Chrome extension is a tiny app used for Chromium-based web browsers for many purposes. It's like a web application serving static contents, which doesn't need a web server. If this is the case, what if we can build the Chrome extension using Blazor WebAssembly? Throughout this post, I'm going to migrate the existing JavaScript-based Chrome extension to Blazor WebAssembly with minimal code changes.
You can download the sample Chrome extension from this GitHub repository.
Series: Browser Extension with Blazor WASM
- Lift & Shift Existing Chrome Extension to Blazor WebAssembly - Applying Blazor WASM 👈
- Lift & Shift Existing Chrome Extension to Blazor WebAssembly #2 - JavaScript Interop
- Lift & Shift Existing Chrome Extension to Blazor WebAssembly #3 - Cross Browser Compatibility
Chrome Extension – JavaScript-based
There's a Chrome extension app based on the Manifest v2. Load it to your Edge browser, and it will look like the following image.
You can change the background colour if you run it on your web browser.
Because it's written in JavaScript, let's migrate this app to Blazor WebAssembly.
Due to the policy change, you cannot publish any Chrome extension based on the manifest v2 to Web Store from January 17th, 2022. I'm going to discuss the manifest v3 extension in the following posts.
Chrome Extension – Blazor WebAssembly-based
Create a Blazor WebAssembly project. In the newly created project, open the index.html
file, and you will see the div
tag having the id
attribute of app
.
Basically, Blazor WebAssembly handles the virtual DOM within the div
tag with the app
ID. Therefore, your Chrome extension should accommodate it. In the coming posts, I'll talk about how you manage the virtual DOM in a Blazor WebAssembply app.
There are several differences between the Chrome extension and the Blazor WebAssembly app. One of the differences is routing. Because Blazor WebAssembly relies on the routing with the virtual DOM, you only need one physical file, index.html
.
Delete both Counter.razor
and FetchData.razor
, and add Popup.razor
page.
@* Popup.razor *@
@page "/popup.html"
<PageTitle>Pop Up</PageTitle>
<h1>Pop Up - Chrome Extension with Blazor WASM</h1>
<button id="changeColor"></button>
Then, add Options.razor
page.
@* Options.razor *@
@page "/options.html"
<PageTitle>Options</PageTitle>
<h1>Options - Chrome Extension with Blazor WASM</h1>
<div id="buttonDiv">
</div>
<div>
<p>Choose a different background color!</p>
</div>
As you can see, each Razor page has the routing value of popup.html
and options.html
, respectively.
Once all is done, publish your Blazor WebAssembly app:
dotnet publish ./src/ChromeExtensionV2 -c Release -o published
You can confirm the published artifact:
It's time to register this migrated extension. However, you can't register it because of the error message below. The error message says that you can't use any directory name or filename starting with an underscore (_
). Unfortunately, the Blazor WebAssembly app generates the _framework
directory by default. Therefore, you can't directly use the published artifact.
It would be best if you changed all the underscored directories and filenames. In addition to that, it would be best if you change all the contents referencing underscored files or directories. You can easily do this with a PowerShell script or a bash shell script. I'm going to use the PowerShell script for this post.
# Run-PostBuild.ps1
function Update-FileContent {
param (
[string] $Filename,
[string] $Value1,
[string] $Value2
)
$content = Get-Content -Path $Filename -Raw
$updated = $content -replace $Value1, $Value2
Set-Content -Path $Filename -Value $updated -Force
}
Update-FileContent `
-Filename "./published/wwwroot/_framework/blazor.webassembly.js" `
-Value1 "_framework" `
-Value2 "framework"
Update-FileContent `
-Filename "./published/wwwroot/index.html" `
-Value1 "_framework" `
-Value2 "framework"
mv ./published/wwwroot/_framework ./published/wwwroot/framework
After running the PowerShell script, you'll be able to see the directory name changed.
Register the extension again. This time, another error occurs that no options.html
file exists. In other words, you physically need the options.html
file.
According to this error, any Chrome extension must have popup.html
and/or options.html
files. Both Razor files are not enough for page rendering. By the way, both popup.html
and options.html
files are fundamentally the same as the index.html
that access the div
tag having the app
ID to handle the virtual DOM. So then, instead of manually creating both files, let's duplicate the existing index.html
file by adding those commands to the PowerShell script.
# Run-PostBuild.ps1
...
cp ./published/wwwroot/index.html ./published/wwwroot/popup.html
cp ./published/wwwroot/index.html ./published/wwwroot/options.html
Rerun the script. Both popup.html
and options.html
are duplicated from index.html
.
Register the extension again. No error has occurred.
However, no error occurring during the registration process doesn't mean that the extension actually works. Click the "Inspect pop-up window" menu like the screenshot below.
Then, you'll see the developer tools window that says there are errors. It's because of the Content Security Policy.
To sort out this error, the manifest.json
file needs the content_security_policy
attribute.
{
"manifest_version": 2,
"version": "1.0",
"name": "Getting Started Example (Blazor WASM)",
"description": "Build an Extension!",
...
"content_security_policy": "script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval' 'sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA='; object-src 'self'",
...
}
Here are some details for the content_security_policy
attribute:
unsafe-eval
: For theFunction()
object, andsetTimeout()
andsetInterval()
functions.wasm-unsafe-eval
: For the web assembly binary files.sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=
: For the Blazor WebAssembly libraries.
After updating the manifest.json
file, register the extension again. Everything looks OK.
However, we still don't see the button to change the background colour. It means that the extension still doesn't work as expected. It's because both popup.html
and options.html
don't have corresponding JavaScript files.
Both files are automatically generated from index.html
through the PowerShell script. Therefore, add the JavaScript reference of js/main.js
to index.html
. Also, create an empty main.js
file under the js
directory.
<!DOCTYPE html>
<html lang="en">
...
<body>
<div id="app">Loading...</div>
...
<script src="_framework/blazor.webassembly.js"></script>
<!-- ⬇️⬇️⬇️ Add this line ⬇️⬇️⬇️ -->
<script src="js/main.js"></script>
<!-- ⬆️⬆️⬆️ Add this line ⬆️⬆️⬆️ -->
</body>
</html>
Then, update the PowerShell script by adding the following lines so that both popup.html
and options.html
have the link to popup.js
and options.js
, respectively.
# Run-PostBuild.ps1
...
Update-FileContent `
-Filename "./published/wwwroot/popup.html" `
-Value1 "js/main.js" `
-Value2 "js/popup.js"
Update-FileContent `
-Filename "./published/wwwroot/options.html" `
-Value1 "js/main.js" `
-Value2 "js/options.js"
Re-register the extension and see how it's going. But another error has occurred this time. Both JavaScript files worked perfectly with no issue but not in the Blazor WebAssembly. Why is that?
The blazor.webassembly.js
file knows the answer. This JavaScript file initiates the Blazor WebAssembly app, followed by loading either popup.js
or options.js
. Unfortunately, those files are loaded even before the Blazor app is initiated, which causes the error above.
Therefore, to figure out this problem, you need to update the JavaScript reference section like below. JS interop for Blazor WebAssembly has more information if you want to know more.
<!DOCTYPE html>
<html lang="en">
...
<body>
<div id="app">Loading...</div>
...
<!-- Add the 'autostart' attribute and set its value to 'false' -->
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
<!-- ⬇️⬇️⬇️ Add these lines ⬇️⬇️⬇️ -->
<script>
Blazor.start().then(function () {
var customScript = document.createElement('script');
customScript.setAttribute('src', 'js/main.js');
document.head.appendChild(customScript);
});
</script>
<!-- ⬆️⬆️⬆️ Add these lines ⬆️⬆️⬆️ -->
</body>
</html>
Build the app and register the extension again. There's another Content Security Policy-related error.
Add the hash value mentioned in error to manifest.json
to fix this error.
{
"manifest_version": 2,
"version": "1.0",
"name": "Getting Started Example (Blazor WASM)",
"description": "Build an Extension!",
...
"content_security_policy": "script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval' 'sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=' 'sha256-DnTH4SKCYpHBGu1OxDOqoYLsvmZTiYIWJVQ1Ava7Kig=' 'sha256-b9roSuk6Pa7l0Hl/LWXGQlupw8fMc6ME2+82/N3qM0Q='; object-src 'self'",
...
}
What are those hash values?
sha256-DnTH4SKCYpHBGu1OxDOqoYLsvmZTiYIWJVQ1Ava7Kig=
: It points topopup.js
.sha256-b9roSuk6Pa7l0Hl/LWXGQlupw8fMc6ME2+82/N3qM0Q=
: It points tooptions.js
.
Once added, register the extension again. Now it's working as expected!
Your existing JavaScript-based Chrome extension has now successfully been migrated to Blazor WebAssembly! If you want to see the complete example, go to this page.
So far, we've migrated the JavaScript-based Chrome extension app to Blazor WebAssembly with minimal code changes. Both extensions are almost identical, except for a couple of points. In other words, your extension can easily be migrated using the "lift & shift" approach. However, it doesn't entirely make use of the powerful JS interop feature yet. In the next post, let's explore how we can use the JS interop feature more for this migration.
Do you want to know more about Blazor?
Here are some tutorials for you.