Chrome extensions in WebView2, CefSharp, and DotNetBrowser
Tags: dotnet, cefsharp, webview, dotnetbrowser
Published: 2023-04-18 08:38:15

It's 2023, and everything is in the browser. Web technologies are remarkably versatile, and we—desktop developers—envy that versatility. 

That's where web view controls come to help. And there are plenty of them in the .NET world: open-source and proprietary, free and commercial.

With embeddable Gecko and Trident retired, all modern resorted to Chromium. It complies with the latest web standards, works fast, and has a million features. But there is one feature all web views struggle with. It's the Chrome extensions.

In this article, I will examine how well web views support Chrome extensions and review their API.

## TL;DR

1. CefSharp's implementation is minimal, and most extensions don't work. It will change when they switch from the old Alloy to the new Chrome runtime.

2. DotNetBrowser's implementation is functional, but not yet stable.

3. WebView2 doesn't support the extensions thus far.

## Today's contestants

As you already noticed, I'm going to look at three libraries: 

* CefSharp
* WebView2
* DotNetBrowser

CefSharp uses Chromium Embedded Framework. This framework, usually abbreviated as CEF, is a fundamental piece of the ecosystem as every open-source web view is essentially a wrapper over it. The capabilities of CEF define what CefSharp and other similar libraries can do.

WebView2 and DotNetBrowser are created and maintained by commercial companies and represent the proprietary end of the spectrum.

## WebView2

WebView2 doesn't support Chrome extensions. But since it uses Microsoft Edge—whose extensions are fully compatible with Chrome's—it may start supporting them in the future.

Follow these GitHub issues to get updates on WebView2 progress:

* [Add Ons or Extensions with WebView2 (WebView2Feedback#98)](https://github.com/MicrosoftEdge/WebView2Feedback/issues/98)
* [Ability to install extensions (WebView2Feedback#191)](https://github.com/MicrosoftEdge/WebView2Feedback/issues/191)
* [Support for UI-less extensions (WebView2Feedback#3258)](https://github.com/MicrosoftEdge/WebView2Feedback/issues/3258)


## DotNetBrowser

DotNetBrowser released a [public preview of the extension support](https://teamdev.com/dotnetbrowser/blog/chrome-extensions-in-dotnetbrowser/?utm_campaign=extensions-preview&utm_medium=article&utm_source=devto). We have yet to finalize the exact list of available browser APIs, but ultimately, we expect most of the extensions to work.

Here are the extensions that work in DotNetBrowser: uBlock and AdBlock, Windows Accounts, Web Vitals, and React Developer Tools. We tested these, but you can expect similar extensions to work too.

If you want to know if a specific extension works, mention it in a [GitHub discussion](https://github.com/TeamDev-IP/DotNetBrowser-Examples/discussions/141).

### API review

#### Installing and uninstalling extensions

In DotNetBrowser, you install extensions permanently. An installed extension is persisted in the profile and remains available until you explicitly uninstall it.

Before installation, grant the permissions first:

```cs
var extensions = engine.Profiles.Default.Extensions;
extensions.ValidatePermissionsHandler =
    new Handler<ValidatePermissionsParameters, ValidatePermissionsResponse>(
        p => ValidatePermissionsResponse.Grant()
    );
```

Then, install the extension with a Chrome Web Store link or a CRX file:

```cs
var extension = extensions.Install("https://chrome.google.com/webstore/detail/…").Result;
// or
var extension = extensions.Install("C:\\extension.crx").Result;
```

To uninstall the extension, execute the following code:

```cs
extension.Uninstall();
```

#### Executing the extension action

Every extension has an icon in the Chrome toolbar. When it's clicked, the extension may execute [the action](https://developer.chrome.com/docs/extensions/reference/action/). For example, it can show a pop-up, change the page, or do anything else. 

Here's how you trigger this action from the code:

```cs
// The action will interact with this `browser`.
extension.GetAction(browser).Click();
```

If the action opens a pop-up, the library will show it in a new window. Alternatively, you can leave the pop-up off the screen and manipulate it from the code: 

```cs
browser.OpenExtensionActionPopupHandler =
    new Handler<OpenExtensionActionPopupParameters, OpenExtensionActionPopupResponse>(p =>
    {
        p.PopupBrowser.Navigation.FrameLoadFinished += (s, e) =>
        {
            e.Frame.GetElementById("switch").Click();
        };
        return OpenExtensionActionPopupResponse.Open();
    });
```

#### Handling extension events

Show or suppress pop-up windows:

```cs
extension.OpenExtensionPopupHandler = 
    new Handler<OpenExtensionPopupParameters, OpenExtensionPopupResponse>
        (p => OpenExtensionPopupResponse.Suppress());
```


Handle downloads started by an extension:

```cs
extension.StartDownloadHandler = 
    new Handler<StartDownloadParameters, StartDownloadResponse>
        (p => StartDownloadResponse.DownloadTo("C:\\Downloads"));
```

Monitor changes in the extension action badge, tooltip, and icon:

```cs
var action = extension.GetAction(browser);
action.Updated += (s, p) =>
{
    var badge = p.ExtensionAction.Badge;
    var icon = p.ExtensionAction.Icon;
    var tooltip = p.ExtensionAction.Tooltip;
};
```

## CefSharp

CefSharp supports the extensions, but most of them will not work. This is because extensions need [special browser APIs](https://developer.chrome.com/docs/extensions/reference/), of which there are over 70. And CEF has only 4—a small set required for a built-in PDF viewer.

At this moment, [further work is discontinued](https://github.com/chromiumembedded/cef/issues/1947#issuecomment-1465003373). After introducing the new integration layer (called Chrome runtime), they decided not to proceed with extensions in the old integration layer (named Alloy). We expect most extensions to work in the new integration layer as the library starts to rely on Chromium's code much more.

So, do extensions work or not? Not yet. The Chrome runtime is incompatible with CefSharp and CefGlue kind of libraries—it's a work in progress. Track these issues to check on the progress:

* [Chrome Runtime discussion](https://github.com/cefsharp/CefSharp/discussions/4123) in CefSharp repository.
* [Support chrome windows with CEF callbacks (cef#2969)](https://github.com/chromiumembedded/cef/issues/2969) 

### API review

#### Loading and unloading extensions

Unlike Chromium, CEF doesn't persist extensions: you load them each time the application starts. To load the extension, have it unzipped in the directory and execute the following code:

```cs
var context = Browser.GetBrowserHost().RequestContext;
context.LoadExtension("C:\\dir", null, new ExtensionHandler());
```

To unload the extension, find it in RequestContext and unload:

```cs
context.GetExtension("gibkoahgjfhphbmeiphbcnhehbfdlcgo").Unload();
```

#### Handling extension events

Extensions emit events: when loaded or unloaded, wants to create a new browser or access the active browser. These are all handled in [`IExtensionHandler`](https://cefsharp.github.io/api/75.1.x/html/T_CefSharp_IExtensionHandler.htm), which you associate with the extension on loading. 

Though, until CefSharp adopts the Chrome runtime, most event handlers may be never called.

Here's a short example to get the gist of the API:

```cs
public class ExtensionHandler : IExtensionHandler
{
    ...
    // Don't allow extensions to create background browsers.
    bool IExtensionHandler.OnBeforeBackgroundBrowser(IExtension extension, string url, IBrowserSettings settings)
    {  
        return true;
    }
 
    // Let the extension know which browser is active now.
    IBrowser IExtensionHandler.GetActiveBrowser(IExtension extension, IBrowser browser, bool includeIncognito)
    { 
        return mainBrowser;
    }

    // Don't allow extensions to access browsers that have a private page
    // loaded.
    bool IExtensionHandler.CanAccessBrowser(IExtension extension, IBrowser browser, bool includeIncognito, IBrowser targetBrowser)
    {
        var url = targetBrowser.MainFrame.Url;
        if (url.Contains("my-private-page"))
        {
            return false;
        }
        return true;
    }
}
```

## Useful reading

* [Support Chrome windows with CEF callbacks](https://github.com/chromiumembedded/cef/issues/2969). The original issue behind the Chrome runtime in CEF.
* [Add support for Chrome extensions](https://github.com/chromiumembedded/cef/issues/1947). The original issue for extensions support in CEF.