├── .editorconfig ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── Blazor.BrowserExtension-Build.yml │ ├── Blazor.BrowserExtension-Docs.yml │ ├── Blazor.BrowserExtension-PR-Build.yml │ └── Blazor.BrowserExtension-Release.yml ├── .gitignore ├── Blazor.BrowserExtension.sln ├── CONTRIBUTING.md ├── GitVersion.yml ├── LICENSE ├── README.md ├── docs ├── AfterPublish.cs ├── App.razor ├── Docs.csproj ├── Layout │ ├── Footer.razor │ ├── Footer.razor.css │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ └── NavMenu.razor.css ├── Pages │ ├── 00_Home.razor │ ├── 00_Home.razor.css │ ├── 01_01_QuickStart.md │ ├── 01_02_ManifestV3.md │ ├── 01_03_ExtensionAPIs.md │ ├── 01_04_RunningAndDebugging.md │ ├── 01_05_Publishing.md │ ├── 01_06_SetupExistingProject.md │ ├── 02_Features.md │ ├── 03_01_app.js.md │ ├── 03_02_BackgroundWorker.md │ ├── 03_02_ContentScripts.md │ ├── 03_02_Options.md │ ├── 03_02_Popup.md │ ├── 03_03_Messaging.md │ ├── 04_01_ConfigureBuild.md │ ├── 04_02_Routing.md │ ├── 04_03_AdditionalInformation.md │ └── NotFound.md ├── Program.cs ├── Properties │ └── launchSettings.json ├── Rendering │ ├── LinkExtension.cs │ └── MarkdownRenderer.cs ├── Routing │ ├── DocumentRouteEvent.cs │ ├── DocumentRouteMetadata.cs │ ├── DocumentRoutePageTitle.razor │ ├── DocumentRouteProvider.cs │ └── RoutingPage.cs ├── _Imports.razor ├── libman.json └── wwwroot │ ├── 404.html │ ├── Demo.gif │ ├── css │ └── app.css │ ├── google55f6f5677a7d6793.html │ ├── js │ └── app.js │ └── lib │ ├── bootstrap │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── fortawesome │ └── fontawesome-free │ │ ├── css │ │ ├── all.css │ │ └── all.min.css │ │ └── webfonts │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff2 │ │ ├── fa-v4compatibility.ttf │ │ └── fa-v4compatibility.woff2 │ └── prism │ ├── components │ ├── prism-bash.js │ ├── prism-bash.min.js │ ├── prism-csharp.js │ ├── prism-csharp.min.js │ ├── prism-cshtml.js │ ├── prism-cshtml.min.js │ ├── prism-json.js │ └── prism-json.min.js │ ├── plugins │ ├── copy-to-clipboard │ │ ├── prism-copy-to-clipboard.js │ │ └── prism-copy-to-clipboard.min.js │ ├── line-numbers │ │ ├── prism-line-numbers.css │ │ ├── prism-line-numbers.js │ │ ├── prism-line-numbers.min.css │ │ └── prism-line-numbers.min.js │ └── toolbar │ │ ├── prism-toolbar.css │ │ ├── prism-toolbar.js │ │ ├── prism-toolbar.min.css │ │ └── prism-toolbar.min.js │ ├── prism.js │ ├── prism.min.js │ └── themes │ ├── prism.css │ └── prism.min.css ├── src ├── Blazor.BrowserExtension.Analyzer │ ├── AnalyzerReleases.Shipped.md │ ├── AnalyzerReleases.Unshipped.md │ ├── BackgroundSourceGenerator.cs │ ├── Blazor.BrowserExtension.Analyzer.csproj │ ├── DiagnosticFactory.cs │ ├── Properties │ │ └── launchSettings.json │ └── Translation │ │ ├── ExpressionTranslator.cs │ │ ├── IndentedCodeLinesBuilder.cs │ │ ├── ParentExpressionType.cs │ │ ├── ParentExpressionTypeSwitcher.cs │ │ ├── SystemTranslations.cs │ │ └── TranslateContext.cs ├── Blazor.BrowserExtension.Build │ ├── Blazor.BrowserExtension.Build.csproj │ ├── README.md │ ├── Tasks │ │ ├── BlazorToBrowserExtensionBootstrapFile.cs │ │ ├── BlazorToBrowserExtensionProcessOutputFiles.cs │ │ ├── BlazorToBrowserExtensionProcessPublishFiles.cs │ │ ├── BlazorToBrowserExtensionProcessRoutingFiles.cs │ │ ├── BlazorToBrowserExtensionProcessStaticWebAssetsManifest.cs │ │ ├── BlazorToBrowserExtensionReplaceContent.cs │ │ ├── BlazorToBrowserExtensionValidateManifest.cs │ │ ├── BlazorToBrowserExtensionWriteBackgroundWorkerFile.cs │ │ ├── BlazorToBrowserExtensionWriteConfigFile.cs │ │ ├── Bootstrap │ │ │ ├── BootstrapFileType.cs │ │ │ ├── BootstrapperFactory.cs │ │ │ ├── IFileBootstrapper.cs │ │ │ ├── ImportsRazorFileBootstrapper.cs │ │ │ ├── IndexRazorFileBootstrapper.cs │ │ │ ├── ProgramCsFileBootstrapper.cs │ │ │ └── ProjectFileBootstrapper.cs │ │ ├── ExtensionManifest │ │ │ ├── IValidator.cs │ │ │ ├── Manifest.cs │ │ │ ├── ManifestItem.cs │ │ │ ├── ManifestItemKey.cs │ │ │ ├── ManifestParseError.cs │ │ │ ├── ManifestParser.cs │ │ │ ├── ManifestV3Validator.cs │ │ │ ├── ValidationResult.cs │ │ │ └── ValidatorFactory.cs │ │ ├── Helpers │ │ │ └── OutputHelper.cs │ │ └── StaticWebAssets │ │ │ ├── BaseManifestProcessor.cs │ │ │ ├── JsonManifestProcessor.cs │ │ │ ├── JsonStaticWebAssets.cs │ │ │ └── StaticWebAssetFile.cs │ ├── build │ │ ├── Blazor.BrowserExtension.Build.Bootstrap.targets │ │ ├── Blazor.BrowserExtension.Build.Build.targets │ │ ├── Blazor.BrowserExtension.Build.Clean.targets │ │ ├── Blazor.BrowserExtension.Build.Compression.targets │ │ ├── Blazor.BrowserExtension.Build.Dependencies.targets │ │ ├── Blazor.BrowserExtension.Build.FileContentReplacements.targets │ │ ├── Blazor.BrowserExtension.Build.Project.targets │ │ ├── Blazor.BrowserExtension.Build.Publish.targets │ │ ├── Blazor.BrowserExtension.Build.props │ │ └── Blazor.BrowserExtension.Build.targets │ ├── content │ │ ├── BackgroundWorker.cs │ │ ├── BackgroundWorker.js │ │ └── manifest.json │ └── lib │ │ ├── Microsoft.Bcl.AsyncInterfaces.dll │ │ ├── System.Buffers.dll │ │ ├── System.Collections.Immutable.dll │ │ ├── System.IO.Pipelines.dll │ │ ├── System.Memory.dll │ │ ├── System.Numerics.Vectors.dll │ │ ├── System.Runtime.CompilerServices.Unsafe.dll │ │ ├── System.Text.Encodings.Web.dll │ │ ├── System.Text.Json.dll │ │ ├── System.Threading.Tasks.Extensions.dll │ │ └── System.ValueTuple.dll ├── Blazor.BrowserExtension.Template │ ├── Blazor.BrowserExtension.Template.csproj │ └── HelloBlazorExtension │ │ ├── .template.config │ │ ├── dotnetcli.host.json │ │ ├── ide.host.json │ │ └── template.json │ │ ├── App.razor │ │ ├── BackgroundWorker.cs │ │ ├── HelloBlazorExtension.csproj │ │ ├── Layout │ │ ├── MainLayout.razor │ │ └── MainLayout.razor.css │ │ ├── Pages │ │ ├── Index.razor │ │ ├── Options.razor │ │ └── Popup.razor │ │ ├── Program.cs │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── _Imports.razor │ │ └── wwwroot │ │ ├── css │ │ └── app.css │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── index.html │ │ └── manifest.json ├── Blazor.BrowserExtension │ ├── BackgroundWorkerBase.cs │ ├── BackgroundWorkerMainAttribute.cs │ ├── Blazor.BrowserExtension.csproj │ ├── BrowserExtensionEnvironment.cs │ ├── BrowserExtensionMode.cs │ ├── Extensions │ │ ├── RootComponentMappingCollectionExtensions.cs │ │ ├── ServiceCollectionExtensions.cs │ │ └── WebAssemblyHostBuilderExtensions.cs │ ├── IBrowserExtensionEnvironment.cs │ ├── Pages │ │ ├── BasePage.cs │ │ └── IndexPage.cs │ ├── ProxyJsRuntime.cs │ ├── build │ │ ├── Blazor.BrowserExtension.BackgroundWorker.targets │ │ ├── Blazor.BrowserExtension.Config.targets │ │ ├── Blazor.BrowserExtension.Dependencies.targets │ │ ├── Blazor.BrowserExtension.StaticWebAssets.targets │ │ ├── Blazor.BrowserExtension.props │ │ └── Blazor.BrowserExtension.targets │ └── content │ │ ├── README.md │ │ ├── dist │ │ ├── BackgroundWorkerRunner.js │ │ ├── Blazor.BrowserExtension.lib.module.js │ │ ├── ContentScript.js │ │ ├── Core.js │ │ └── CoreInternal.js │ │ ├── jsconfig.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── rollup-plugins │ │ ├── rollupCleanup.js │ │ └── rollupNormalizeLineEndings.js │ │ ├── rollup.config.js │ │ └── src │ │ ├── BackgroundWorkerRunner.js │ │ ├── Blazor.BrowserExtension.lib.module.js │ │ ├── ContentScript.js │ │ ├── Core.js │ │ ├── Modules │ │ ├── BackgroundProxies.js │ │ ├── BlazorBrowserExtension.js │ │ ├── BrowserExtension.js │ │ ├── BrowserExtensionConfig.js │ │ ├── BrowserExtensionModes.js │ │ ├── CoreInternal.js │ │ └── GlobalVariableInitializer.js │ │ └── lib │ │ ├── browser-polyfill.js │ │ ├── browser-polyfill.js.map │ │ ├── browser-polyfill.min.js │ │ ├── browser-polyfill.min.js.map │ │ ├── decode.js │ │ └── decode.min.js └── Icon │ ├── Icon.png │ └── Icon.svg ├── test ├── Blazor.BrowserExtension.Analyzer.Test │ ├── BaseBackgroundSourceGeneratorTest.cs │ ├── Blazor.BrowserExtension.Analyzer.Test.csproj │ ├── TestFiles │ │ ├── BackgroundWorkerGeneratedTemplate.cs │ │ └── BackgroundWorkerTemplate.cs │ └── Tests │ │ ├── ArrayTest.cs │ │ ├── DelegateTest.cs │ │ ├── JsAccessPathTest.cs │ │ ├── NestedObjectArrayTest.cs │ │ ├── ObjectTest.cs │ │ └── SystemTranslationTest.cs ├── Blazor.BrowserExtension.Build.Test │ ├── Blazor.BrowserExtension.Build.Test.csproj │ ├── BuildIntegrationTest.cs │ ├── BuildIntegrationTestFixture.cs │ ├── Helpers │ │ ├── CommandHelper.cs │ │ └── WebDriverExtensionHelper.cs │ ├── Tasks │ │ └── Bootstrap │ │ │ ├── BaseFileBootstrapperTest.cs │ │ │ ├── ImportsRazorFileBootstrapperTest.cs │ │ │ ├── IndexRazorFileBootstrapperTest.cs │ │ │ ├── ProgramCsFileBootstrapperTest.cs │ │ │ └── ProjectFileBootstrapperTest.cs │ └── TestProjects │ │ ├── Directory.Build.props │ │ ├── Directory.Build.targets │ │ └── EmptyBlazorProject │ │ ├── App.razor │ │ ├── EmptyBlazorProject.csproj │ │ ├── MainLayout.razor │ │ ├── Pages │ │ └── Index.razor │ │ ├── Program.cs │ │ ├── _Imports.razor │ │ └── wwwroot │ │ └── index.html ├── Blazor.BrowserExtension.IntegrationTest │ ├── App.razor │ ├── BackgroundWorker.cs │ ├── Blazor.BrowserExtension.IntegrationTest.csproj │ ├── Layout │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.css │ │ ├── NavMenu.razor │ │ └── NavMenu.razor.css │ ├── Pages │ │ ├── ContentScript.razor │ │ ├── Counter.razor │ │ ├── FetchData.razor │ │ ├── Index.razor │ │ ├── Options.razor │ │ └── Popup.razor │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── _Imports.razor │ └── wwwroot │ │ ├── app.js │ │ ├── css │ │ ├── app.css │ │ ├── bootstrap │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ └── open-iconic │ │ │ ├── FONT-LICENSE │ │ │ ├── ICON-LICENSE │ │ │ ├── README.md │ │ │ └── font │ │ │ ├── css │ │ │ └── open-iconic-bootstrap.min.css │ │ │ └── fonts │ │ │ ├── open-iconic.eot │ │ │ ├── open-iconic.otf │ │ │ ├── open-iconic.svg │ │ │ ├── open-iconic.ttf │ │ │ └── open-iconic.woff │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── index.html │ │ ├── manifest.json │ │ ├── manifestv3.json │ │ └── sample-data │ │ └── weather.json └── Blazor.BrowserExtension.IntegrationTestRunner │ ├── BaseIntegrationTest.cs │ ├── Blazor.BrowserExtension.IntegrationTestRunner.csproj │ ├── Fixture.cs │ ├── IntegrationTestManifestV3.cs │ ├── OrderAttribute.cs │ ├── TestOrderer.cs │ └── WebDriverHelper.cs └── tool └── DependenciesUpdater.ps1 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mingyaulee 2 | open_collective: mingyaulee-gh -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Check for updates to GitHub Actions every week 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | open-pull-requests-limit: 5 9 | # Check for updates to NuGet packages every week 10 | - package-ecosystem: "nuget" 11 | directories: 12 | - "/test/Blazor.BrowserExtension.IntegrationTest/" 13 | - "/test/Blazor.BrowserExtension.IntegrationTestRunner/" 14 | - "/test/Blazor.BrowserExtension.Build.Test/" 15 | allow: 16 | - dependency-type: "direct" 17 | schedule: 18 | interval: "weekly" 19 | open-pull-requests-limit: 5 20 | groups: 21 | aspnetcore: 22 | patterns: [ Microsoft.AspNetCore.* ] 23 | mstest: 24 | patterns: [ MSTest.* ] 25 | xunit: 26 | patterns: [ xunit, xunit.* ] 27 | -------------------------------------------------------------------------------- /.github/workflows/Blazor.BrowserExtension-Docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy documentation to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: [ docs/** ] 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pages: write 17 | id-token: write 18 | environment: 19 | name: github-pages 20 | url: ${{ steps.deployment.outputs.page_url }} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup .NET 25 | uses: actions/setup-dotnet@v4 26 | with: 27 | dotnet-version: 9.0.x 28 | - name: Restore dependencies 29 | run: dotnet restore docs 30 | - name: Build 31 | run: dotnet build docs --no-restore --configuration Release -p:OutDir=$PWD/docs/bin/BuildOutput 32 | - name: Publish 33 | run: dotnet publish docs --no-restore --no-build --configuration Release -p:PublishDir=$PWD/PublishOutput 34 | - name: Create runtimeconfig.json 35 | run: | 36 | cat < $PWD/docs/bin/BuildOutput/Docs.runtimeconfig.json 37 | { 38 | "runtimeOptions": { 39 | "tfm": "net9.0", 40 | "framework": { 41 | "name": "Microsoft.AspNetCore.App", 42 | "version": "9.0.0" 43 | }, 44 | "rollForwardOnNoCandidateFx": 2 45 | } 46 | } 47 | EOF 48 | - name: After Publish 49 | run: dotnet docs/bin/BuildOutput/Docs.dll --after-publish $PWD/PublishOutput/wwwroot 50 | - name: Configure GitHub Pages 51 | uses: actions/configure-pages@v5 52 | - name: Upload GitHub Pages artifact 53 | uses: actions/upload-pages-artifact@v3 54 | with: 55 | path: PublishOutput/wwwroot 56 | - name: Deploy to GitHub Pages 57 | id: deployment 58 | uses: actions/deploy-pages@v4 59 | -------------------------------------------------------------------------------- /.github/workflows/Blazor.BrowserExtension-PR-Build.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | buildAndTestPR: 13 | runs-on: windows-latest 14 | steps: 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: 9.0.x 19 | - name: Setup Chrome and Chrome Driver 20 | uses: nanasess/setup-chromedriver@master 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | - name: Build 28 | run: dotnet build --no-restore --configuration Debug 29 | - name: Run tests 30 | run: dotnet test --no-restore --no-build --configuration Debug 31 | -------------------------------------------------------------------------------- /.github/workflows/Blazor.BrowserExtension-Release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: 9.0.x 19 | - name: Install GitVersion 20 | uses: gittools/actions/gitversion/setup@v3.1.11 21 | with: 22 | versionSpec: 5.x 23 | - name: Determine Version 24 | id: gitversion 25 | uses: gittools/actions/gitversion/execute@v3.1.11 26 | with: 27 | useConfigFile: true 28 | - name: Restore dependencies 29 | run: dotnet restore 30 | - name: Build 31 | run: dotnet build --no-restore --configuration Release -p:Version=${{ steps.gitversion.outputs.NuGetVersion }} 32 | - name: Pack 33 | run: dotnet pack --no-restore --no-build --configuration Release -p:Version=${{ steps.gitversion.outputs.NuGetVersion }} 34 | - name: Push generated package to NuGet 35 | run: dotnet nuget push ./src/PackageOutput/*.nupkg --api-key ${NUGET_AUTH_TOKEN} --source https://api.nuget.org/v3/index.json 36 | env: 37 | NUGET_AUTH_TOKEN: ${{ secrets.NUGET_KEY }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific files 2 | *.rsuser 3 | *.suo 4 | *.user 5 | *.userosscache 6 | *.sln.docstates 7 | 8 | # Build results 9 | [Dd]ebug/ 10 | [Dd]ebugPublic/ 11 | [Rr]elease/ 12 | [Rr]eleases/ 13 | x64/ 14 | x86/ 15 | [Aa][Rr][Mm]/ 16 | [Aa][Rr][Mm]64/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | [Ll]og/ 21 | [Ll]ogs/ 22 | [Bb]uild[Oo]utput/ 23 | [Pp]ackage[Oo]utput/ 24 | PackagesCache/ 25 | 26 | # Test results 27 | [Tt]est[Rr]esults/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | 32 | # .NET Core 33 | project.lock.json 34 | project.fragment.lock.json 35 | artifacts/ 36 | 37 | # Files built by Visual Studio 38 | *_i.c 39 | *_p.c 40 | *_h.h 41 | *.ilk 42 | *.meta 43 | *.obj 44 | *.iobj 45 | *.pch 46 | *.pdb 47 | *.ipdb 48 | *.pgc 49 | *.pgd 50 | *.rsp 51 | *.sbr 52 | *.tlb 53 | *.tli 54 | *.tlh 55 | *.tmp 56 | *.tmp_proj 57 | *_wpftmp.csproj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # NuGet Packages 67 | *.nupkg 68 | # NuGet Symbol Packages 69 | *.snupkg 70 | # The packages folder can be ignored because of Package Restore 71 | **/[Pp]ackages/* 72 | # except build/, which is used as an MSBuild target. 73 | !**/[Pp]ackages/build/ 74 | # Uncomment if necessary however generally it will be regenerated when needed 75 | #!**/[Pp]ackages/repositories.config 76 | # NuGet v3's project.json files produces more ignorable files 77 | *.nuget.props 78 | *.nuget.targets 79 | 80 | # Visual Studio cache files 81 | # files ending in .cache can be ignored 82 | *.[Cc]ache 83 | # but keep track of directories ending in .cache 84 | !?*.[Cc]ache/ 85 | 86 | # Node Modules 87 | node_modules 88 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | major-version-bump-message: '(breaking|major):' 2 | minor-version-bump-message: '(feature|minor):' 3 | patch-version-bump-message: '(fix|patch):' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 mingyaulee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blazor.BrowserExtension 2 | 3 | [![Nuget](https://img.shields.io/nuget/v/Blazor.BrowserExtension?style=for-the-badge&color=blue)](https://www.nuget.org/packages/Blazor.BrowserExtension/) 4 | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mingyaulee/Blazor.BrowserExtension/Blazor.BrowserExtension-Build.yml?branch=main&style=for-the-badge&color=blue)](https://github.com/mingyaulee/Blazor.BrowserExtension/actions/workflows/Blazor.BrowserExtension-Build.yml) 5 | [![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/Blazor.BrowserExtension?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge)](https://sonarcloud.io/dashboard?id=Blazor.BrowserExtension) 6 | 7 | You can now easily build a browser extension with Blazor! 8 | 9 | Visit the [documentations page](https://mingyaulee.github.io/Blazor.BrowserExtension) to find out more! 10 | -------------------------------------------------------------------------------- /docs/AfterPublish.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Docs.Routing; 5 | 6 | namespace Docs 7 | { 8 | public static class AfterPublish 9 | { 10 | public static void Run(string publishPath) 11 | { 12 | var entryFile = "404.html"; 13 | var sourceFilePath = Path.Combine(publishPath, entryFile); 14 | Console.WriteLine($"Source file '{sourceFilePath}'"); 15 | 16 | foreach (var webPath in DocumentRouteProvider.DocumentRoutes.Select(documentRoute => documentRoute.WebPath)) 17 | { 18 | var fileName = webPath; 19 | if (string.IsNullOrEmpty(fileName)) 20 | { 21 | fileName = "index"; 22 | } 23 | 24 | var destinationFilePath = Path.Combine(publishPath, fileName + ".html"); 25 | Console.WriteLine($"Destination file '{destinationFilePath}'"); 26 | File.Copy(sourceFilePath, destinationFilePath, true); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/App.razor: -------------------------------------------------------------------------------- 1 | @using Docs.Routing 2 | @inject DocumentRouteEvent DocumentRouteEvent 3 | 4 | 5 | 6 | @if (routeData.PageType != typeof(RoutingPage)) 7 | { 8 | DocumentRouteEvent.TriggerChange(DocumentRouteProvider.GetDocumentRouteMetadataFromResourceName(routeData.PageType.FullName)); 9 | } 10 | 11 | 12 | 13 | 14 | 15 | Not found 16 | 17 |

Sorry, there's nothing at this address.

18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /docs/Docs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | 7 | 8 | 9 | 10 | wwwroot\favicon.png 11 | 12 | 13 | wwwroot\lib\decode.min.js 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/Layout/Footer.razor: -------------------------------------------------------------------------------- 1 | @using Docs.Routing 2 | @inject DocumentRouteEvent DocumentRouteEvent 3 | @inject NavigationManager Navigation 4 | 23 | 24 | @code { 25 | private string? fileName; 26 | 27 | protected override void OnInitialized() 28 | { 29 | base.OnInitialized(); 30 | GetFileName(); 31 | DocumentRouteEvent.OnChange += OnDocumentChanged; 32 | } 33 | 34 | private void OnDocumentChanged() 35 | { 36 | GetFileName(); 37 | StateHasChanged(); 38 | } 39 | 40 | private void GetFileName() 41 | { 42 | fileName = DocumentRouteEvent.CurrentDocument?.FileName; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/Layout/Footer.razor.css: -------------------------------------------------------------------------------- 1 | footer { 2 | border: 1px solid #ccc; 3 | background-color: rgba(var(--bs-secondary-bg-rgb), 0.3); 4 | } 5 | 6 | [data-bs-theme=dark] footer { 7 | background-color: rgba(var(--bs-secondary-rgb),0.3); 8 | } 9 | 10 | @media (min-width: 768px) { 11 | footer { 12 | max-width: calc(100vw - 280px); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @inject IJSInProcessRuntime JsRuntime 3 | 4 |
5 | 8 | 9 |
10 |
11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | @Body 27 |
28 |
29 |
30 |
31 | 32 | @code { 33 | void ToggleDarkMode() 34 | { 35 | JsRuntime.InvokeVoid("globalThis.appUtils.toggleDarkMode"); 36 | } 37 | 38 | protected override void OnInitialized() 39 | { 40 | base.OnInitialized(); 41 | JsRuntime.InvokeVoid("globalThis.appUtils.initializeMode"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 | @using Docs.Routing 2 | 10 | 11 | 23 | 24 | @code { 25 | private bool collapseNavMenu = true; 26 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 27 | 28 | private void ToggleNavMenu() 29 | { 30 | collapseNavMenu = !collapseNavMenu; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .top-row { 2 | background: radial-gradient(150% 128px at 0px 56px, #005b96 0%, #03396c 50%, #03396c 100%); 3 | } 4 | 5 | .navbar-toggler { 6 | background-color: rgba(255, 255, 255, 0.1); 7 | } 8 | 9 | .navbar-brand { 10 | font-size: 1.2rem; 11 | font-weight: 700; 12 | position: relative; 13 | } 14 | 15 | .navbar-brand::after { 16 | content: ""; 17 | bottom: 4px; 18 | left: 0; 19 | right: 0; 20 | position: absolute; 21 | pointer-events: none; 22 | background: linear-gradient(128.87deg,#512bd4 14.05%,#d600aa 89.3%); 23 | height: 4px; 24 | border-radius: 4px; 25 | } 26 | 27 | .navbar-brand .dot { 28 | display: inline-block; 29 | margin: 0 3px; 30 | width: 8px; 31 | height: 8px; 32 | background: radial-gradient(circle, #6924cd 0%, #9b13bc 100%); 33 | position: relative; 34 | bottom: 3px; 35 | border-radius: 50%; 36 | } 37 | 38 | .bi { 39 | display: inline-block; 40 | position: relative; 41 | width: 1.25rem; 42 | height: 1.25rem; 43 | margin-right: 0.75rem; 44 | top: -1px; 45 | background-size: cover; 46 | } 47 | 48 | .nav-item { 49 | font-size: 0.9rem; 50 | padding-bottom: 0.5rem; 51 | } 52 | 53 | .nav-item:first-of-type { 54 | padding-top: 1rem; 55 | } 56 | 57 | .nav-item:last-of-type { 58 | padding-bottom: 1rem; 59 | } 60 | 61 | .nav-item ::deep a { 62 | color: inherit; 63 | border-radius: 4px; 64 | height: 3rem; 65 | display: flex; 66 | align-items: center; 67 | line-height: 3rem; 68 | } 69 | 70 | .nav-item ::deep a.active { 71 | background-color: rgba(255,255,255,0.37); 72 | } 73 | 74 | .nav-item ::deep a:hover { 75 | background-color: rgba(255,255,255,0.1); 76 | color: white; 77 | } 78 | 79 | @media (min-width: 768px) { 80 | .top-row { 81 | background: radial-gradient(560px 128px at 0px 56px, #005b96 0%, #03396c 50%, #03396c 100%); 82 | } 83 | 84 | .navbar-toggler { 85 | display: none; 86 | } 87 | 88 | .collapse { 89 | /* Never collapse the sidebar for wide screens */ 90 | display: block; 91 | } 92 | 93 | .nav-scrollable { 94 | /* Allow sidebar to scroll for tall menus */ 95 | height: calc(100vh - 3.5rem); 96 | overflow-y: auto; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/Pages/00_Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |
4 |
5 |

Build browser extensions easily with Blazor

6 |

Create a browser extension within the .Net ecosystem, use NuGet packages, interop with JavaScript libraries, all made possible with Blazor WebAssembly.

7 |

8 | 9 | Get started 10 | 11 |

12 |
13 |
14 |

15 | 16 | 17 | 18 |

19 |

20 | 21 | All releases 22 | 23 |

24 |
25 |
26 | 27 |
28 |

Demo

29 | 30 |
31 | -------------------------------------------------------------------------------- /docs/Pages/00_Home.razor.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | -webkit-text-fill-color: transparent; 3 | background: linear-gradient(128.87deg,#512bd4 14.05%,#d600aa 89.3%); 4 | background-clip: text; 5 | -webkit-background-clip: text; 6 | } 7 | 8 | .landing { 9 | min-height: calc(100vh - 130px); 10 | display: flex; 11 | flex-direction: column; 12 | justify-content: space-around; 13 | } -------------------------------------------------------------------------------- /docs/Pages/01_01_QuickStart.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | **Installation** 4 | 5 | ```shell 6 | dotnet new install Blazor.BrowserExtension.Template 7 | ``` 8 | 9 | **Create a new project** 10 | 11 | ```shell 12 | dotnet new browserext --name MyBlazorExtension 13 | ``` 14 | 15 | **Build** 16 | 17 | ```shell 18 | cd MyBlazorExtension 19 | dotnet build 20 | ``` 21 | 22 | The built extension will be in the directory `MyBlazorExtension/bin/Debug/net9.0/browserextension`. 23 | 24 | > **Tips** 25 | > 26 | > If you are using Visual Studio, after installing the template, you can create a new project and build it from the Visual Studio UI. 27 | 28 | **Next** 29 | 30 | [Running the extension](01_04_RunningAndDebugging.md) 31 | -------------------------------------------------------------------------------- /docs/Pages/01_02_ManifestV3.md: -------------------------------------------------------------------------------- 1 | # Manifest V3 is supported 2 | 3 | > Manifest V2 is deprecated in Chromium based browsers and should not be used in new projects. 4 | 5 | What is Manifest V3? Read more about manifest V3 [here](https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3). 6 | 7 | At the moment, Chromium based browsers (Chrome & Edge) have support for the manifest V3 specification and manifest V2 is deprecated. 8 | 9 | Firefox's implementation has slight differences with Chromium based browsers in the [manifest format](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json). 10 | 11 | 12 | ## Migrate from Manifest V2 to V3 13 | 14 | 1. Update the `Blazor.BrowserExtension` NuGet package version to the latest version. 15 | 0. Update target framework to `net9.0` and all .Net packages to .Net 9. 16 | 0. Update the `manifest.json` file 17 | - Update `manifest_version` 18 | ```json 19 | "manifest_version": 3, 20 | ``` 21 | - Update `background` 22 | ```json 23 | "background": { 24 | "service_worker": "content/BackgroundWorker.js", 25 | "type": "module" 26 | }, 27 | ``` 28 | - Update `browser_action` 29 | ```json 30 | "action": { 31 | "default_popup": "popup.html" 32 | }, 33 | ``` 34 | - Update `content_security_policy` 35 | ```json 36 | "content_security_policy": { 37 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 38 | } 39 | ``` 40 | - Update `web_accessible_resources` 41 | ```json 42 | "web_accessible_resources": [ 43 | { 44 | "resources": [ 45 | ... 46 | ], 47 | "matches": [ "" ] 48 | } 49 | ] 50 | ``` 51 | 0. Move all the codes from `Background.razor` to `BackgroundWorker.cs` 52 | - Create a new file `BackgroundWorker.cs`. 53 | - Refer to the [Background Worker page](03_02_BackgroundWorker.md) for the sample class. 54 | - Remove `Background.razor` 55 | - Refer to [this guide](https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/) for migrating from background page to background service worker. 56 | 57 | 58 | # Reference 59 | 60 | - [Chrome for Developers](https://developer.chrome.com/docs/extensions/develop/migrate) 61 | -------------------------------------------------------------------------------- /docs/Pages/01_03_ExtensionAPIs.md: -------------------------------------------------------------------------------- 1 | # APIs for Browser Extension 2 | 3 | There are [differences](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs) in some APIs available in different browsers, most of the general APIs are standardised with the use of [Mozilla's WebExtension polyfill](https://github.com/mozilla/webextension-polyfill). 4 | 5 | The APIs are available in JavaScript and most of them have been ported to Blazor from the package [WebExtensions.Net](https://github.com/mingyaulee/WebExtensions.Net), which uses `IJSRuntime` to interop with the standard extension APIs. 6 | 7 | When you create a new Razor page, add `@inherits BasePage` on top to get access to the property exposed by the `WebExtensions` property. 8 | 9 | ```razor 10 | @inherits BasePage; 11 | 12 | 13 |

@optionsPageUrl

14 | 15 | @code { 16 | string optionsPageUrl = null; 17 | async Task GetOptionsPageUrl() 18 | { 19 | optionsPageUrl = WebExtensions.Runtime.GetURL("options.html"); 20 | } 21 | } 22 | ``` 23 | 24 | Each of the properties in the `IWebExtensionsApi` class provides a set of APIs for different functionalities that can be implemented by an extension. 25 | These properties sometimes include description of additional setup required to use the API. 26 | 27 | For example, the `Alarms` API shows that it requires manifest permission `alarms`. 28 | 29 | ```csharp 30 | public interface IWebExtensionsApi 31 | { 32 | // 33 | // Summary: 34 | // Requires manifest permission alarms. 35 | IAlarmsApi Alarms { get; } 36 | } 37 | ``` 38 | 39 | # Reference 40 | 41 | - [Chrome for Developers](https://developer.chrome.com/docs/extensions/reference/api) 42 | - [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API) 43 | -------------------------------------------------------------------------------- /docs/Pages/01_05_Publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing the extension 2 | 3 | 1. Run the `dotnet publish` command to publish the extension. 4 | 0. The extension output is in the `bin/Release/net9.0/publish/browserextension` directory. 5 | 0. The contents of this directory should be used when packing your extension. The contents can also be loaded unpacked the same way as the build output. 6 | 7 | ## Packing the extension 8 | 9 | Typically the extension stores require that you pack your extension when submitting it to the store. 10 | Most of them require just a simple zip format, where the zipped file is renamed with `.crx` extension. 11 | 12 | Refer to the guidance for the respective store for more details. 13 | 14 | - [Chrome Web Store](https://developer.chrome.com/docs/webstore/publish) 15 | - [Microsoft Edge Add-ons](https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension) 16 | - [AMO (addons.mozilla.org)](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/) 17 | - [Opera addons](https://dev.opera.com/extensions/publishing-guidelines/) 18 | - [Safari App Store](https://developer.apple.com/documentation/safariservices/safari_web_extensions/distributing_your_safari_web_extension) 19 | 20 | ## Optimization 21 | 22 | Built-in optimization has been done by this package to use Brotli compression by default, removing all the uncompressed files from the publish output to reduce the package size. 23 | 24 | If you would like to use AOT compilation, it is best to benchmark the size before and after using AOT compilation as your extension might end up having an increase in size. 25 | 26 | You can also enable IL trimming when publishing and disable unused features such as timezone support. 27 | 28 | Refer to [Blazor performance best practices](https://learn.microsoft.com/en-us/aspnet/core/blazor/performance?view=aspnetcore-8.0#minimize-app-download-size) documentation page for more information. 29 | -------------------------------------------------------------------------------- /docs/Pages/02_Features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | - This package imports three other packages, which are: 4 | 1. [WebExtensions.Net](https://github.com/mingyaulee/WebExtensions.Net) - Provides interop for WebExtensions standard API. 5 | 2. [JsBind.Net](https://github.com/mingyaulee/JsBind.Net) - Provides advanced JavaScript interop features used by WebExtensions.Net. 6 | 3. Blazor.BrowserExtension.Build (in this repository) - Adds build targets and tasks to the project. 7 | 8 | - Hook into Blazor lifecycle events with [`app.js`](03_01_app.js.md). 9 | - Publishing with Brotli compression enabled by default to minimize published extension app size. 10 | 11 | - Prepares the extension files when building and publishing. 12 | - Replicates index.html to enable [routing](04_02_Routing.md). 13 | - Copies all static web assets from other RCLs. 14 | 15 | - Configurable build and publish with [MSBuild properties](04_01_ConfigureBuild.md). 16 | 17 | ## Samples/References 18 | 19 | Sample projects are available in the repository [Blazor.BrowserExtension.Samples](https://github.com/mingyaulee/Blazor.BrowserExtension.Samples). 20 | 21 | You can also refer to the following projects for real life extensions: 22 | 23 | - [Blazor Edge New Tab](https://github.com/dragnilar/EdgeExtensionsBlazor) - Published for [Chrome](https://chrome.google.com/webstore/detail/blazor-edge-new-tab/bdcfngldhocoffghnlmhibpifmoakiec) and [Edge](https://microsoftedge.microsoft.com/addons/detail/blazor-edge-new-tab/bfhdepjammnaoddhikhogfbnikmeocfj). 24 | - [Amazing Favorites](https://github.com/Amazing-Favorites/Amazing-Favorites) - Published for [Chrome](https://chrome.google.com/webstore/detail/amazing-favorites/podhpclhgkdeiechkdceginfehfanhcb) and [Edge](https://microsoftedge.microsoft.com/addons/detail/amazing-favorites/bknjgbpkaloajcphccpcnahegfglfiei). 25 | 26 | Or check out the [GitHub dependency graph](https://github.com/mingyaulee/Blazor.BrowserExtension/network/dependents) for more repositories. 27 | 28 | > If you want to add another project to the list, create a PR or issue in GitHub. 29 | 30 | 31 | ## More samples 32 | 33 | There are plenty more samples provided by Chrome and MDN in vanilla JavaScript, which of course in most cases can be ported to Blazor. 34 | 35 | Check out these repositories to get inspired: 36 | 37 | 1. [webextensions-examples](https://github.com/mdn/webextensions-examples) 38 | 2. [chrome-extensions-samples](https://github.com/GoogleChrome/chrome-extensions-samples) 39 | -------------------------------------------------------------------------------- /docs/Pages/03_02_Options.md: -------------------------------------------------------------------------------- 1 | # Options page 2 | 3 | An options page can be used to allow the user to change the extension settings and user preferences. 4 | 5 | Add the following to the `manifest.json` 6 | 7 | ```json 8 | "options_ui": { 9 | "page": "options.html", 10 | "open_in_tab": true 11 | } 12 | ``` 13 | 14 | Add a `Options.razor` Razor component under `Pages` directory with the following content. 15 | 16 | ```razor 17 | @page "/options.html" 18 | @inherits BasePage 19 | 20 |

My options page

21 | ``` 22 | 23 | 24 | # Reference 25 | 26 | - [Chrome for Developers](https://developer.chrome.com/docs/extensions/develop/ui/options-page) 27 | - [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Options_pages) 28 | -------------------------------------------------------------------------------- /docs/Pages/03_02_Popup.md: -------------------------------------------------------------------------------- 1 | # Popup page 2 | 3 | A popup is shown when the user clicks on the extension action icon. 4 | 5 | Add the following to the `manifest.json` 6 | 7 | ```json 8 | "action": { 9 | "default_popup": "popup.html" 10 | }, 11 | ``` 12 | 13 | Add a `Popup.razor` Razor component under `Pages` directory with the following content. 14 | 15 | ```razor 16 | @page "/popup.html" 17 | @inherits BasePage 18 | 19 |

My popup page

20 | ``` 21 | 22 | 23 | # Reference 24 | 25 | - [Chrome for Developers](https://developer.chrome.com/docs/extensions/develop/ui/add-popup) 26 | - [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Popups) 27 | -------------------------------------------------------------------------------- /docs/Pages/03_03_Messaging.md: -------------------------------------------------------------------------------- 1 | # Messaging between the contexts 2 | 3 | Communication between the different contexts is a common requirement, the way to achieve this depends on the source and the target context of the message. 4 | 5 | ## Sending a message 6 | 7 | The `Runtime.SendMessage` API can be used from any context to send a message to all other instances except for Content Scripts. 8 | 9 | The `Tabs.SendMessage` API can be used from any context to send a message to all the instances in a tab, i.e. Content Scripts and extension iframes. 10 | 11 | Any object can be passed when invoking the API, so you can just create a message wrapper to include the metadata for the message, e.g. sender, intended recipient, timestamp, message type, payload, etc. 12 | 13 | 14 | ## Receiving a message 15 | 16 | The `Runtime.OnMessage` API can be used from any context to receive a message. 17 | 18 | It is essential to be aware that there may be multiple instances receiving the same message, therefore you can use something to filter out noise messages, for example, having a property in the message object to indicate which context the message is intended for. 19 | 20 | The pages with listeners will only get the message if the page is active when the message is sent. 21 | However, for Background Worker that listens to `OnMessage` event, it will be activated if it is already idle when the message is sent. 22 | 23 | 24 | ## Continuous Messaging 25 | 26 | The contexts can also use the `Runtime.Connect` and `Tabs.Connect` API to create a `Port` that you can use to continuously send messages to the target context. 27 | 28 | The recipient context should then use the `Runtime.OnConnect` event to listen to port creation events and respond to messages on the `Port` object. 29 | 30 | 31 | # Reference 32 | 33 | - [Chrome for Developers](https://developer.chrome.com/docs/extensions/develop/concepts/messaging) 34 | - [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime) 35 | - [Messaging Sample Project](https://github.com/mingyaulee/Blazor.BrowserExtension.Samples/tree/main/Messaging) 36 | -------------------------------------------------------------------------------- /docs/Pages/04_02_Routing.md: -------------------------------------------------------------------------------- 1 | # How does routing work? 2 | 3 | Physical file routing is used to serve different pages. 4 | 5 | When building the project: 6 | 7 | 1. All the Razor components are processed to get a list of all the physical file routes from the `@page` attribute. 8 | 0. The routing entry file (default is `index.html`, see [`BrowserExtensionRoutingEntryFile`](04_01_ConfigureBuild.md)) is replicated in the output directory based on the list of physical file routes. 9 | 10 | For example, if the `Popup.razor` contains `@page "/popup.html"` and the `Options.razor` contains `@page "/options.html"`, when the project is built or published, the file `index.html` will be replicated in the output directory with the names `popup.html` and `options.html`. 11 | 12 | This is especially useful for browser extensions because the browsers only serve static files, and the presence of these physical files supports the routing when the extension page is reloaded. 13 | -------------------------------------------------------------------------------- /docs/Pages/04_03_AdditionalInformation.md: -------------------------------------------------------------------------------- 1 | # Additional Information 2 | 3 | Find out how to build a cross browser extension with the links below: 4 | 5 | 1. [MDN - Building a cross-browser extension](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension) 6 | 2. [David Rousset - Creating One Browser Extension For All Browsers: Edge, Chrome, Firefox, Opera, Brave And Vivaldi](https://www.smashingmagazine.com/2017/04/browser-extension-edge-chrome-firefox-opera-brave-vivaldi/) 7 | 8 | If you want to publish the extension to the browser extension stores, do take note of the following (courtesy of [@dragnilar](https://github.com/mingyaulee/Blazor.BrowserExtension/issues/39)): 9 | 10 | - Publishing to the Microsoft Edge Add-ons store is easier as long as you note somewhere in the testing steps and/or description that the extension uses Blazor. It's slower than Google but it's pretty much a hassle free experience. 11 | - Publishing to Google Chrome Extension store is more difficult. They will reject your extension at first if you declare any chromium apis in the manifest. Their automated testing doesn't work with Blazor / WASM yet but the support team will help you to resolve the challenges throughout the process. 12 | -------------------------------------------------------------------------------- /docs/Pages/NotFound.md: -------------------------------------------------------------------------------- 1 | # 404 Not Found 2 | 3 | The page requested is not found. -------------------------------------------------------------------------------- /docs/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Docs.Routing; 3 | using Microsoft.AspNetCore.Components.Web; 4 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.JSInterop; 7 | 8 | namespace Docs 9 | { 10 | public static class Program 11 | { 12 | public static async Task Main(string[] args) 13 | { 14 | if (args.Length == 2 && args[0] == "--after-publish") 15 | { 16 | AfterPublish.Run(args[1]); 17 | return; 18 | } 19 | 20 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 21 | builder.RootComponents.Add("#app"); 22 | builder.RootComponents.Add("head::after"); 23 | builder.Services.AddTransient(sp => (IJSInProcessRuntime)sp.GetRequiredService()); 24 | builder.Services.AddSingleton(); 25 | await builder.Build().RunAsync(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:44271", 8 | "sslPort": 44357 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/Blazor.BrowserExtension/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5183", 18 | "launchUrl": "http://localhost:5183/Blazor.BrowserExtension/404.html.gz", 19 | "commandLineArgs": "--pathbase=/Blazor.BrowserExtension", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | }, 24 | "https": { 25 | "commandName": "Project", 26 | "dotnetRunMessages": true, 27 | "launchBrowser": true, 28 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/Blazor.BrowserExtension/_framework/debug/ws-proxy?browser={browserInspectUri}", 29 | "applicationUrl": "https://localhost:7023;http://localhost:5183", 30 | "launchUrl": "https://localhost:7023/Blazor.BrowserExtension/404.html.gz", 31 | "commandLineArgs": "--pathbase=/Blazor.BrowserExtension", 32 | "environmentVariables": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | } 35 | }, 36 | "IIS Express": { 37 | "commandName": "IISExpress", 38 | "launchBrowser": true, 39 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 40 | "environmentVariables": { 41 | "ASPNETCORE_ENVIRONMENT": "Development" 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/Rendering/LinkExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using Docs.Routing; 6 | using Markdig; 7 | using Markdig.Renderers; 8 | using Markdig.Renderers.Html; 9 | using Markdig.Syntax; 10 | using Markdig.Syntax.Inlines; 11 | 12 | namespace Docs.Rendering 13 | { 14 | public class LinkExtension : IMarkdownExtension 15 | { 16 | public void Setup(MarkdownPipelineBuilder pipeline) 17 | { 18 | pipeline.DocumentProcessed += OnDocumentProcessed; 19 | } 20 | 21 | public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) 22 | { 23 | } 24 | 25 | private void OnDocumentProcessed(MarkdownDocument document) 26 | { 27 | foreach (var node in document.Descendants()) 28 | { 29 | if (node is LinkInline link) 30 | { 31 | if (link.Url is not null && IsDocument(link.Url, out var documentRouteMetadata)) 32 | { 33 | link.Url = link.Url.Replace(documentRouteMetadata.FileName, documentRouteMetadata.WebPath); 34 | } 35 | else 36 | { 37 | link.GetAttributes().AddProperty("target", "_blank"); 38 | } 39 | } 40 | } 41 | } 42 | 43 | static bool IsDocument(string url, [NotNullWhen(true)] out DocumentRouteMetadata? documentRouteMetadata) 44 | { 45 | if (Uri.TryCreate(url, UriKind.Absolute, out _)) 46 | { 47 | documentRouteMetadata = null; 48 | return false; 49 | } 50 | 51 | var hashIndex = url.IndexOf('#'); 52 | url = hashIndex == -1 ? url : url[..hashIndex]; 53 | documentRouteMetadata = DocumentRouteProvider.GetDocumentRouteMetadataFromFileName(url); 54 | return documentRouteMetadata is not null; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /docs/Rendering/MarkdownRenderer.cs: -------------------------------------------------------------------------------- 1 | using Markdig; 2 | 3 | namespace Docs.Rendering 4 | { 5 | public static class MarkdownRenderer 6 | { 7 | private static readonly MarkdownPipeline pipeline = new MarkdownPipelineBuilder() 8 | .UseAutoIdentifiers() 9 | .UsePipeTables() 10 | .UseBootstrap() 11 | .Use() 12 | .Build(); 13 | 14 | public static string Render(string source) 15 | { 16 | return Markdown.ToHtml(source, pipeline); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/Routing/DocumentRouteEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Docs.Routing 4 | { 5 | public class DocumentRouteEvent 6 | { 7 | public event Action? OnChange; 8 | public DocumentRouteMetadata? CurrentDocument { get; set; } 9 | public void TriggerChange(DocumentRouteMetadata? document) 10 | { 11 | CurrentDocument = document; 12 | OnChange?.Invoke(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/Routing/DocumentRouteMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Docs.Routing 2 | { 3 | public class DocumentRouteMetadata 4 | { 5 | public required string FileName { get; init; } 6 | public required string Name { get; init; } 7 | public required string WebPath { get; init; } 8 | public required int Depth { get; init; } 9 | public required string ResourceName { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/Routing/DocumentRoutePageTitle.razor: -------------------------------------------------------------------------------- 1 | @inject DocumentRouteEvent DocumentRouteEvent 2 | 3 | @if (DocumentRouteEvent.CurrentDocument?.Name is not null) 4 | { 5 | @DocumentRouteEvent.CurrentDocument?.Name | Blazor.BrowserExtension 6 | } 7 | 8 | @code { 9 | protected override void OnInitialized() 10 | { 11 | base.OnInitialized(); 12 | DocumentRouteEvent.OnChange += StateHasChanged; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/Routing/RoutingPage.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Docs.Rendering; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | using Microsoft.JSInterop; 6 | 7 | namespace Docs.Routing 8 | { 9 | [Route("/{*pageRoute}")] 10 | public class RoutingPage : ComponentBase 11 | { 12 | [Parameter] 13 | public string? PageRoute { get; set; } 14 | 15 | [Inject] 16 | public IJSInProcessRuntime JsRuntime { get; set; } = default!; 17 | 18 | [Inject] 19 | public DocumentRouteEvent DocumentRouteEvent { get; set; } = default!; 20 | 21 | protected override void BuildRenderTree(RenderTreeBuilder builder) 22 | { 23 | var documentRouteMetadata = DocumentRouteProvider.GetDocumentRouteMetadataFromPath(PageRoute); 24 | using var stream = DocumentRouteProvider.GetDocumentRouteResourceStream(documentRouteMetadata); 25 | using var streamReader = new StreamReader(stream); 26 | var markdown = MarkdownRenderer.Render(streamReader.ReadToEnd()); 27 | builder.AddMarkupContent(0, markdown); 28 | DocumentRouteEvent.TriggerChange(documentRouteMetadata); 29 | } 30 | 31 | protected override void OnAfterRender(bool firstRender) 32 | { 33 | base.OnAfterRender(firstRender); 34 | JsRuntime.InvokeVoid("globalThis.appUtils.onPageRender"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using Docs 10 | @using Docs.Layout 11 | -------------------------------------------------------------------------------- /docs/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [ 5 | { 6 | "library": "bootstrap@5.3.2", 7 | "destination": "wwwroot/lib/bootstrap/", 8 | "files": [ 9 | "js/bootstrap.min.js", 10 | "js/bootstrap.min.js.map", 11 | "js/bootstrap.js.map", 12 | "js/bootstrap.js", 13 | "css/bootstrap.css", 14 | "css/bootstrap.css.map", 15 | "css/bootstrap.min.css", 16 | "css/bootstrap.min.css.map" 17 | ] 18 | }, 19 | { 20 | "provider": "jsdelivr", 21 | "library": "@fortawesome/fontawesome-free@6.5.1", 22 | "destination": "wwwroot/lib/fortawesome/fontawesome-free/", 23 | "files": [ 24 | "css/all.min.css", 25 | "css/all.css", 26 | "webfonts/fa-brands-400.ttf", 27 | "webfonts/fa-brands-400.woff2", 28 | "webfonts/fa-regular-400.ttf", 29 | "webfonts/fa-regular-400.woff2", 30 | "webfonts/fa-solid-900.ttf", 31 | "webfonts/fa-solid-900.woff2", 32 | "webfonts/fa-v4compatibility.ttf", 33 | "webfonts/fa-v4compatibility.woff2" 34 | ] 35 | }, 36 | { 37 | "library": "prism@1.29.0", 38 | "destination": "wwwroot/lib/prism/", 39 | "files": [ 40 | "prism.min.js", 41 | "prism.js", 42 | "themes/prism.css", 43 | "themes/prism.min.css", 44 | "plugins/line-numbers/prism-line-numbers.css", 45 | "plugins/line-numbers/prism-line-numbers.js", 46 | "plugins/line-numbers/prism-line-numbers.min.css", 47 | "plugins/line-numbers/prism-line-numbers.min.js", 48 | "plugins/toolbar/prism-toolbar.css", 49 | "plugins/toolbar/prism-toolbar.js", 50 | "plugins/toolbar/prism-toolbar.min.css", 51 | "plugins/toolbar/prism-toolbar.min.js", 52 | "plugins/copy-to-clipboard/prism-copy-to-clipboard.js", 53 | "plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js", 54 | "components/prism-json.js", 55 | "components/prism-json.min.js", 56 | "components/prism-bash.js", 57 | "components/prism-bash.min.js", 58 | "components/prism-csharp.js", 59 | "components/prism-csharp.min.js", 60 | "components/prism-cshtml.js", 61 | "components/prism-cshtml.min.js" 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /docs/wwwroot/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/Demo.gif -------------------------------------------------------------------------------- /docs/wwwroot/google55f6f5677a7d6793.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google55f6f5677a7d6793.html -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/docs/wwwroot/lib/fortawesome/fontawesome-free/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /docs/wwwroot/lib/prism/components/prism-cshtml.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function s(e,s){for(var a=0;a/g,(function(){return"(?:"+e+")"}));return e.replace(//g,"[^\\s\\S]").replace(//g,'(?:@(?!")|"(?:[^\r\n\\\\"]|\\\\.)*"|@"(?:[^\\\\"]|""|\\\\[^])*"(?!")|\'(?:(?:[^\r\n\'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})\'|(?=[^\\\\](?!\'))))').replace(//g,"(?:/(?![/*])|//.*[\r\n]|/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)")}var a=s("\\((?:[^()'\"@/]|||)*\\)",2),t=s("\\[(?:[^\\[\\]'\"@/]|||)*\\]",1),r=s("\\{(?:[^{}'\"@/]|||)*\\}",2),n="@(?:await\\b\\s*)?(?:(?!await\\b)\\w+\\b|"+a+")(?:[?!]?\\.\\w+\\b|(?:"+s("<(?:[^<>'\"@/]||)*>",1)+")?"+a+"|"+t+")*(?![?!\\.(\\[]|<(?!/))",l="(?:\"[^\"@]*\"|'[^'@]*'|[^\\s'\"@>=]+(?=[\\s>])|[\"'][^\"'@]*(?:(?:@(?![\\w()])|"+n+")[^\"'@]*)+[\"'])",i="(?:\\s(?:\\s*[^\\s>/=]+(?:\\s*=\\s*|(?=[\\s/>])))+)?".replace(//,l),g="(?!\\d)[^\\s>/=$<%]+"+i+"\\s*/?>",o="\\B@?(?:<([a-zA-Z][\\w:]*)"+i+"\\s*>(?:[^<]|(?:[^<]|)*",2)+")*|<"+g+")";e.languages.cshtml=e.languages.extend("markup",{});var c={pattern:/\S[\s\S]*/,alias:"language-csharp",inside:e.languages.insertBefore("csharp","string",{html:{pattern:RegExp(o),greedy:!0,inside:e.languages.cshtml}},{csharp:e.languages.extend("csharp",{})})},p={pattern:RegExp("(^|[^@])"+n),lookbehind:!0,greedy:!0,alias:"variable",inside:{keyword:/^@/,csharp:c}};e.languages.cshtml.tag.pattern=RegExp(" code { 8 | position: relative; 9 | white-space: inherit; 10 | } 11 | 12 | .line-numbers .line-numbers-rows { 13 | position: absolute; 14 | pointer-events: none; 15 | top: 0; 16 | font-size: 100%; 17 | left: -3.8em; 18 | width: 3em; /* works for line-numbers below 1000 lines */ 19 | letter-spacing: -1px; 20 | border-right: 1px solid #999; 21 | 22 | -webkit-user-select: none; 23 | -moz-user-select: none; 24 | -ms-user-select: none; 25 | user-select: none; 26 | 27 | } 28 | 29 | .line-numbers-rows > span { 30 | display: block; 31 | counter-increment: linenumber; 32 | } 33 | 34 | .line-numbers-rows > span:before { 35 | content: counter(linenumber); 36 | color: #999; 37 | display: block; 38 | padding-right: 0.8em; 39 | text-align: right; 40 | } 41 | -------------------------------------------------------------------------------- /docs/wwwroot/lib/prism/plugins/line-numbers/prism-line-numbers.min.css: -------------------------------------------------------------------------------- 1 | pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} -------------------------------------------------------------------------------- /docs/wwwroot/lib/prism/plugins/line-numbers/prism-line-numbers.min.js: -------------------------------------------------------------------------------- 1 | !function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);ts&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;r .toolbar { 6 | position: absolute; 7 | z-index: 10; 8 | top: .3em; 9 | right: .2em; 10 | transition: opacity 0.3s ease-in-out; 11 | opacity: 0; 12 | } 13 | 14 | div.code-toolbar:hover > .toolbar { 15 | opacity: 1; 16 | } 17 | 18 | /* Separate line b/c rules are thrown out if selector is invalid. 19 | IE11 and old Edge versions don't support :focus-within. */ 20 | div.code-toolbar:focus-within > .toolbar { 21 | opacity: 1; 22 | } 23 | 24 | div.code-toolbar > .toolbar > .toolbar-item { 25 | display: inline-block; 26 | } 27 | 28 | div.code-toolbar > .toolbar > .toolbar-item > a { 29 | cursor: pointer; 30 | } 31 | 32 | div.code-toolbar > .toolbar > .toolbar-item > button { 33 | background: none; 34 | border: 0; 35 | color: inherit; 36 | font: inherit; 37 | line-height: normal; 38 | overflow: visible; 39 | padding: 0; 40 | -webkit-user-select: none; /* for button */ 41 | -moz-user-select: none; 42 | -ms-user-select: none; 43 | } 44 | 45 | div.code-toolbar > .toolbar > .toolbar-item > a, 46 | div.code-toolbar > .toolbar > .toolbar-item > button, 47 | div.code-toolbar > .toolbar > .toolbar-item > span { 48 | color: #bbb; 49 | font-size: .8em; 50 | padding: 0 .5em; 51 | background: #f5f2f0; 52 | background: rgba(224, 224, 224, 0.2); 53 | box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); 54 | border-radius: .5em; 55 | } 56 | 57 | div.code-toolbar > .toolbar > .toolbar-item > a:hover, 58 | div.code-toolbar > .toolbar > .toolbar-item > a:focus, 59 | div.code-toolbar > .toolbar > .toolbar-item > button:hover, 60 | div.code-toolbar > .toolbar > .toolbar-item > button:focus, 61 | div.code-toolbar > .toolbar > .toolbar-item > span:hover, 62 | div.code-toolbar > .toolbar > .toolbar-item > span:focus { 63 | color: inherit; 64 | text-decoration: none; 65 | } 66 | -------------------------------------------------------------------------------- /docs/wwwroot/lib/prism/plugins/toolbar/prism-toolbar.min.css: -------------------------------------------------------------------------------- 1 | div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none} -------------------------------------------------------------------------------- /docs/wwwroot/lib/prism/plugins/toolbar/prism-toolbar.min.js: -------------------------------------------------------------------------------- 1 | !function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e=[],t={},n=function(){};Prism.plugins.toolbar={};var a=Prism.plugins.toolbar.registerButton=function(n,a){var r;r="function"==typeof a?a:function(e){var t;return"function"==typeof a.onClick?((t=document.createElement("button")).type="button",t.addEventListener("click",(function(){a.onClick.call(this,e)}))):"string"==typeof a.url?(t=document.createElement("a")).href=a.url:t=document.createElement("span"),a.className&&t.classList.add(a.className),t.textContent=a.text,t},n in t?console.warn('There is a button with the key "'+n+'" registered already.'):e.push(t[n]=r)},r=Prism.plugins.toolbar.hook=function(a){var r=a.element.parentNode;if(r&&/pre/i.test(r.nodeName)&&!r.parentNode.classList.contains("code-toolbar")){var o=document.createElement("div");o.classList.add("code-toolbar"),r.parentNode.insertBefore(o,r),o.appendChild(r);var i=document.createElement("div");i.classList.add("toolbar");var l=e,d=function(e){for(;e;){var t=e.getAttribute("data-toolbar-order");if(null!=t)return(t=t.trim()).length?t.split(/\s*,\s*/g):[];e=e.parentElement}}(a.element);d&&(l=d.map((function(e){return t[e]||n}))),l.forEach((function(e){var t=e(a);if(t){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(t),i.appendChild(n)}})),o.appendChild(i)}};a("label",(function(e){var t=e.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&t.hasAttribute("data-label")){var n,a,r=t.getAttribute("data-label");try{a=document.querySelector("template#"+r)}catch(e){}return a?n=a.content:(t.hasAttribute("data-url")?(n=document.createElement("a")).href=t.getAttribute("data-url"):n=document.createElement("span"),n.textContent=r),n}})),Prism.hooks.add("complete",r)}}(); -------------------------------------------------------------------------------- /docs/wwwroot/lib/prism/themes/prism.min.css: -------------------------------------------------------------------------------- 1 | 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:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-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:#f5f2f0}: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} -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/AnalyzerReleases.Shipped.md: -------------------------------------------------------------------------------- 1 | ## Release 1.0 2 | 3 | ### New Rules 4 | 5 | Rule ID | Category | Severity | Notes 6 | --------|----------|----------|------- 7 | BROWSEREXT001 | CodeGenerator | Error | Failed to translate expression in Background Worker Main method 8 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/AnalyzerReleases.Unshipped.md: -------------------------------------------------------------------------------- 1 | .Uns -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/Blazor.BrowserExtension.Analyzer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | latest 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | true 19 | 20 | 21 | false 22 | 23 | 24 | MSB3277;$(NoWarn) 25 | 26 | 27 | true 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/DiagnosticFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Blazor.BrowserExtension.Analyzer 4 | { 5 | internal static class DiagnosticFactory 6 | { 7 | public static Diagnostic CreateTranslationError(Location location, string message) 8 | { 9 | return Diagnostic.Create(TranslationDiagnostic, location, message); 10 | } 11 | 12 | static DiagnosticDescriptor TranslationDiagnostic = new( 13 | id: "BROWSEREXT001", 14 | title: "Background Worker Translation Error", 15 | messageFormat: "Failed to translate expression: {0}", 16 | category: "CodeGenerator", 17 | defaultSeverity: DiagnosticSeverity.Error, 18 | isEnabledByDefault: true 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Blazor.BrowserExtension.Analyzer": { 4 | "commandName": "DebugRoslynComponent", 5 | "targetProject": "..\\..\\test\\Blazor.BrowserExtension.IntegrationTest\\Blazor.BrowserExtension.IntegrationTest.csproj" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/Translation/IndentedCodeLinesBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Blazor.BrowserExtension.Analyzer.Translation 5 | { 6 | internal class IndentedCodeLinesBuilder(string indentation, string separator) 7 | { 8 | private bool isEmpty = true; 9 | private readonly StringBuilder stringBuilder = new(); 10 | private const string NewLine = @" 11 | "; 12 | 13 | public void Append(string lines) 14 | { 15 | if (!isEmpty) 16 | { 17 | stringBuilder.Append(separator); 18 | } 19 | 20 | foreach (var line in lines.Split(NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) 21 | { 22 | if (!isEmpty) 23 | { 24 | stringBuilder.Append(NewLine); 25 | } 26 | 27 | stringBuilder.Append(indentation); 28 | stringBuilder.Append(line); 29 | isEmpty = false; 30 | } 31 | } 32 | 33 | public string Build() 34 | { 35 | return stringBuilder.ToString(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/Translation/ParentExpressionType.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Translation 2 | { 3 | internal enum ParentExpressionType 4 | { 5 | None, 6 | ObjectCreation 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/Translation/ParentExpressionTypeSwitcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Blazor.BrowserExtension.Analyzer.Translation 4 | { 5 | internal class ParentExpressionTypeSwitcher : IDisposable 6 | { 7 | private readonly TranslateContext context; 8 | private readonly ParentExpressionType originalType; 9 | private bool disposedValue; 10 | 11 | public ParentExpressionTypeSwitcher(TranslateContext context, ParentExpressionType type) 12 | { 13 | this.context = context; 14 | originalType = context.ParentExpressionType; 15 | context.ParentExpressionType = type; 16 | } 17 | 18 | protected virtual void Dispose(bool disposing) 19 | { 20 | if (!disposedValue) 21 | { 22 | if (disposing) 23 | { 24 | context.ParentExpressionType = originalType; 25 | } 26 | 27 | disposedValue = true; 28 | } 29 | } 30 | 31 | public void Dispose() 32 | { 33 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 34 | Dispose(disposing: true); 35 | GC.SuppressFinalize(this); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Analyzer/Translation/SystemTranslations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Analyzer.Translation 4 | { 5 | internal static class SystemTranslations 6 | { 7 | private static Dictionary systemTranslations = new() 8 | { 9 | { "System.Console", "console" }, 10 | { "System.Console.WriteLine", "log" }, 11 | { "System.Console.Write", "log" }, 12 | { "Microsoft.Extensions.Logging.ILogger", "console" }, 13 | { "Microsoft.Extensions.Logging.LoggerExtensions.LogTrace", "trace" }, 14 | { "Microsoft.Extensions.Logging.LoggerExtensions.LogDebug", "debug" }, 15 | { "Microsoft.Extensions.Logging.LoggerExtensions.LogInformation", "log" }, 16 | { "Microsoft.Extensions.Logging.LoggerExtensions.LogWarning", "warn" }, 17 | { "Microsoft.Extensions.Logging.LoggerExtensions.LogError", "error" }, 18 | { "Microsoft.Extensions.Logging.LoggerExtensions.LogCritical", "error" }, 19 | }; 20 | 21 | public static bool TryGetValue(string key, out string translation) => systemTranslations.TryGetValue(key, out translation); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/README.md: -------------------------------------------------------------------------------- 1 | ## Upgrading `System.Text.Json` package for .Net Framework 4.7.2 2 | All required binaries for `System.Text.Json` from the output (`bin/` directory) must be copied to the `lib` directory so that they can be packaged in the NuGet package and loaded when build is running in Visual Studio. -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/BlazorToBrowserExtensionBootstrapFile.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Bootstrap; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Blazor.BrowserExtension.Build.Tasks 9 | { 10 | public class BlazorToBrowserExtensionBootstrapFile : Task 11 | { 12 | private const string LogPrefix = " "; 13 | 14 | [Required] 15 | public string FilePath { get; set; } 16 | 17 | [Required] 18 | public string FileType { get; set; } 19 | 20 | public override bool Execute() 21 | { 22 | try 23 | { 24 | var bootstrapFileType = ParseFileType(); 25 | 26 | Log.LogMessage(MessageImportance.Normal, $"{LogPrefix}Reading content of {bootstrapFileType} file '{FilePath}'"); 27 | var fileLines = File.ReadAllLines(FilePath).ToList(); 28 | var bootstrapper = BootstrapperFactory.GetBootstrapper(bootstrapFileType); 29 | 30 | if (bootstrapper.Bootstrap(fileLines)) 31 | { 32 | File.WriteAllLines(FilePath, fileLines); 33 | Log.LogMessage(MessageImportance.Normal, $"{LogPrefix}Bootstrapping completed for {bootstrapFileType} file '{FilePath}'"); 34 | } 35 | else 36 | { 37 | Log.LogMessage(MessageImportance.Normal, $"{LogPrefix}Bootstrapping skipped for {bootstrapFileType} file '{FilePath}'"); 38 | } 39 | 40 | return true; 41 | } 42 | catch (Exception ex) 43 | { 44 | Log.LogError($"{LogPrefix}An unexpected error occurred when bootstrapping file '{FilePath}'"); 45 | Log.LogErrorFromException(ex); 46 | return false; 47 | } 48 | } 49 | 50 | private BootstrapFileType ParseFileType() 51 | { 52 | if (Enum.TryParse(FileType, out var bootstrapFileType)) 53 | { 54 | return bootstrapFileType; 55 | } 56 | 57 | #pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one 58 | throw new ArgumentOutOfRangeException(nameof(FileType), $"{nameof(FileType)} is not a recognized {nameof(BootstrapFileType)}."); 59 | #pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/BlazorToBrowserExtensionProcessOutputFiles.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Helpers; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace Blazor.BrowserExtension.Build.Tasks 8 | { 9 | public class BlazorToBrowserExtensionProcessOutputFiles : Task 10 | { 11 | [Required] 12 | public ITaskItem[] Input { get; set; } 13 | 14 | [Output] 15 | public ITaskItem[] Output { get; set; } 16 | 17 | public override bool Execute() 18 | { 19 | var output = new List(); 20 | foreach (var input in Input) 21 | { 22 | ProcessItem(input, output); 23 | } 24 | Output = output.ToArray(); 25 | return true; 26 | } 27 | 28 | private static void ProcessItem(ITaskItem input, List output) 29 | { 30 | var itemPath = input.ItemSpec; 31 | var relativePath = OutputHelper.GetOutputRelativePath(GetRelativePath(input)); 32 | output.Add(new TaskItem(itemPath, new Dictionary() 33 | { 34 | { "ContentRelativeDirectory", relativePath } 35 | })); 36 | } 37 | 38 | private static string GetRelativePath(ITaskItem item) 39 | { 40 | return Path.GetDirectoryName(item.GetMetadata("RelativePath")) ?? string.Empty; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/BlazorToBrowserExtensionProcessStaticWebAssetsManifest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Blazor.BrowserExtension.Build.Tasks.StaticWebAssets; 4 | using Microsoft.Build.Framework; 5 | using Microsoft.Build.Utilities; 6 | 7 | namespace Blazor.BrowserExtension.Build.Tasks 8 | { 9 | public class BlazorToBrowserExtensionProcessStaticWebAssetsManifest : Task 10 | { 11 | [Required] 12 | public ITaskItem Input { get; set; } 13 | public ITaskItem[] Exclude { get; set; } 14 | [Required] 15 | public string OutputPath { get; set; } 16 | 17 | [Output] 18 | public ITaskItem[] Output { get; set; } 19 | 20 | public override bool Execute() 21 | { 22 | var excludePaths = Exclude?.Select(exclude => exclude.ItemSpec); 23 | Output = Process(Input.ItemSpec, excludePaths, OutputPath); 24 | return true; 25 | } 26 | 27 | private static ITaskItem[] Process(string filePath, IEnumerable excludePaths, string outputPath) 28 | { 29 | var processor = new JsonManifestProcessor(excludePaths); 30 | processor.ReadFromFile(filePath); 31 | processor.Process(outputPath); 32 | var output = processor.GetOutput() 33 | .Select(staticWebAssetFile => 34 | new TaskItem(staticWebAssetFile.FilePath, new Dictionary() 35 | { 36 | { "ContentRelativeDirectory", staticWebAssetFile.RelativePath } 37 | }) 38 | ).ToArray(); 39 | return output; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/BlazorToBrowserExtensionWriteConfigFile.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Framework; 2 | using Microsoft.Build.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text.Json; 7 | 8 | namespace Blazor.BrowserExtension.Build.Tasks 9 | { 10 | public class BlazorToBrowserExtensionWriteConfigFile : Task 11 | { 12 | private const string LogPrefix = " "; 13 | 14 | [Required] 15 | public ITaskItem[] Input { get; set; } 16 | 17 | [Required] 18 | public string FilePath { get; set; } 19 | 20 | public override bool Execute() 21 | { 22 | try 23 | { 24 | var option = GetOption(Input); 25 | var optionJson = JsonSerializer.Serialize(option); 26 | 27 | var directory = Path.GetDirectoryName(FilePath); 28 | if (!Directory.Exists(directory)) 29 | { 30 | Directory.CreateDirectory(directory); 31 | } 32 | 33 | File.WriteAllText(FilePath, optionJson); 34 | return true; 35 | } 36 | catch (Exception ex) 37 | { 38 | Log.LogError($"{LogPrefix}An unexpected error occurred when writing option file '{FilePath}'"); 39 | Log.LogErrorFromException(ex); 40 | return false; 41 | } 42 | } 43 | 44 | private static Dictionary GetOption(IEnumerable items) 45 | { 46 | var option = new Dictionary(); 47 | foreach (var item in items) 48 | { 49 | var key = item.GetMetadata("Key"); 50 | var valueString = item.GetMetadata("Value"); 51 | var type = item.GetMetadata("Type"); 52 | option[key] = GetValue(valueString, type); 53 | } 54 | return option; 55 | } 56 | 57 | private static object GetValue(string value, string type) 58 | { 59 | if (type == "bool") 60 | { 61 | return bool.TryParse(value, out var boolValue) && boolValue; 62 | } 63 | 64 | return value; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Bootstrap/BootstrapFileType.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Build.Tasks.Bootstrap 2 | { 3 | public enum BootstrapFileType 4 | { 5 | Project, 6 | IndexRazor, 7 | ImportsRazor, 8 | ProgramCs 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Bootstrap/BootstrapperFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.Bootstrap 4 | { 5 | public static class BootstrapperFactory 6 | { 7 | public static IFileBootstrapper GetBootstrapper(BootstrapFileType bootstrapFileType) 8 | { 9 | return bootstrapFileType switch 10 | { 11 | BootstrapFileType.Project => new ProjectFileBootstrapper(), 12 | BootstrapFileType.IndexRazor => new IndexRazorFileBootstrapper(), 13 | BootstrapFileType.ImportsRazor => new ImportsRazorFileBootstrapper(), 14 | BootstrapFileType.ProgramCs => new ProgramCsFileBootstrapper(), 15 | _ => throw new InvalidOperationException($"Bootstrap file type '{bootstrapFileType}' is not supported.") 16 | }; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Bootstrap/IFileBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.Bootstrap 4 | { 5 | public interface IFileBootstrapper 6 | { 7 | bool Bootstrap(List fileLines); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Bootstrap/ImportsRazorFileBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.Bootstrap 4 | { 5 | public class ImportsRazorFileBootstrapper : IFileBootstrapper 6 | { 7 | public bool Bootstrap(List fileLines) 8 | { 9 | var isUpdated = false; 10 | 11 | // Add 12 | // @using Blazor.BrowserExtension.Pages 13 | var usingStatement = "@using Blazor.BrowserExtension.Pages"; 14 | var usingStatementIndex = fileLines.FindIndex(fileLine => fileLine.Contains(usingStatement)); 15 | if (usingStatementIndex == -1) 16 | { 17 | fileLines.Insert(fileLines.FindLastIndex(line => !string.IsNullOrEmpty(line)) + 1, usingStatement); 18 | isUpdated = true; 19 | } 20 | 21 | return isUpdated; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Bootstrap/IndexRazorFileBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.Bootstrap 4 | { 5 | public class IndexRazorFileBootstrapper : IFileBootstrapper 6 | { 7 | public bool Bootstrap(List fileLines) 8 | { 9 | var isUpdated = false; 10 | 11 | // Replace 12 | // @page "/" 13 | // with 14 | // @page "/index.html" 15 | // @inherits IndexPage 16 | var pageDirectiveIndex = fileLines.FindIndex(fileLine => fileLine.Contains("@page \"/\"")); 17 | if (pageDirectiveIndex > -1) 18 | { 19 | fileLines[pageDirectiveIndex] = "@page \"/index.html\""; 20 | fileLines.Insert(pageDirectiveIndex + 1, "@inherits IndexPage"); 21 | isUpdated = true; 22 | } 23 | 24 | return isUpdated; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Bootstrap/ProjectFileBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.Bootstrap 4 | { 5 | public class ProjectFileBootstrapper : IFileBootstrapper 6 | { 7 | public bool Bootstrap(List fileLines) 8 | { 9 | var isUpdated = false; 10 | 11 | // Remove true 12 | var bootstrapPropertyTagIndex = fileLines.FindIndex(fileLine => fileLine.Contains("BrowserExtensionBootstrap")); 13 | if (bootstrapPropertyTagIndex > -1) 14 | { 15 | fileLines.RemoveAt(bootstrapPropertyTagIndex); 16 | isUpdated = true; 17 | } 18 | 19 | return isUpdated; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/IValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 4 | { 5 | internal interface IValidator 6 | { 7 | IEnumerable Validate(IDictionary manifestItems); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/Manifest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 4 | { 5 | internal class Manifest 6 | { 7 | public IDictionary Items { get; set; } 8 | public IEnumerable ParseErrors { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/ManifestItem.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 4 | { 5 | internal class ManifestItem 6 | { 7 | public ManifestItem(long? lineNumber, long? columnNumber, string value) 8 | { 9 | LineNumber = lineNumber; 10 | ColumnNumber = columnNumber; 11 | Value = value; 12 | } 13 | 14 | public long? LineNumber { get; } 15 | public long? ColumnNumber { get; set; } 16 | public string Value { get; } 17 | 18 | public bool ContainsExact(string value) => Regex.IsMatch(Value, Regex.Escape($"\"{value}\"")); 19 | public bool ContainsExactIgnoreCase(string value) => Regex.IsMatch(Value, Regex.Escape($"\"{value}\""), RegexOptions.IgnoreCase); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/ManifestItemKey.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 2 | { 3 | internal enum ManifestItemKey 4 | { 5 | ManifestVersion, 6 | OptionsUi, 7 | BrowserAction, 8 | Background, 9 | ContentSecurityPolicy, 10 | ContentScripts, 11 | WebAccessibleResources, 12 | Permissions 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/ManifestParseError.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 2 | { 3 | internal class ManifestParseError 4 | { 5 | public ManifestParseError(long? lineNumber, long? columnNumber, string message) 6 | { 7 | LineNumber = lineNumber; 8 | ColumnNumber = columnNumber; 9 | Message = message; 10 | } 11 | 12 | public long? LineNumber { get; } 13 | public long? ColumnNumber { get; } 14 | public string Message { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/ManifestV3Validator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 4 | { 5 | internal class ManifestV3Validator : IValidator 6 | { 7 | public IEnumerable Validate(IDictionary manifestItems) 8 | { 9 | var validationResults = new List(); 10 | foreach (var item in manifestItems) 11 | { 12 | var manifestItem = item.Value; 13 | switch (item.Key) 14 | { 15 | case ManifestItemKey.WebAccessibleResources: 16 | if (!manifestItem.ContainsExactIgnoreCase("framework/*") 17 | || !manifestItem.ContainsExactIgnoreCase("content/*")) 18 | { 19 | validationResults.Add(new ValidationResult() 20 | { 21 | Item = manifestItem, 22 | Error = "Manifest item 'web_accessible_resources' must specify \"framework/*\" and \"content/*\"." 23 | }); 24 | } 25 | break; 26 | } 27 | } 28 | return validationResults; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/ValidationResult.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 2 | { 3 | internal class ValidationResult 4 | { 5 | public string Error { get; set; } 6 | public ManifestItem Item { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/ExtensionManifest/ValidatorFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Build.Tasks.ExtensionManifest 2 | { 3 | internal static class ValidatorFactory 4 | { 5 | public static IValidator GetValidator(ManifestItem version) 6 | { 7 | #if NETFRAMEWORK 8 | if (version.Value.Contains("3")) 9 | #else 10 | if (version.Value.Contains('3')) 11 | #endif 12 | { 13 | return new ManifestV3Validator(); 14 | } 15 | 16 | return null; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/Helpers/OutputHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Build.Tasks.Helpers 4 | { 5 | internal static class OutputHelper 6 | { 7 | public static IDictionary PathMappings { get; } = new Dictionary() 8 | { 9 | { "_framework", "framework" }, 10 | { "_content", "content" } 11 | }; 12 | 13 | public static string GetOutputRelativePath(string relativePath) 14 | { 15 | foreach (var mapping in PathMappings) 16 | { 17 | relativePath = relativePath?.Replace(mapping.Key, mapping.Value); 18 | } 19 | return relativePath; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/StaticWebAssets/BaseManifestProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace Blazor.BrowserExtension.Build.Tasks.StaticWebAssets 7 | { 8 | public abstract class BaseManifestProcessor 9 | { 10 | private readonly IEnumerable excludePaths; 11 | private readonly List output; 12 | 13 | protected BaseManifestProcessor(IEnumerable excludePaths) 14 | { 15 | this.excludePaths = excludePaths?.Select(excludePath => NormalizePath(excludePath)).ToList() ?? Enumerable.Empty(); 16 | output = new List(); 17 | } 18 | 19 | public abstract void ReadFromFile(string filePath); 20 | 21 | public abstract void Process(string outputPath); 22 | 23 | public abstract void WriteToFile(string filePath); 24 | 25 | protected void AddOutput(string filePath, string relativePath) 26 | { 27 | relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar, '_').TrimEnd(Path.DirectorySeparatorChar); 28 | output.Add(new StaticWebAssetFile() 29 | { 30 | FilePath = filePath, 31 | RelativePath = relativePath 32 | }); 33 | } 34 | 35 | public IEnumerable GetOutput() 36 | { 37 | return output; 38 | } 39 | 40 | protected bool ShouldExcludePath(string path) 41 | { 42 | return excludePaths.Any(excludePath => path.Equals(excludePath, StringComparison.OrdinalIgnoreCase)); 43 | } 44 | 45 | protected static string NormalizePath(string path) 46 | { 47 | return Path.GetFullPath(new Uri(path).LocalPath) 48 | .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/Tasks/StaticWebAssets/StaticWebAssetFile.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Build.Tasks.StaticWebAssets 2 | { 3 | public class StaticWebAssetFile 4 | { 5 | public string FilePath { get; set; } 6 | public string RelativePath { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/build/Blazor.BrowserExtension.Build.Compression.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | brotli 12 | _framework/**/*.js;_framework/**/*.json 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/build/Blazor.BrowserExtension.Build.Project.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <_BrowserExtension_Project_IntermediateOutput_Directory>$(IntermediateOutputPath) 11 | <_BrowserExtension_Project_IntermediateOutput_Directory Condition="'$([System.IO.Path]::IsPathRooted($(IntermediateOutputPath)))' == 'false'">$(ProjectDir)$(IntermediateOutputPath) 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/build/Blazor.BrowserExtension.Build.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/content/BackgroundWorker.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension; 2 | using System.Threading.Tasks; 3 | 4 | namespace {{ProjectNamespace}} 5 | { 6 | public partial class BackgroundWorker : BackgroundWorkerBase 7 | { 8 | [BackgroundWorkerMain] 9 | public override void Main() 10 | { 11 | WebExtensions.Runtime.OnInstalled.AddListener(OnInstalled); 12 | } 13 | 14 | async Task OnInstalled() 15 | { 16 | var indexPageUrl = WebExtensions.Runtime.GetURL("index.html"); 17 | await WebExtensions.Tabs.Create(new() 18 | { 19 | Url = indexPageUrl 20 | }); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/content/BackgroundWorker.js: -------------------------------------------------------------------------------- 1 | // Import for the side effect of defining a global 'browser' variable 2 | import * as _ from "/content/Blazor.BrowserExtension/lib/browser-polyfill.min.js"; 3 | 4 | browser.runtime.onInstalled.addListener(() => { 5 | const indexPageUrl = browser.runtime.getURL("index.html"); 6 | browser.tabs.create({ 7 | url: indexPageUrl 8 | }); 9 | }); -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/content/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "My Blazor Extension", 4 | "description": "My browser extension built with Blazor WebAssembly", 5 | "version": "0.1", 6 | "background": { 7 | "service_worker": "content/BackgroundWorker.js", 8 | "type": "module" 9 | }, 10 | "content_security_policy": { 11 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 12 | }, 13 | "web_accessible_resources": [ 14 | { 15 | "resources": [ 16 | "framework/*", 17 | "content/*" 18 | ], 19 | "matches": [ "" ] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/Microsoft.Bcl.AsyncInterfaces.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/Microsoft.Bcl.AsyncInterfaces.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Buffers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Buffers.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Collections.Immutable.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Collections.Immutable.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.IO.Pipelines.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.IO.Pipelines.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Memory.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Memory.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Numerics.Vectors.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Numerics.Vectors.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Runtime.CompilerServices.Unsafe.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Runtime.CompilerServices.Unsafe.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Text.Encodings.Web.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Text.Encodings.Web.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Text.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Text.Json.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.Threading.Tasks.Extensions.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.Threading.Tasks.Extensions.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Build/lib/System.ValueTuple.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Build/lib/System.ValueTuple.dll -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/Blazor.BrowserExtension.Template.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | latest 7 | false 8 | true 9 | false 10 | 11 | 12 | 13 | 14 | true 15 | Template 16 | ..\PackageOutput 17 | false 18 | 19 | true 20 | mingyaulee 21 | Template to create a browser extension with Blazor WebAssembly. 22 | https://github.com/mingyaulee/Blazor.BrowserExtension 23 | Blazor Browser Chrome Firefox Edge Extension Addons .Net 24 | MIT 25 | Icon.png 26 | 27 | 28 | 29 | 30 | true 31 | content 32 | 33 | 34 | 35 | true 36 | 37 | 38 | 39 | true 40 | content/.template.config/Icon.png 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/.template.config/dotnetcli.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/dotnetcli.host", 3 | "symbolInfo": { 4 | "ManifestVersion": { 5 | "longName": "manifest-version", 6 | "shortName": "mv" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "Icon.png" 4 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "mingyaulee", 4 | "classifications": [ 5 | "Web", 6 | "Blazor", 7 | "WebAssembly", 8 | "BrowserExtension" 9 | ], 10 | "name": "Browser extension with Blazor WebAssembly", 11 | "description": "Template to create a browser extension with Blazor WebAssembly.", 12 | "identity": "Blazor.BrowserExtension", 13 | "shortName": "browserext", 14 | "tags": { 15 | "language": "C#", 16 | "type": "project" 17 | }, 18 | "sourceName": "HelloBlazorExtension", 19 | "defaultName": "BrowserExtension1", 20 | "preferNameDirectory": true, 21 | "symbols": { 22 | "Framework": { 23 | "type": "parameter", 24 | "description": "The target framework for the project.", 25 | "datatype": "choice", 26 | "choices": [ 27 | { 28 | "choice": "net8.0", 29 | "description": "Target .NET 8.0" 30 | }, 31 | { 32 | "choice": "net9.0", 33 | "description": "Target .NET 9.0" 34 | } 35 | ], 36 | "defaultValue": "net9.0", 37 | "replaces": "net9.0" 38 | }, 39 | "ManifestVersion": { 40 | "type": "parameter", 41 | "description": "The manifest version for the browser extension.", 42 | "datatype": "choice", 43 | "choices": [ 44 | { 45 | "choice": "3", 46 | "description": "Manifest V3" 47 | } 48 | ], 49 | "defaultValue": "3" 50 | }, 51 | "IsNet8": { 52 | "type": "computed", 53 | "value": "Framework == \"net8.0\"" 54 | } 55 | }, 56 | "sources": [ 57 | { 58 | "source": "./", 59 | "target": "./", 60 | "include": [ "**/*" ], 61 | "exclude": [ ".template.config/**/*" ], 62 | "modifiers": [] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/BackgroundWorker.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension; 2 | 3 | namespace HelloBlazorExtension 4 | { 5 | public partial class BackgroundWorker : BackgroundWorkerBase 6 | { 7 | [BackgroundWorkerMain] 8 | public override void Main() 9 | { 10 | WebExtensions.Runtime.OnInstalled.AddListener(OnInstalled); 11 | } 12 | 13 | async Task OnInstalled() 14 | { 15 | var indexPageUrl = WebExtensions.Runtime.GetURL("index.html"); 16 | await WebExtensions.Tabs.Create(new() 17 | { 18 | Url = indexPageUrl 19 | }); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/HelloBlazorExtension.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | $(NoWarn);NU1504;NU1605 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | @Body -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f8f9fa; 3 | } 4 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/index.html" 2 | @inherits IndexPage 3 | 4 |

Hello, from Blazor.

5 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Pages/Options.razor: -------------------------------------------------------------------------------- 1 | @page "/options.html" 2 | @inherits BasePage 3 | 4 |

My options page

5 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Pages/Popup.razor: -------------------------------------------------------------------------------- 1 | @page "/popup.html" 2 | @inherits BasePage 3 | 4 |

My popup page

5 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using Blazor.BrowserExtension; 4 | using HelloBlazorExtension; 5 | 6 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 7 | builder.UseBrowserExtension(browserExtension => 8 | { 9 | if (browserExtension.Mode == BrowserExtensionMode.Background) 10 | { 11 | builder.RootComponents.AddBackgroundWorker(); 12 | } 13 | else 14 | { 15 | builder.RootComponents.Add("#app"); 16 | builder.RootComponents.Add("head::after"); 17 | } 18 | }); 19 | 20 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 21 | 22 | await builder.Build().RunAsync(); 23 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:10657", 7 | "sslPort": 44314 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "HelloBlazorExtension": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": "true", 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using HelloBlazorExtension 10 | @using HelloBlazorExtension.Layout 11 | @using Blazor.BrowserExtension.Pages 12 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Template/HelloBlazorExtension/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Blazor.BrowserExtension.Template/HelloBlazorExtension/wwwroot/favicon.png -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HelloBlazorExtension 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 | An unhandled error has occurred. 25 | Reload 26 | 🗙 27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension.Template/HelloBlazorExtension/wwwroot/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "HelloBlazorExtension Extension", 4 | "description": "My browser extension built with Blazor WebAssembly", 5 | "version": "0.1", 6 | "background": { 7 | "service_worker": "content/BackgroundWorker.js", 8 | "type": "module" 9 | }, 10 | "action": { 11 | "default_popup": "popup.html" 12 | }, 13 | "options_ui": { 14 | "page": "options.html", 15 | "open_in_tab": true 16 | }, 17 | "content_security_policy": { 18 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 19 | }, 20 | "web_accessible_resources": [ 21 | { 22 | "resources": [ 23 | "framework/*", 24 | "content/*" 25 | ], 26 | "matches": [ "" ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/BackgroundWorkerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using JsBind.Net; 5 | using Microsoft.AspNetCore.Components; 6 | using Microsoft.Extensions.Logging; 7 | using WebExtensions.Net; 8 | 9 | namespace Blazor.BrowserExtension 10 | { 11 | /// 12 | /// Inherit from the this class and add the [BackgroundWorker] to allow the source generator to fully support generated background worker. 13 | /// 14 | public abstract class BackgroundWorkerBase : IComponent 15 | { 16 | protected virtual Dictionary JsInstance => throw new InvalidOperationException("The JsInstance should be overridden by the generated code."); 17 | 18 | bool isInitialized; 19 | 20 | [Inject] 21 | private IJsRuntimeAdapter JsRuntimeAdapter { get; set; } 22 | 23 | [Inject] 24 | public IWebExtensionsApi WebExtensions { get; set; } 25 | 26 | [Inject] 27 | public ILogger Logger { get; set; } 28 | 29 | /// 30 | /// Implement the main method and decorate with [BackgroundWorkerMain] attribute. 31 | /// 32 | public abstract void Main(); 33 | 34 | /// 35 | /// Invoked after the background worker is initialized from JS. 36 | /// 37 | public virtual void OnInitialized() 38 | { 39 | } 40 | 41 | void IComponent.Attach(RenderHandle renderHandle) 42 | { 43 | if (isInitialized) 44 | { 45 | throw new InvalidOperationException("Background worker can only be initialized once."); 46 | } 47 | 48 | isInitialized = true; 49 | Initialize(); 50 | } 51 | 52 | Task IComponent.SetParametersAsync(ParameterView parameters) 53 | { 54 | return Task.CompletedTask; 55 | } 56 | 57 | private void Initialize() 58 | { 59 | using var initializer = new JsInitializer(JsRuntimeAdapter); 60 | initializer.Initialize(JsInstance); 61 | OnInitialized(); 62 | } 63 | 64 | private sealed class JsInitializer : ObjectBindingBase 65 | { 66 | public JsInitializer(IJsRuntimeAdapter jsRuntime) 67 | { 68 | Initialize(jsRuntime); 69 | SetAccessPath("globalThis"); 70 | } 71 | 72 | public void Initialize(Dictionary jsInstance) 73 | { 74 | InvokeVoid("setBackgroundWorkerInstance", jsInstance); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/BackgroundWorkerMainAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Blazor.BrowserExtension 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | public class BackgroundWorkerMainAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/BrowserExtensionEnvironment.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension 2 | { 3 | public class BrowserExtensionEnvironment : IBrowserExtensionEnvironment 4 | { 5 | public BrowserExtensionMode Mode { get; init; } 6 | public string BaseUrl { get; init; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/BrowserExtensionMode.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension 2 | { 3 | public enum BrowserExtensionMode 4 | { 5 | Standard, 6 | Background, 7 | ContentScript, 8 | Debug, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/Extensions/RootComponentMappingCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Blazor.BrowserExtension; 3 | 4 | namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting 5 | { 6 | public static class RootComponentMappingCollectionExtensions 7 | { 8 | public static void AddBackgroundWorker(this RootComponentMappingCollection rootComponents) 9 | where T : BackgroundWorkerBase 10 | { 11 | rootComponents.AddBackgroundWorker(typeof(T)); 12 | } 13 | 14 | public static void AddBackgroundWorker(this RootComponentMappingCollection rootComponents, Type backgroundWorkerType) 15 | { 16 | rootComponents.Add(backgroundWorkerType, "#background"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension; 2 | using Microsoft.JSInterop; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static partial class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection AddBrowserExtensionServices(this IServiceCollection services) 11 | { 12 | var iJsRuntimeService = services.FirstOrDefault(service => service.ServiceType == typeof(IJSRuntime)); 13 | if (iJsRuntimeService?.ImplementationInstance is not IJSRuntime jsRuntime) 14 | { 15 | throw new NotSupportedException("An instance of IJSRuntime must be registered by Blazor."); 16 | } 17 | 18 | var browserExtensionEnvironment = GetBrowserExtensionEnvironment(); 19 | IBrowserExtensionEnvironment.Instance = browserExtensionEnvironment; 20 | services.AddSingleton(browserExtensionEnvironment); 21 | 22 | services.Remove(iJsRuntimeService); 23 | services.AddSingleton(new ProxyJsRuntime(jsRuntime, browserExtensionEnvironment)); 24 | 25 | if (browserExtensionEnvironment.Mode == BrowserExtensionMode.Debug) 26 | { 27 | services.AddMockWebExtensions(); 28 | } 29 | else 30 | { 31 | services.AddWebExtensions(); 32 | } 33 | 34 | return services; 35 | } 36 | 37 | private static BrowserExtensionEnvironment GetBrowserExtensionEnvironment() 38 | { 39 | var browserExtensionEnvironmentValues = GetBrowserExtensionEnvironmentInterop().Split('|'); 40 | var browserExtensionModeString = browserExtensionEnvironmentValues[0]; 41 | var baseUrl = browserExtensionEnvironmentValues[1]; 42 | if (!Enum.TryParse(browserExtensionModeString, out var mode)) 43 | { 44 | mode = BrowserExtensionMode.Standard; 45 | } 46 | 47 | return new BrowserExtensionEnvironment() 48 | { 49 | Mode = mode, 50 | BaseUrl = baseUrl 51 | }; 52 | } 53 | 54 | 55 | [System.Runtime.InteropServices.JavaScript.JSImport("globalThis.BlazorBrowserExtension.BrowserExtension._getBrowserExtensionEnvironment")] 56 | private static partial string GetBrowserExtensionEnvironmentInterop(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/Extensions/WebAssemblyHostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Blazor.BrowserExtension; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | { 7 | public static class WebAssemblyHostBuilderExtensions 8 | { 9 | public static WebAssemblyHostBuilder UseBrowserExtension(this WebAssemblyHostBuilder builder, Action setup = null) 10 | { 11 | builder.Services.AddBrowserExtensionServices(); 12 | setup?.Invoke(IBrowserExtensionEnvironment.Instance); 13 | return builder; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/IBrowserExtensionEnvironment.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension 2 | { 3 | public interface IBrowserExtensionEnvironment 4 | { 5 | BrowserExtensionMode Mode { get; } 6 | static IBrowserExtensionEnvironment Instance { get; internal set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/Pages/BasePage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.Extensions.Logging; 3 | using WebExtensions.Net; 4 | 5 | namespace Blazor.BrowserExtension.Pages 6 | { 7 | public class BasePage : ComponentBase 8 | { 9 | [Inject] 10 | public IWebExtensionsApi WebExtensions { get; set; } 11 | 12 | [Inject] 13 | public ILogger Logger { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/Pages/IndexPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using System; 3 | 4 | namespace Blazor.BrowserExtension.Pages 5 | { 6 | public class IndexPage : BasePage 7 | { 8 | [Inject] 9 | public NavigationManager NavigationManager { get; set; } 10 | 11 | protected override void OnParametersSet() 12 | { 13 | if (TryGetPath(NavigationManager.Uri, out var path)) 14 | { 15 | NavigationManager.NavigateTo(path); 16 | } 17 | base.OnParametersSet(); 18 | } 19 | 20 | private static bool TryGetPath(string url, out string path) 21 | { 22 | path = null; 23 | if (string.IsNullOrEmpty(url)) 24 | { 25 | return false; 26 | } 27 | 28 | var queryStartIndex = url.IndexOf('?'); 29 | if (queryStartIndex == -1) 30 | { 31 | return false; 32 | } 33 | 34 | var query = url[(queryStartIndex + 1)..]; 35 | foreach (var queryParam in query.Split('&')) 36 | { 37 | var keyValue = queryParam.Split('='); 38 | if (keyValue.Length == 2 && keyValue[0].Trim().Equals("path", StringComparison.OrdinalIgnoreCase)) 39 | { 40 | path = Uri.UnescapeDataString(keyValue[1].Trim()); 41 | return !string.IsNullOrEmpty(path); 42 | } 43 | } 44 | 45 | return false; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/ProxyJsRuntime.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.JSInterop; 7 | 8 | namespace Blazor.BrowserExtension 9 | { 10 | internal class ProxyJsRuntime(IJSRuntime instance, BrowserExtensionEnvironment browserExtensionEnvironment) : IJSInProcessRuntime 11 | { 12 | private readonly IJSInProcessRuntime inProcessInstance = instance as IJSInProcessRuntime; 13 | 14 | public ValueTask InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, object[] args) 15 | { 16 | return instance.InvokeAsync(identifier, InterceptArgs(identifier, args)); 17 | } 18 | 19 | public ValueTask InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object[] args) 20 | { 21 | return instance.InvokeAsync(identifier, cancellationToken, InterceptArgs(identifier, args)); 22 | } 23 | 24 | [RequiresUnreferencedCode("JSON serialization and deserialization might require types that cannot be statically analyzed.")] 25 | public TResult Invoke<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TResult>(string identifier, params object[] args) 26 | { 27 | return inProcessInstance.Invoke(identifier, args); 28 | } 29 | 30 | object[] InterceptArgs(string identifier, object[] args) 31 | { 32 | if (identifier == "import") 33 | { 34 | return args?.Select(importArg => importArg is string importPath ? ReplaceImportPath(importPath) : importArg).ToArray(); 35 | } 36 | return args; 37 | } 38 | 39 | string ReplaceImportPath(string importPath) 40 | { 41 | var splitPaths = importPath.Split('/'); 42 | var replacedImportPath = string.Join('/', splitPaths.Select((path, index) => index < splitPaths.Length - 1 && path.StartsWith('_') ? path[1..] : path)); 43 | 44 | if (browserExtensionEnvironment.Mode == BrowserExtensionMode.ContentScript) 45 | { 46 | return Path.Combine(browserExtensionEnvironment.BaseUrl, replacedImportPath.TrimStart('/', '.')); 47 | } 48 | 49 | return replacedImportPath; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/build/Blazor.BrowserExtension.StaticWebAssets.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | 8 | <_BrowserExtension_Package_Contents_Scripts_Directory Condition="'$(_BrowserExtension_Package_Contents_Scripts_Directory)' == ''">$(MSBuildThisFileDirectory)..\content\BrowserExtensionScripts 9 | 10 | 11 | 12 | 13 | Package 14 | Blazor.BrowserExtension 15 | $(_BrowserExtension_Package_Contents_Scripts_Directory) 16 | _content/Blazor.BrowserExtension 17 | %(RecursiveDir)%(Filename)%(Extension) 18 | All 19 | All 20 | Primary 21 | 22 | 23 | JSModule 24 | JSLibraryModule 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/build/Blazor.BrowserExtension.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | true 7 | wwwroot 8 | true 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/build/Blazor.BrowserExtension.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript content files 2 | 3 | All the JavaScript content source files are in the `src` directory. 4 | The bundled file which will be packaged is in the `dist` directory. 5 | 6 | If there are any changes to the source files, the source files must be bundled again. 7 | 1. Open cmd/PowerShell window with this directory as the working directory 8 | 2. Run `npm install` 9 | 3. Run `npm run bundle` 10 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/dist/Blazor.BrowserExtension.lib.module.js: -------------------------------------------------------------------------------- 1 | import { initializeInternal } from './CoreInternal.js'; 2 | 3 | let debugMode = false; 4 | const hasExtensionsApi = (namespace) => typeof globalThis[namespace] == "object" && globalThis[namespace]?.runtime?.id; 5 | if (!hasExtensionsApi("browser") && !hasExtensionsApi("chrome")) { 6 | debugMode = true; 7 | } 8 | 9 | let url; 10 | let browserExtensionMode; 11 | if (!debugMode) { 12 | url = (globalThis.browser || globalThis.chrome).runtime.getURL(""); 13 | browserExtensionMode = "Standard"; 14 | } else { 15 | url = globalThis.location.origin + "/"; 16 | browserExtensionMode = "Debug"; 17 | } 18 | 19 | let config; 20 | let appJs; 21 | 22 | async function beforeStart(options, extensions) { 23 | const configUrl = `${url}_content/browserextension.config.json`; 24 | const configRequest = await fetch(configUrl); 25 | 26 | config = await configRequest.json(); 27 | 28 | const blazorBrowserExtension = options.BlazorBrowserExtension ?? initializeInternal(config, url, browserExtensionMode); 29 | 30 | if (debugMode) { 31 | blazorBrowserExtension.ImportBrowserPolyfill = false; 32 | } 33 | 34 | if (config.HasAppJs) { 35 | appJs = await import(`${url}app.js`); 36 | } 37 | 38 | if (blazorBrowserExtension.ImportBrowserPolyfill) { 39 | // import browser extension API polyfill 40 | // @ts-ignore JS is not a module 41 | await import('./lib/browser-polyfill.min.js'); 42 | } 43 | 44 | await blazorBrowserExtension.BrowserExtension.InitializeCoreAsync(options); 45 | 46 | if (appJs?.beforeStart) { 47 | const beforeStartReturn = appJs.beforeStart(options, extensions, blazorBrowserExtension); 48 | if (beforeStartReturn instanceof Promise) { 49 | await beforeStartReturn; 50 | } 51 | } 52 | } 53 | 54 | function afterStarted(blazor) { 55 | if (appJs?.afterStarted) { 56 | return appJs.afterStarted(blazor); 57 | } 58 | } 59 | 60 | export { afterStarted, beforeStart }; 61 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/dist/ContentScript.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const initializeInternal = (await import('./CoreInternal.js')).initializeInternal; 3 | const url = (globalThis.browser || globalThis.chrome).runtime.getURL(""); 4 | 5 | const configRequest = await fetch(`${url}content/browserextension.config.json`); 6 | const config = await configRequest.json(); 7 | 8 | const blazorBrowserExtension = initializeInternal(config, url, "ContentScript"); 9 | 10 | globalThis.importProxy = (module) => { 11 | if (module.startsWith(document.baseURI) && blazorBrowserExtension.BrowserExtension) { 12 | module = new URL(module.substring(document.baseURI.length), blazorBrowserExtension.BrowserExtension.Url); 13 | } 14 | 15 | return import(module); 16 | }; 17 | 18 | await blazorBrowserExtension.BrowserExtension.InitializeContentScriptAsync({ 19 | BlazorBrowserExtension: blazorBrowserExtension 20 | }); 21 | })(); 22 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/dist/Core.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | console.warn("Core.js is deprecated. Use '_framework/blazor.webassembly.js' in the script tag."); 3 | 4 | let debugMode = false; 5 | const hasExtensionsApi = namespace => typeof globalThis[namespace] == "object" && globalThis[namespace]?.runtime?.id; 6 | if (!hasExtensionsApi("browser") && !hasExtensionsApi("chrome")) { 7 | debugMode = true; 8 | } 9 | 10 | const initializeInternal = (await import('./CoreInternal.js')).initializeInternal; 11 | let url; 12 | let browserExtensionMode; 13 | if (!debugMode) { 14 | url = (globalThis.browser || globalThis.chrome).runtime.getURL(""); 15 | browserExtensionMode = "Standard"; 16 | } else { 17 | url = globalThis.location.origin + "/"; 18 | browserExtensionMode = "Debug"; 19 | } 20 | 21 | const configUrl = `${url}content/browserextension.config.json`; 22 | 23 | const configRequest = await fetch(configUrl); 24 | const config = await configRequest.json(); 25 | 26 | const blazorBrowserExtension = initializeInternal(config, url, browserExtensionMode); 27 | // Clear the BrowserExtension property for the module initializer to initialize it 28 | const browserExtension = blazorBrowserExtension.BrowserExtension; 29 | blazorBrowserExtension.BrowserExtension = null; 30 | 31 | if (debugMode) { 32 | blazorBrowserExtension.ImportBrowserPolyfill = false; 33 | } 34 | 35 | if (config.HasAppJs) { 36 | await import(`${url}app.js`); 37 | } 38 | 39 | if (blazorBrowserExtension.ImportBrowserPolyfill) { 40 | // import browser extension API polyfill 41 | // @ts-ignore JS is not a module 42 | await import('./lib/browser-polyfill.min.js'); 43 | } 44 | 45 | if (blazorBrowserExtension.StartBlazorBrowserExtension) { 46 | await browserExtension.InitializeAsync(); 47 | } 48 | })(); 49 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "checkJs": true 6 | }, 7 | "include": [ "src/*.js", "src/Modules/**/*.js" ], 8 | "exclude": [ "src/lib/*.*" ] 9 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blazor-browser-extension", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "blazor-browser-extension", 8 | "devDependencies": { 9 | "rollup": "^2.70.2" 10 | } 11 | }, 12 | "node_modules/fsevents": { 13 | "version": "2.3.2", 14 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 15 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 16 | "dev": true, 17 | "hasInstallScript": true, 18 | "optional": true, 19 | "os": [ 20 | "darwin" 21 | ], 22 | "engines": { 23 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 24 | } 25 | }, 26 | "node_modules/rollup": { 27 | "version": "2.70.2", 28 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz", 29 | "integrity": "sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==", 30 | "dev": true, 31 | "bin": { 32 | "rollup": "dist/bin/rollup" 33 | }, 34 | "engines": { 35 | "node": ">=10.0.0" 36 | }, 37 | "optionalDependencies": { 38 | "fsevents": "~2.3.2" 39 | } 40 | } 41 | }, 42 | "dependencies": { 43 | "fsevents": { 44 | "version": "2.3.2", 45 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 46 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 47 | "dev": true, 48 | "optional": true 49 | }, 50 | "rollup": { 51 | "version": "2.70.2", 52 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz", 53 | "integrity": "sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==", 54 | "dev": true, 55 | "requires": { 56 | "fsevents": "~2.3.2" 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blazor-browser-extension", 3 | "private": true, 4 | "devDependencies": { 5 | "rollup": "^2.70.2" 6 | }, 7 | "scripts": { 8 | "bundle": "rollup -c" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/rollup-plugins/rollupCleanup.js: -------------------------------------------------------------------------------- 1 | const rollupCleanup = function () { 2 | const singleLineRegex = /\/\*\* *([^*])*\*\/(\n|\r\n)?/.toString(); 3 | const multiLineRegex = / *\/\*\* *([^*]|\*[^/])*\*\/(\n|\r\n)+/.toString(); 4 | const regex = `(${singleLineRegex.substring(1, singleLineRegex.length - 1)})|(${multiLineRegex.substring(1, multiLineRegex.length - 1)})`; 5 | return { 6 | name: "cleanup", 7 | transform: function (/** @type {string} */code) { 8 | return code.replace(new RegExp(regex, "g"), ""); 9 | } 10 | }; 11 | }; 12 | 13 | export default rollupCleanup; 14 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/rollup-plugins/rollupNormalizeLineEndings.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | 4 | const rollupNormalizeLineEndings = function () { 5 | return { 6 | name: "normalizeLineEndings", 7 | writeBundle: function (options, bundle) { 8 | for (const chunk of Object.values(bundle)) { 9 | if (chunk.type !== "chunk") { 10 | continue; 11 | } 12 | const filePath = path.resolve(path.join(options.dir, chunk.fileName)); 13 | let fileContent = fs.readFileSync(filePath, "utf8"); 14 | fileContent = fileContent.replace(/(\r\n|\n)/g, "\r\n"); 15 | fs.writeFileSync(filePath, fileContent, { encoding: "utf8" }); 16 | } 17 | } 18 | }; 19 | }; 20 | 21 | export default rollupNormalizeLineEndings; 22 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/rollup.config.js: -------------------------------------------------------------------------------- 1 | import rollupCleanup from "./rollup-plugins/rollupCleanup.js"; 2 | import rollupNormalizeLineEndings from "./rollup-plugins/rollupNormalizeLineEndings.js"; 3 | 4 | export default [ 5 | { 6 | input: ["src/BackgroundWorkerRunner.js", "src/ContentScript.js", "src/Core.js", "src/Blazor.BrowserExtension.lib.module.js"], 7 | output: { 8 | dir: "dist", 9 | chunkFileNames: "[name].js", 10 | format: "es" // ES module file 11 | }, 12 | external: [ 13 | "./lib/browser-polyfill.min.js", 14 | "../lib/decode.min.js", 15 | "../../JsBind.Net/JsBindNet.js" 16 | ], 17 | plugins: [ 18 | rollupCleanup(), 19 | rollupNormalizeLineEndings() 20 | ] 21 | } 22 | ]; -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Blazor.BrowserExtension.lib.module.js: -------------------------------------------------------------------------------- 1 | import { initializeInternal } from "./Modules/CoreInternal.js"; 2 | 3 | let debugMode = false; 4 | const hasExtensionsApi = (namespace) => typeof globalThis[namespace] == "object" && globalThis[namespace]?.runtime?.id; 5 | if (!hasExtensionsApi("browser") && !hasExtensionsApi("chrome")) { 6 | debugMode = true; 7 | } 8 | 9 | let url; 10 | /** @type {import("./Modules/BrowserExtensionModes.js").BrowserExtensionMode} */ 11 | let browserExtensionMode; 12 | if (!debugMode) { 13 | url = (globalThis.browser || globalThis.chrome).runtime.getURL(""); 14 | browserExtensionMode = "Standard"; 15 | } else { 16 | url = globalThis.location.origin + "/"; 17 | browserExtensionMode = "Debug"; 18 | } 19 | 20 | /** @type {import("./Modules/BrowserExtensionConfig.js").default} */ 21 | let config; 22 | let appJs; 23 | 24 | /** 25 | * Called before Blazor starts. 26 | * @param {object} options Blazor WebAssembly start options. Refer to https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts 27 | * @param {object} extensions Extensions added during publishing 28 | */ 29 | export async function beforeStart(options, extensions) { 30 | const configUrl = `${url}_content/browserextension.config.json`; 31 | const configRequest = await fetch(configUrl); 32 | 33 | /** @type {import("./Modules/BrowserExtensionConfig.js").default} */ 34 | config = await configRequest.json(); 35 | 36 | /** @type {import("./Modules/BlazorBrowserExtension.js").default} */ 37 | const blazorBrowserExtension = options.BlazorBrowserExtension ?? initializeInternal(config, url, browserExtensionMode); 38 | 39 | if (debugMode) { 40 | blazorBrowserExtension.ImportBrowserPolyfill = false; 41 | } 42 | 43 | if (config.HasAppJs) { 44 | appJs = await import(`${url}app.js`); 45 | } 46 | 47 | if (blazorBrowserExtension.ImportBrowserPolyfill) { 48 | // import browser extension API polyfill 49 | // @ts-ignore JS is not a module 50 | await import("./lib/browser-polyfill.min.js"); 51 | } 52 | 53 | await blazorBrowserExtension.BrowserExtension.InitializeCoreAsync(options); 54 | 55 | if (appJs?.beforeStart) { 56 | const beforeStartReturn = appJs.beforeStart(options, extensions, blazorBrowserExtension); 57 | if (beforeStartReturn instanceof Promise) { 58 | await beforeStartReturn; 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Called after Blazor is ready to receive calls from JS. 65 | * @param {any} blazor The Blazor instance 66 | */ 67 | export function afterStarted(blazor) { 68 | if (appJs?.afterStarted) { 69 | return appJs.afterStarted(blazor); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/ContentScript.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const initializeInternal = (await import("./Modules/CoreInternal.js")).initializeInternal; 3 | const url = (globalThis.browser || globalThis.chrome).runtime.getURL(""); 4 | 5 | const configRequest = await fetch(`${url}content/browserextension.config.json`); 6 | /** @type {import("./Modules/BrowserExtensionConfig.js").default} */ 7 | const config = await configRequest.json(); 8 | 9 | const blazorBrowserExtension = initializeInternal(config, url, "ContentScript"); 10 | 11 | globalThis.importProxy = (module) => { 12 | if (module.startsWith(document.baseURI) && blazorBrowserExtension.BrowserExtension) { 13 | module = new URL(module.substring(document.baseURI.length), blazorBrowserExtension.BrowserExtension.Url); 14 | } 15 | 16 | return import(module); 17 | }; 18 | 19 | await blazorBrowserExtension.BrowserExtension.InitializeContentScriptAsync({ 20 | BlazorBrowserExtension: blazorBrowserExtension 21 | }); 22 | })(); 23 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Core.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | console.warn("Core.js is deprecated. Use '_framework/blazor.webassembly.js' in the script tag."); 3 | 4 | let debugMode = false; 5 | const hasExtensionsApi = namespace => typeof globalThis[namespace] == "object" && globalThis[namespace]?.runtime?.id; 6 | if (!hasExtensionsApi("browser") && !hasExtensionsApi("chrome")) { 7 | debugMode = true; 8 | } 9 | 10 | const initializeInternal = (await import("./Modules/CoreInternal.js")).initializeInternal; 11 | let url; 12 | /** @type {import("./Modules/BrowserExtensionModes.js").BrowserExtensionMode} */ 13 | let browserExtensionMode; 14 | if (!debugMode) { 15 | url = (globalThis.browser || globalThis.chrome).runtime.getURL(""); 16 | browserExtensionMode = "Standard"; 17 | } else { 18 | url = globalThis.location.origin + "/"; 19 | browserExtensionMode = "Debug"; 20 | } 21 | 22 | const configUrl = `${url}content/browserextension.config.json`; 23 | 24 | const configRequest = await fetch(configUrl); 25 | /** @type {import("./Modules/BrowserExtensionConfig.js").default} */ 26 | const config = await configRequest.json(); 27 | 28 | const blazorBrowserExtension = initializeInternal(config, url, browserExtensionMode); 29 | // Clear the BrowserExtension property for the module initializer to initialize it 30 | const browserExtension = blazorBrowserExtension.BrowserExtension; 31 | blazorBrowserExtension.BrowserExtension = null; 32 | 33 | if (debugMode) { 34 | blazorBrowserExtension.ImportBrowserPolyfill = false; 35 | } 36 | 37 | if (config.HasAppJs) { 38 | await import(`${url}app.js`); 39 | } 40 | 41 | if (blazorBrowserExtension.ImportBrowserPolyfill) { 42 | // import browser extension API polyfill 43 | // @ts-ignore JS is not a module 44 | await import("./lib/browser-polyfill.min.js"); 45 | } 46 | 47 | if (blazorBrowserExtension.StartBlazorBrowserExtension) { 48 | await browserExtension.InitializeAsync(); 49 | } 50 | })(); 51 | -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Modules/BlazorBrowserExtension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import("./BrowserExtension.js").default} BrowserExtension 3 | * @typedef {import("./BrowserExtensionModes.js").BrowserExtensionModesEnum} BrowserExtensionModesEnum 4 | */ 5 | 6 | export default class BlazorBrowserExtension { 7 | /** @type {boolean} */ 8 | ImportBrowserPolyfill; 9 | /** 10 | * @type {boolean} 11 | * @deprecated 12 | */ 13 | StartBlazorBrowserExtension; 14 | /** @type {BrowserExtensionModesEnum} */ 15 | Modes; 16 | /** @type {BrowserExtension} */ 17 | BrowserExtension; 18 | 19 | constructor() { 20 | this.ImportBrowserPolyfill = true; 21 | this.StartBlazorBrowserExtension = true; 22 | this.Modes = null; 23 | this.BrowserExtension = null; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Modules/BrowserExtensionConfig.js: -------------------------------------------------------------------------------- 1 | export default class BrowserExtensionConfig { 2 | /** @type {string} */ 3 | EnvironmentName = null; 4 | /** @type {boolean} */ 5 | CompressionEnabled = null; 6 | /** @type {boolean} */ 7 | HasAppJs = null; 8 | 9 | constructor() { 10 | this.EnvironmentName = null; 11 | this.CompressionEnabled = null; 12 | this.HasAppJs = null; 13 | } 14 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Modules/BrowserExtensionModes.js: -------------------------------------------------------------------------------- 1 | /** @typedef {"Standard" | "Background" | "ContentScript" | "Debug"} BrowserExtensionMode */ 2 | 3 | /** 4 | * @typedef BrowserExtensionModesEnum 5 | * @property {BrowserExtensionMode} Standard 6 | * @property {BrowserExtensionMode} Background 7 | * @property {BrowserExtensionMode} ContentScript 8 | * @property {BrowserExtensionMode} Debug 9 | */ 10 | 11 | /** 12 | * @type {BrowserExtensionModesEnum} 13 | */ 14 | const BrowserExtensionModes = { 15 | Standard: "Standard", 16 | Background: "Background", 17 | ContentScript: "ContentScript", 18 | Debug: "Debug" 19 | }; 20 | 21 | export default BrowserExtensionModes; -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Modules/CoreInternal.js: -------------------------------------------------------------------------------- 1 | import BrowserExtension from "./BrowserExtension.js"; 2 | import { initializeGlobalVariable } from "./GlobalVariableInitializer.js"; 3 | 4 | /** 5 | * @typedef {import("./BlazorBrowserExtension.js").default} BlazorBrowserExtension 6 | * @typedef {import("./BrowserExtensionModes.js").BrowserExtensionMode} BrowserExtensionMode 7 | * @typedef {import("./BrowserExtensionConfig.js").default} BrowserExtensionConfig 8 | */ 9 | 10 | /** 11 | * Initializes the Blazor Browser Extension internally 12 | * @param {BrowserExtensionConfig} config The initialization options. 13 | * @param {string} browserExtensionUrl The browser extension url. 14 | * @param {BrowserExtensionMode} browserExtensionMode The browser extension mode. 15 | * @returns {BlazorBrowserExtension} 16 | */ 17 | export function initializeInternal(config, browserExtensionUrl, browserExtensionMode) { 18 | const browserExtension = new BrowserExtension(browserExtensionUrl, browserExtensionMode, config); 19 | return initializeGlobalVariable(browserExtension); 20 | } -------------------------------------------------------------------------------- /src/Blazor.BrowserExtension/content/src/Modules/GlobalVariableInitializer.js: -------------------------------------------------------------------------------- 1 | import BlazorBrowserExtension from "./BlazorBrowserExtension.js"; 2 | import BrowserExtensionModes from "./BrowserExtensionModes.js"; 3 | 4 | /** 5 | * @typedef {import("./BrowserExtension.js").default} BrowserExtension 6 | */ 7 | 8 | /** 9 | * Initializes the Blazor Browser Extension global variable 10 | * @param {BrowserExtension} browserExtension The browser extension. 11 | * @returns {BlazorBrowserExtension} 12 | */ 13 | export function initializeGlobalVariable(browserExtension) { 14 | /** @type {BlazorBrowserExtension} */ 15 | let blazorBrowserExtension; 16 | 17 | // initialize global property BlazorBrowserExtension 18 | if (!globalThis.hasOwnProperty("BlazorBrowserExtension")) { 19 | blazorBrowserExtension = new BlazorBrowserExtension(); 20 | blazorBrowserExtension.Modes = BrowserExtensionModes; 21 | globalThis.BlazorBrowserExtension = blazorBrowserExtension; 22 | } else { 23 | blazorBrowserExtension = /** @type {BlazorBrowserExtension} */(globalThis.BlazorBrowserExtension); 24 | } 25 | 26 | if (blazorBrowserExtension.BrowserExtension) { 27 | // Extensions execution should be isolated so this property should be null upon initialization. 28 | throw new Error("Browser extension cannot be loaded."); 29 | } 30 | 31 | blazorBrowserExtension.BrowserExtension = browserExtension; 32 | 33 | return blazorBrowserExtension; 34 | } -------------------------------------------------------------------------------- /src/Icon/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/src/Icon/Icon.png -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Blazor.BrowserExtension.Analyzer.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | latest 6 | enable 7 | enable 8 | 9 | false 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/TestFiles/BackgroundWorkerGeneratedTemplate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Blazor.BrowserExtension.Analyzer.Test 4 | { 5 | public partial class BackgroundWorker 6 | { 7 | protected override Dictionary JsInstance => new() 8 | { 9 | /* JsInstance_Placeholder */ 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/TestFiles/BackgroundWorkerTemplate.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace Blazor.BrowserExtension.Analyzer.Test 7 | { 8 | public partial class BackgroundWorker : BackgroundWorkerBase 9 | { 10 | [BackgroundWorkerMain] 11 | public override void Main() 12 | { 13 | /* Main_Method_Body_Placeholder */ 14 | } 15 | 16 | /* Members_Placeholder */ 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Tests/ArrayTest.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Test.Tests 2 | { 3 | public class ArrayTest : BaseBackgroundSourceGeneratorTest 4 | { 5 | protected override string MainMethodBody => 6 | """ 7 | var items1 = new string[] 8 | { 9 | "Item1", 10 | "Item2", 11 | "Item3" 12 | }; 13 | string[] items2 = new[] 14 | { 15 | "Item4" 16 | }; 17 | object[] items3 = 18 | [ 19 | true, 20 | 123, 21 | 123.45, 22 | 123.45m 23 | ]; 24 | """; 25 | 26 | protected override string ExpectedBackgroundWorkerJs => 27 | """ 28 | let items1 = [ 29 | "Item1", 30 | "Item2", 31 | "Item3" 32 | ]; 33 | let items2 = [ 34 | "Item4" 35 | ]; 36 | let items3 = [ 37 | true, 38 | 123, 39 | 123.45, 40 | 123.45 41 | ]; 42 | """; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Tests/DelegateTest.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Test.Tests 2 | { 3 | public class DelegateTest : BaseBackgroundSourceGeneratorTest 4 | { 5 | protected override string MainMethodBody => 6 | """ 7 | Console.WriteLine(OnInstalled); 8 | WebExtensions.Runtime.OnInstalled.AddListener(OnInstalled); 9 | """; 10 | 11 | protected override string BackgroundClassMembers => 12 | """ 13 | Task OnInstalled() => throw new NotImplementedException(); 14 | """; 15 | 16 | protected override string ExpectedJsInstance => 17 | """ 18 | { "OnInstalled", (object)OnInstalled }, 19 | { "OnInstalled0", (System.Delegate)OnInstalled }, 20 | """; 21 | 22 | protected override string ExpectedBackgroundWorkerJs => 23 | """ 24 | console.log(fromReference('OnInstalled')); 25 | browser.runtime.onInstalled.addListener(fromReference('OnInstalled0')); 26 | """; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Tests/JsAccessPathTest.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Test.Tests 2 | { 3 | public class JsAccessPathTest : BaseBackgroundSourceGeneratorTest 4 | { 5 | protected override string MainMethodBody => 6 | """ 7 | Console.WriteLine(WebExtensions.Runtime.OnInstalled); 8 | """; 9 | 10 | protected override string ExpectedBackgroundWorkerJs => 11 | """ 12 | console.log(browser.runtime.onInstalled); 13 | """; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Tests/NestedObjectArrayTest.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Test.Tests 2 | { 3 | public class NestedObjectArrayTest : BaseBackgroundSourceGeneratorTest 4 | { 5 | protected override string MainMethodBody => 6 | """ 7 | var nestedObject = new 8 | { 9 | NestedObject1 = new 10 | { 11 | StringProperty = "Test", 12 | BoolProperty = true 13 | }, 14 | NestedObject2 = new { 15 | IntProperty = 123, 16 | DoubleProperty = 123.45, 17 | DecimalProperty = 123.45m 18 | }, 19 | NestedArray = new object[] 20 | { 21 | "StringItem", 22 | true 23 | } 24 | }; 25 | var nestedArray = new object[] 26 | { 27 | "Item1", 28 | new 29 | { 30 | Property = "Value" 31 | } 32 | }; 33 | """; 34 | 35 | protected override string ExpectedBackgroundWorkerJs => 36 | """ 37 | let nestedObject = { 38 | NestedObject1: { 39 | StringProperty: "Test", 40 | BoolProperty: true 41 | }, 42 | NestedObject2: { 43 | IntProperty: 123, 44 | DoubleProperty: 123.45, 45 | DecimalProperty: 123.45 46 | }, 47 | NestedArray: [ 48 | "StringItem", 49 | true 50 | ] 51 | }; 52 | let nestedArray = [ 53 | "Item1", 54 | { 55 | Property: "Value" 56 | } 57 | ]; 58 | """; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Tests/ObjectTest.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Test.Tests 2 | { 3 | public class ObjectTest : BaseBackgroundSourceGeneratorTest 4 | { 5 | protected override string MainMethodBody => 6 | """ 7 | var object1 = new 8 | { 9 | StringProperty = "Test", 10 | BoolProperty = true, 11 | IntProperty = 123, 12 | DoubleProperty = 123.45, 13 | DecimalProperty = 123.45 14 | }; 15 | var object2 = new WebExtensions.Net.WebRequest.RequestFilter() 16 | { 17 | Incognito = true 18 | }; 19 | """; 20 | 21 | protected override string ExpectedBackgroundWorkerJs => 22 | """ 23 | let object1 = { 24 | StringProperty: "Test", 25 | BoolProperty: true, 26 | IntProperty: 123, 27 | DoubleProperty: 123.45, 28 | DecimalProperty: 123.45 29 | }; 30 | let object2 = { 31 | incognito: true 32 | }; 33 | """; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Analyzer.Test/Tests/SystemTranslationTest.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.Analyzer.Test.Tests 2 | { 3 | public class SystemTranslationTest : BaseBackgroundSourceGeneratorTest 4 | { 5 | protected override string MainMethodBody => 6 | """ 7 | Console.WriteLine("WriteLine"); 8 | Console.Write("Write"); 9 | Logger.LogTrace("Trace"); 10 | Logger.LogDebug("Debug"); 11 | Logger.LogInformation("Information"); 12 | Logger.LogWarning("Warning"); 13 | Logger.LogError("Error"); 14 | Logger.LogCritical("Critical"); 15 | """; 16 | 17 | protected override string ExpectedBackgroundWorkerJs => 18 | """ 19 | console.log("WriteLine"); 20 | console.log("Write"); 21 | console.trace("Trace"); 22 | console.debug("Debug"); 23 | console.log("Information"); 24 | console.warn("Warning"); 25 | console.error("Error"); 26 | console.error("Critical"); 27 | """; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Blazor.BrowserExtension.Build.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | latest 6 | true 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Helpers/CommandHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Blazor.BrowserExtension.Build.Test.Helpers 4 | { 5 | public static class CommandHelper 6 | { 7 | public static string ExecuteCommand(string command, string arguments, string workingDirectory = null) 8 | => ExecuteCommandInternal(command, arguments, workingDirectory, true); 9 | 10 | public static void ExecuteCommandVoid(string command, string arguments, string workingDirectory = null) 11 | => ExecuteCommandInternal(command, arguments, workingDirectory, false); 12 | 13 | private static string ExecuteCommandInternal(string command, string arguments, string workingDirectory, bool readOutput) 14 | { 15 | var processStartInfo = new ProcessStartInfo(command, arguments) 16 | { 17 | WorkingDirectory = workingDirectory, 18 | RedirectStandardError = true, 19 | RedirectStandardOutput = true, 20 | CreateNoWindow = false, 21 | }; 22 | 23 | var process = new Process() 24 | { 25 | StartInfo = processStartInfo 26 | }; 27 | 28 | process.Start(); 29 | process.WaitForExit(TimeSpan.FromMinutes(2)); 30 | 31 | if (!process.HasExited || process.ExitCode != 0) 32 | { 33 | var output = process.StandardOutput.ReadToEnd(); 34 | var error = process.StandardError.ReadToEnd(); 35 | var commandDetails = $"'{command}{arguments?.PadLeft(arguments.Length + 1)}'{(string.IsNullOrEmpty(workingDirectory) ? null : $" in '{workingDirectory}'")}"; 36 | 37 | if (!process.HasExited) 38 | { 39 | throw new Exception($"Command {commandDetails} timed out. Error output: '{error}'. Standard output: '{output}'"); 40 | } 41 | else if (process.ExitCode != 0) 42 | { 43 | throw new Exception($"Command {commandDetails} exited with code {process.ExitCode}. Error output: '{error}'. Standard output: '{output}'"); 44 | } 45 | } 46 | 47 | if (readOutput) 48 | { 49 | return process.StandardOutput.ReadToEnd(); 50 | } 51 | 52 | return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Tasks/Bootstrap/BaseFileBootstrapperTest.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Bootstrap; 2 | using Xunit; 3 | 4 | namespace Blazor.BrowserExtension.Build.Test.Tasks.Bootstrap 5 | { 6 | public abstract class BaseFileBootstrapperTest 7 | where TBootstrapper : IFileBootstrapper, new() 8 | { 9 | protected abstract string OriginalFileContent { get; } 10 | protected abstract string BootstrappedFileContent { get; } 11 | 12 | [Fact] 13 | public void TestBootstrap() 14 | { 15 | // Arrange 16 | var bootstrapper = new TBootstrapper(); 17 | var fileLines = OriginalFileContent.Split(Environment.NewLine).ToList(); 18 | var expectedFileLines = BootstrappedFileContent.Split(Environment.NewLine).ToList(); 19 | 20 | // Act 21 | var isUpdated = bootstrapper.Bootstrap(fileLines); 22 | 23 | // Assert 24 | isUpdated.ShouldBeTrue(); 25 | fileLines.ShouldBeEquivalentTo(expectedFileLines); 26 | } 27 | 28 | [Fact] 29 | public void TestBootstrapWithBootstrappedFile() 30 | { 31 | // Arrange 32 | var bootstrapper = new TBootstrapper(); 33 | var fileLines = BootstrappedFileContent.Split(Environment.NewLine).ToList(); 34 | var expectedFileLines = BootstrappedFileContent.Split(Environment.NewLine).ToList(); 35 | 36 | // Act 37 | var isUpdated = bootstrapper.Bootstrap(fileLines); 38 | 39 | // Assert 40 | isUpdated.ShouldBeFalse(); 41 | fileLines.ShouldBeEquivalentTo(expectedFileLines); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Tasks/Bootstrap/ImportsRazorFileBootstrapperTest.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Bootstrap; 2 | 3 | namespace Blazor.BrowserExtension.Build.Test.Tasks.Bootstrap 4 | { 5 | public class ImportsRazorFileBootstrapperTest : BaseFileBootstrapperTest 6 | { 7 | protected override string OriginalFileContent => """ 8 | @using Microsoft.AspNetCore.Components.Forms 9 | @using Microsoft.AspNetCore.Components.Routing 10 | @using Microsoft.AspNetCore.Components.Web 11 | 12 | """; 13 | 14 | protected override string BootstrappedFileContent => """ 15 | @using Microsoft.AspNetCore.Components.Forms 16 | @using Microsoft.AspNetCore.Components.Routing 17 | @using Microsoft.AspNetCore.Components.Web 18 | @using Blazor.BrowserExtension.Pages 19 | 20 | """; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Tasks/Bootstrap/IndexRazorFileBootstrapperTest.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Bootstrap; 2 | 3 | namespace Blazor.BrowserExtension.Build.Test.Tasks.Bootstrap 4 | { 5 | public class IndexRazorFileBootstrapperTest : BaseFileBootstrapperTest 6 | { 7 | protected override string OriginalFileContent => """ 8 | @page "/" 9 | 10 |

Hello, from Blazor.

11 | """; 12 | 13 | protected override string BootstrappedFileContent => """ 14 | @page "/index.html" 15 | @inherits IndexPage 16 | 17 |

Hello, from Blazor.

18 | """; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Tasks/Bootstrap/ProgramCsFileBootstrapperTest.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Bootstrap; 2 | 3 | namespace Blazor.BrowserExtension.Build.Test.Tasks.Bootstrap 4 | { 5 | public class ProgramCsFileBootstrapperTest : BaseFileBootstrapperTest 6 | { 7 | protected override string OriginalFileContent => """ 8 | using System; 9 | 10 | public static class Program 11 | { 12 | public static async Task Main(string[] args) 13 | { 14 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 15 | builder.RootComponents.Add("#app"); 16 | builder.RootComponents.Add("head::after"); 17 | 18 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 19 | await builder.Build().RunAsync(); 20 | } 21 | } 22 | """; 23 | 24 | protected override string BootstrappedFileContent => """ 25 | using Blazor.BrowserExtension; 26 | using System; 27 | 28 | public static class Program 29 | { 30 | public static async Task Main(string[] args) 31 | { 32 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 33 | 34 | builder.UseBrowserExtension(browserExtension => 35 | { 36 | if (browserExtension.Mode == BrowserExtensionMode.Background) 37 | { 38 | builder.RootComponents.AddBackgroundWorker(); 39 | } 40 | else 41 | { 42 | builder.RootComponents.Add("#app"); 43 | builder.RootComponents.Add("head::after"); 44 | } 45 | }); 46 | 47 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 48 | await builder.Build().RunAsync(); 49 | } 50 | } 51 | """; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/Tasks/Bootstrap/ProjectFileBootstrapperTest.cs: -------------------------------------------------------------------------------- 1 | using Blazor.BrowserExtension.Build.Tasks.Bootstrap; 2 | 3 | namespace Blazor.BrowserExtension.Build.Test.Tasks.Bootstrap 4 | { 5 | public class ProjectFileBootstrapperTest : BaseFileBootstrapperTest 6 | { 7 | protected override string OriginalFileContent => """ 8 | 9 | 10 | 11 | net9.0 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | """; 22 | 23 | protected override string BootstrappedFileContent => """ 24 | 25 | 26 | 27 | net9.0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | """; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | https://api.nuget.org/v3/index.json; 6 | $(MSBuildThisFileDirectory)/../../../src/PackageOutput 7 | 8 | $(MSBuildThisFileDirectory)/../../../PackagesCache 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/Directory.Build.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/EmptyBlazorProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | @Body 5 |
6 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |

Hello, world!

4 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using EmptyBlazorProject; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | builder.RootComponents.Add("#app"); 7 | builder.RootComponents.Add("head::after"); 8 | 9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 10 | 11 | await builder.Build().RunAsync(); 12 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using Microsoft.AspNetCore.Components.Web 5 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 6 | @using Microsoft.JSInterop 7 | @using EmptyBlazorProject 8 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.Build.Test/TestProjects/EmptyBlazorProject/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EmptyBlazorProject 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 |
Loading...
16 | 17 |
18 | An unhandled error has occurred. 19 | Reload 20 | 🗙 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/BackgroundWorker.cs: -------------------------------------------------------------------------------- 1 | namespace Blazor.BrowserExtension.IntegrationTest 2 | { 3 | public partial class BackgroundWorker : BackgroundWorkerBase 4 | { 5 | [BackgroundWorkerMain] 6 | public override void Main() 7 | { 8 | WebExtensions.Runtime.OnInstalled.AddListener(OnInstalled); 9 | } 10 | 11 | async Task OnInstalled() 12 | { 13 | var indexPageUrl = WebExtensions.Runtime.GetURL("index.html"); 14 | await WebExtensions.Tabs.Create(new() 15 | { 16 | Url = indexPageUrl 17 | }); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Blazor.BrowserExtension.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | latest 6 | enable 7 | enable 8 | <_BrowserExtensionBuildOutputFolder>bin\$(Configuration) 9 | 10 | 11 | 12 | 13 | <_BrowserExtension_Package_Contents_Scripts_Directory>$(MSBuildThisFileDirectory)..\..\src\Blazor.BrowserExtension\content\dist 14 | 15 | 16 | 17 | 18 | Package 19 | Blazor.BrowserExtension 20 | $(MSBuildThisFileDirectory)..\..\src\Blazor.BrowserExtension\content\src\lib 21 | _content/Blazor.BrowserExtension/lib 22 | %(RecursiveDir)%(Filename)%(Extension) 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 |
10 | About 11 |
12 | 13 |
14 | @Body 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | min-width: 600px; 6 | min-height: 400px; 7 | } 8 | 9 | .main { 10 | flex: 1; 11 | } 12 | 13 | .sidebar { 14 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 15 | } 16 | 17 | .top-row { 18 | background-color: #f7f7f7; 19 | border-bottom: 1px solid #d6d5d5; 20 | justify-content: flex-end; 21 | height: 3.5rem; 22 | display: flex; 23 | align-items: center; 24 | } 25 | 26 | .top-row ::deep a, .top-row .btn-link { 27 | white-space: nowrap; 28 | margin-left: 1.5rem; 29 | } 30 | 31 | .top-row a:first-child { 32 | overflow: hidden; 33 | text-overflow: ellipsis; 34 | } 35 | 36 | @media (max-width: 640.98px) { 37 | .top-row:not(.auth) { 38 | display: none; 39 | } 40 | 41 | .top-row.auth { 42 | justify-content: space-between; 43 | } 44 | 45 | .top-row a, .top-row .btn-link { 46 | margin-left: 0; 47 | } 48 | } 49 | 50 | @media (min-width: 641px) { 51 | .page { 52 | flex-direction: row; 53 | } 54 | 55 | .sidebar { 56 | width: 250px; 57 | height: 100vh; 58 | position: sticky; 59 | top: 0; 60 | } 61 | 62 | .top-row { 63 | position: sticky; 64 | top: 0; 65 | z-index: 1; 66 | } 67 | 68 | .main > div { 69 | padding-left: 2rem !important; 70 | padding-right: 1.5rem !important; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  7 | 8 |
9 | 26 |
27 | 28 | @code { 29 | private bool collapseNavMenu = true; 30 | 31 | private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; 32 | 33 | private void ToggleNavMenu() 34 | { 35 | collapseNavMenu = !collapseNavMenu; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Pages/ContentScript.razor: -------------------------------------------------------------------------------- 1 | @page "/contentscript.html" 2 | @inherits Blazor.BrowserExtension.Pages.BasePage 3 |

ContentScript

4 | 5 | @code { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 |

Counter

4 | 5 |

Current count: @currentCount

6 | 7 | 8 | 9 | @code { 10 | private int currentCount = 0; 11 | 12 | private void IncrementCount() 13 | { 14 | currentCount++; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | @inject HttpClient Http 3 | 4 |

Weather forecast

5 | 6 |

This component demonstrates fetching data from the server.

7 | 8 | @if (forecasts == null) 9 | { 10 |

Loading...

11 | } 12 | else 13 | { 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @foreach (var forecast in forecasts) 25 | { 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | 34 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
35 | } 36 | 37 | @code { 38 | private WeatherForecast[] forecasts; 39 | 40 | protected override async Task OnInitializedAsync() 41 | { 42 | forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); 43 | } 44 | 45 | public class WeatherForecast 46 | { 47 | public DateTime Date { get; set; } 48 | 49 | public int TemperatureC { get; set; } 50 | 51 | public string Summary { get; set; } 52 | 53 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @page "/index.html" 3 | @inherits Blazor.BrowserExtension.Pages.IndexPage 4 | 5 |

Index

6 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Pages/Options.razor: -------------------------------------------------------------------------------- 1 | @page "/options" 2 | @page "/options.html" 3 | @inherits Blazor.BrowserExtension.Pages.BasePage 4 |

Options

5 | 6 | @code { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Pages/Popup.razor: -------------------------------------------------------------------------------- 1 | @page "/popup" 2 | @page "/popup.html" 3 | @inherits Blazor.BrowserExtension.Pages.BasePage 4 |

Popup

5 | 6 | @code { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using Blazor.BrowserExtension; 4 | using Blazor.BrowserExtension.IntegrationTest; 5 | using Blazor.BrowserExtension.IntegrationTest.Pages; 6 | 7 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 8 | builder.UseBrowserExtension(browserExtension => 9 | { 10 | if (browserExtension.Mode == BrowserExtensionMode.ContentScript) 11 | { 12 | builder.RootComponents.Add("#Blazor_BrowserExtension_IntegrationTest_app"); 13 | } 14 | else if (browserExtension.Mode == BrowserExtensionMode.Background) 15 | { 16 | builder.RootComponents.AddBackgroundWorker(); 17 | } 18 | else 19 | { 20 | builder.RootComponents.Add("#app"); 21 | builder.RootComponents.Add("head::after"); 22 | } 23 | }); 24 | 25 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 26 | 27 | await builder.Build().RunAsync(); 28 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51669", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Blazor.BrowserExtension.IntegrationTest": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": "true", 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using Blazor.BrowserExtension.IntegrationTest 10 | @using Blazor.BrowserExtension.IntegrationTest.Layout 11 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Called before Blazor starts. 3 | * @param {object} options Blazor WebAssembly start options. Refer to https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts 4 | * @param {object} extensions Extensions added during publishing 5 | * @param {object} blazorBrowserExtension Blazor browser extension 6 | */ 7 | export function beforeStart(options, extensions, blazorBrowserExtension) { 8 | if (blazorBrowserExtension.BrowserExtension.Mode === blazorBrowserExtension.Modes.ContentScript) { 9 | const appDiv = document.createElement("div"); 10 | appDiv.id = "Blazor_BrowserExtension_IntegrationTest_app"; 11 | document.body.appendChild(appDiv); 12 | } 13 | } -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/test/Blazor.BrowserExtension.IntegrationTest/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/test/Blazor.BrowserExtension.IntegrationTest/wwwroot/favicon.ico -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingyaulee/Blazor.BrowserExtension/02b05273b525b4e6de74b70a033278b70cdb6e82/test/Blazor.BrowserExtension.IntegrationTest/wwwroot/favicon.png -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Blazor.BrowserExtension.IntegrationTest 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 | An unhandled error has occurred. 26 | Reload 27 | 🗙 28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Blazor.BrowserExtension Integration Test", 4 | "description": "A browser extension for integration test of Blazor.BrowserExtension", 5 | "version": "1.0", 6 | "background": { 7 | "service_worker": "content/BackgroundWorker.js", 8 | "type": "module" 9 | }, 10 | "action": { 11 | "default_popup": "popup.html" 12 | }, 13 | "options_ui": { 14 | "page": "options.html", 15 | "open_in_tab": true 16 | }, 17 | "content_scripts": [ 18 | { 19 | "matches": [ "*://*/*" ], 20 | "js": [ "content/Blazor.BrowserExtension/ContentScript.js" ] 21 | } 22 | ], 23 | "content_security_policy": { 24 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 25 | }, 26 | "web_accessible_resources": [ 27 | { 28 | "resources": [ 29 | "framework/*", 30 | "content/*", 31 | "app.js" 32 | ], 33 | "matches": [ "" ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/manifestv3.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Blazor.BrowserExtension Integration Test", 4 | "description": "A browser extension for integration test of Blazor.BrowserExtension", 5 | "version": "1.0", 6 | "background": { 7 | "service_worker": "content/BackgroundWorker.js", 8 | "type": "module" 9 | }, 10 | "action": { 11 | "default_popup": "popup.html" 12 | }, 13 | "options_ui": { 14 | "page": "options.html", 15 | "open_in_tab": true 16 | }, 17 | "content_scripts": [ 18 | { 19 | "matches": [ "*://*/*" ], 20 | "js": [ "content/Blazor.BrowserExtension/ContentScript.js" ] 21 | } 22 | ], 23 | "content_security_policy": { 24 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 25 | }, 26 | "web_accessible_resources": [ 27 | { 28 | "resources": [ 29 | "framework/*", 30 | "content/*", 31 | "app.js" 32 | ], 33 | "matches": [ "" ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTest/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2018-05-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2018-05-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2018-05-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2018-05-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2018-05-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTestRunner/Blazor.BrowserExtension.IntegrationTestRunner.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | latest 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTestRunner/IntegrationTestManifestV3.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Blazor.BrowserExtension.IntegrationTestRunner 4 | { 5 | public class IntegrationTestManifestV3 : BaseIntegrationTest 6 | { 7 | public IntegrationTestManifestV3(Fixture fixture) : base(fixture) 8 | { 9 | fixture.Initialize(); 10 | } 11 | 12 | protected override void SetupBeforeInitialize() 13 | { 14 | var extensionPath = Fixture.GetExtensionPath(); 15 | File.Copy(Path.Combine(extensionPath, "manifestv3.json"), Path.Combine(extensionPath, "manifest.json"), true); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTestRunner/OrderAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Blazor.BrowserExtension.IntegrationTestRunner 4 | { 5 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 6 | public class OrderAttribute : Attribute 7 | { 8 | public OrderAttribute(int order) 9 | { 10 | Order = order; 11 | } 12 | public int Order { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTestRunner/TestOrderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit.Abstractions; 4 | using Xunit.Sdk; 5 | 6 | namespace Blazor.BrowserExtension.IntegrationTestRunner 7 | { 8 | public class TestOrderer : ITestCaseOrderer 9 | { 10 | public IEnumerable OrderTestCases(IEnumerable testCases) where TTestCase : ITestCase 11 | { 12 | return testCases.OrderBy(testCase => 13 | { 14 | var attributeInfo = testCase.TestMethod.Method.GetCustomAttributes(typeof(OrderAttribute)).FirstOrDefault(); 15 | return attributeInfo?.GetNamedArgument(nameof(OrderAttribute.Order)) ?? 0; 16 | }); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Blazor.BrowserExtension.IntegrationTestRunner/WebDriverHelper.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace Blazor.BrowserExtension.IntegrationTestRunner 6 | { 7 | public class WebDriverHelper 8 | { 9 | public readonly WebDriver WebDriver; 10 | public string CurrentUrl => WebDriver.Url; 11 | public string ExtensionBaseUrl { get; private set; } 12 | 13 | public WebDriverHelper(WebDriver webDriver) 14 | { 15 | WebDriver = webDriver; 16 | } 17 | 18 | public Task NavigateToUrl(string url) 19 | { 20 | WebDriver.Url = url; 21 | return Retry(() => (bool)WebDriver.ExecuteScript("return document.readyState === \"complete\";"), TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); 22 | } 23 | 24 | public void SetExtensionBaseUrl(string url) 25 | { 26 | ExtensionBaseUrl = url.Substring(0, url.LastIndexOf("/") + 1); 27 | } 28 | 29 | public string GetExtensionUrl(string path) 30 | { 31 | return $"{ExtensionBaseUrl}index.html?path={path}"; 32 | } 33 | 34 | public async Task GetPageContent(bool isContentScript = false) 35 | { 36 | var appId = isContentScript ? "#Blazor_BrowserExtension_IntegrationTest_app" : "#app"; 37 | await Retry( 38 | () => (bool)WebDriver.ExecuteScript($"""return document.querySelector("{appId} h3") != null;"""), 39 | TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(20)); 40 | return WebDriver.FindElement(By.CssSelector($"{appId} h3"))?.Text; 41 | } 42 | 43 | public async Task Retry(Func action, TimeSpan interval, TimeSpan timeout) 44 | { 45 | var retryCount = timeout.TotalMilliseconds / interval.TotalMilliseconds; 46 | while (retryCount > 0) 47 | { 48 | retryCount--; 49 | if (action()) 50 | { 51 | return true; 52 | } 53 | await Task.Delay(interval); 54 | } 55 | return false; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tool/DependenciesUpdater.ps1: -------------------------------------------------------------------------------- 1 | $relativeLibPath = "../src/Blazor.BrowserExtension/content/src/lib" 2 | $scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition 3 | $libPath = Resolve-Path "$scriptPath/$relativeLibPath" 4 | 5 | If (-Not(Test-Path -Path $libPath)) { 6 | Throw "Library path does not exist: $libPath" 7 | } 8 | 9 | Function DownloadFile { 10 | param ( 11 | $baseUrl, 12 | $fileName 13 | ) 14 | Invoke-RestMethod -Uri "$baseUrl/$fileName" -OutFile "$libPath/$fileName" 15 | } 16 | 17 | Function UpdateBrowserPolyfill { 18 | $baseUrl = "https://unpkg.com" 19 | $request = [System.Net.WebRequest]::Create("$baseUrl/webextension-polyfill") 20 | $request.AllowAutoRedirect = $false 21 | $response = $request.GetResponse() 22 | 23 | If ($response.StatusCode -eq "Found") { 24 | $versionRelativeUrl = $response.GetResponseHeader("Location") 25 | $versionUrl = "$baseUrl$versionRelativeUrl/dist" 26 | DownloadFile -baseUrl $versionUrl -fileName "browser-polyfill.js" 27 | DownloadFile -baseUrl $versionUrl -fileName "browser-polyfill.js.map" 28 | DownloadFile -baseUrl $versionUrl -fileName "browser-polyfill.min.js" 29 | DownloadFile -baseUrl $versionUrl -fileName "browser-polyfill.min.js.map" 30 | } Else { 31 | Throw "Unable to get the latest version of webextension-polyfill." 32 | } 33 | } 34 | 35 | Function UpdateBrotliDecode { 36 | $baseUrl = "https://raw.githubusercontent.com/google/brotli/master/js" 37 | DownloadFile -baseUrl $baseUrl -fileName "decode.js" 38 | DownloadFile -baseUrl $baseUrl -fileName "decode.min.js" 39 | } 40 | 41 | UpdateBrowserPolyfill 42 | UpdateBrotliDecode --------------------------------------------------------------------------------