General Usage - kiewic/CefSharp GitHub Wiki
This guide introduces the general concepts involved when developing an application using CefSharp
. It's important to remember that CefSharp
is a simple .Net wrapper around the Chromium Embedded Framework (CEF). CEF is an open source project based on the Google Chromium project. Unlike the Chromium project itself, which focuses mainly on Google Chrome application development, CEF focuses on facilitating embedded browser use cases in third-party applications.
CEF
is based on the multi-process Chromium Content API and as a result only a subset of the features that exist in Chromium
are currently available. For example, support for extensions is not yet avaliable (It may be in the future, check the CEF Issue Tracker
as there is an open issue regarding support).
The CEF
project has its own site https://bitbucket.org/chromiumembedded/cef/overview and support forum http://magpcss.org/ceforum/index.php if you're dealing with a low level problem or your question is pretty generic you can search or post on the forum. Be sure to mention which CEF
version you're using, CefSharp
versions look like 51.0.0
, whilst CEF
versions are like 3.2840.1493
. Open about:chrome
or check your packages.config
to easily determine the version. Post on ceforum
before opening an issue on the CEF Issue Tracker
.
It's important to remember that CefSharp
is limited by the API
that CEF
exposes, and even then not all of the CEF API
is currently implemented. The C headers
are available at https://bitbucket.org/chromiumembedded/cef/src/607d420baf2fb4f9ea2f12c4270c9df448a93b43/include/?at=master you should look through those if you're chasing a feature. The http://magpcss.org/ceforum/apidocs3/index-all.html is outdated, though still relevant in a lot of cases. If there is a piece of the CEF API
that is not currently exposed, then you can implement it yourself and submit a PR
for inclusion in the main project.
This document is based on https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md
CefSharp
provides three different flavors, WinForms
, WPF
and OffScreen
. The WPF
and OffScreen
versions use the OffScreen Rendering(OSR)
rendering mode. In OSR
mode each frame is rendered to a buffer and then either drawn on the screen as in the case of WPF
or available as a Bitmap
in the OffScreen
. All versions use the CefSharp
and CefSharp.Core
libraries, as a result much of the API
is used exactly the same within all three flavors. This limits code duplication and reduces the maintenance burden of adding a new feature, the only downside is the WPF
version is not quite as WPF
friendly as it could be (you can subclass the ChromiumWebBrowser
class and implement any missing parts in your application that are required). Also you can host the WinForms
version within WPF
using the WindowsFormsHost
, this may be required to get around some limitations of the WPF
version (CEF
has yet to implement full touch screen support in the OSR
mode, there is an open issue on the CEF Issue Tracker
, if this is something you require, get involved there).
- Release Notes
- Software Requirements
- Need to Know/Limitations
- Examples
- Logging
- Processes
- Threads
- Initialize and Shutdown
- CefSettings and BrowserSettings
- IBrowser, IFrame and IBrowserHost
- Handlers
- File URI (file:///)
- Proxy Resolution
- Request Context (Browser Isolation)
- Printing
- High DPI Displays/Support
- MultiThreadedMessageLoop
- Popups
- JavaScript Integration
- Adobe Flash Player (Pepper Flash)
- Offscreen Rendering (OSR)
- UserAgent
- DevTools
- Screenshots
- Win32 Out of Memory
- Load URL with PostData
- Spellchecking
- WebAssembly
- Exception Handling
- Dependency Checking
- Multimedia (Audio/Video)
- OnScreen (Virtual) Keyboard
Release notes are available for each version at https://github.com/cefsharp/CefSharp/releases please take the time to read over them if you're having problems or curious about what's changed. Check the Known Issues section if you're having problems and there are usually notes that contain useful info about a release.
CefSharp
uses Visual C++(VC++
) to interface with the underlying native C++ API, as a result it will only run on Windows. (There is no Windows APP Store version). CefSharp
releases on every second Chromium
version, e.g. 47, 49, 51
Each CefSharp
release has it's own branch, see https://github.com/cefsharp/CefSharp#release-branches for details and requirements for each branch are listed there. Google
recently removed support for older operating systems like Windows XP, Vista and their server counterparts. If you require your app to run on these operating systems then check the releases for more details https://github.com/cefsharp/CefSharp/releases
CefSharp requires:
- Microsoft
.Net 4.5.2
or greater -
Microsoft Visual C++ Redistributable Package (either
x86
orx64
depending on your application). To determine which versions ofVisual C++
you require see https://github.com/cefsharp/CefSharp#release-branches
Notes:
- You can package the
VC++ Redist Dll's
with your application see https://github.com/cefsharp/CefSharp/wiki/Frequently-asked-questions#Including_vcredist for details - The Microsoft .NET Framework 4.5.2 Developer Pack for Visual Studio 2012 and Visual Studio 2013 is available here: https://www.microsoft.com/en-gb/download/details.aspx?id=42637
Newer versions now support targeting AnyCPU
, see https://github.com/cefsharp/CefSharp/issues/1714 for details on how to implement this. The same technique can be used to move libcef.dll
, etc to a different folder or common location on disk.
- Specifying a
CachePath
is required for persistence of cookies, saving of passwords, etc, anIn-Memory
cache is used by default (similar toIncogneto
). See Initialize and Shutdown section below for an example of Initializing CEF with aCachePath
below. - Not currently possible to
Clear Cache
see http://magpcss.org/ceforum/viewtopic.php?f=6&t=11992 for official response. You can define a different cache perRequestContext
, seeRequestContext
section of this document. - Add an
app.manifest
to your app forHiDPI
support, app compatibility (running onWindows 10
) and tooltips inWinForms
. The examples contain sampleapp.manifest
files. This is very important (http://magpcss.org/ceforum/viewtopic.php?f=6&t=14721) - An error in the logs similar to
Check failed: fallback_available == base::win::GetVersion() > base::win::VERSION_WIN8 (1 vs. 0)
is a sign your application needs aapp.manifest
with the relevantcompatibility
entries. -
WPF
version lacks touch support (not yet implement inCEF
), you can use theWindowsFormsHost
to host theWinForms
version inWPF
, you'll see a performance benefit also. -
CEF
can only beInitialized/Shutdown
once per process, see the section below for full details, this is a limitation of the underlyingChromium
framework. -
Minimal
designer support was added for bothWPF
andWinForms
in version57.0.0
, see #1989 (WPF) and #1946 (WinForms) for details. Designer support requires you targetx86
(In theoryAnyCPU
should also work, not yet tested).Visual Studio
isx86
so you cannot use thex64
version. For older versions there is no designer support (designer will throw an exception). - Only runs in the default
AppDomain
, there are some workarounds like those at https://github.com/flole/CefSharp.AppDomain and https://github.com/stever/AppHostCefSharp - Due to limited resources only a single version is supported at a time, see https://github.com/cefsharp/CefSharp#release-branches to see which version is current. If you're using an older version and encounter a problem you'll have to upgrade to the current supported version.
- Only runs on
Windows
and noApp Store
version. - There is no
.Net Core
version -Microsoft
has started implementingC++/CLI
as part of.Net Core
you can track the issue https://github.com/dotnet/coreclr/issues/18013 They are only adding support forWindows
initially. See also https://github.com/dotnet/coreclr/issues/659 -
Sandboxing
has not been implemented as it's technically infeasible to add support directly intoCefSharp
, see #697 for details. -
WinForms
on screen keyboard potentially can benefit fromdisable-usb-keyboard-detect
command line argument https://github.com/cefsharp/CefSharp/issues/1691#issuecomment-323603277 - Prevent browser from scaling on
High DPI
usesettings.CefCommandLineArgs.Add("force-device-scale-factor", "1");
command line flag. -
WPF
users withHigh DPI
monitors are recommended to install.Net 4.6
on their target machines, as there is a bug in the.Net Framework
that can potentially cause aMILERR_WIN32ERROR Exception
see #2035 for details -
CEF
does not currently supportPNaCl
which is required to loadGoogle Earth
see http://magpcss.org/ceforum/viewtopic.php?f=6&t=15761
The CefSharp
source code contains examples of many different features. There is also the MinimalExample
project which uses the latest Nuget
packages to provide very simple Browser
implementations. The MinimalExample
is the best place to get started, download this project and get it running for a base reference to make sure everything works on your system.
https://github.com/cefsharp/CefSharp.MinimalExample
By default CEF
maintains its own log file ('Debug.log') in your application's executing folder e.g. bin
. To disable logging change settings.LogSeverity
, and to change the file name/path use settings.LogFile
.
When debugging a problem, the first place to check is this log file as it contains low level Chromium
messages. If you see errors or warnings then search on http://magpcss.org/ceforum/index.php and https://bitbucket.org/chromiumembedded/cef/issues?status=new&status=open
CEF
runs using multiple processes. The main process which handles window creation, painting and network access is called the browser
process. This is generally the same process as the host application and the majority of the application logic will run in the browser process. Blink rendering and JavaScript execution occur in a separate render
process. Some application logic, such as JavaScript bindings, will also run in the render process. The default process model will spawn a new render process for each unique origin (scheme + domain). Other processes will be spawned as needed, such as “plugin” processes to handle plugins like Flash and “gpu” processes to handle accelerated compositing.
By default CefSharp
comes with a default implementation of the render
process called CefSharp.BrowserSubProcess.exe
. This process will be spawned multiple times to represent separate processes as described above. It is possible as of version 51.0.0
to provide your own custom BrowserSubProcess
, as the executable is now a very simple wrapper around the underlying VC++
implementation.
CEF uses multiple threads for different levels of processing. The browser
process for example contains the following commonly-referenced threads:
-
UI thread is the main thread in the browser process. By default
CefSharp
usessetting.MultiThreadedMessageLoop = true
so theCEF UI
thread is different to your main application thread - IO thread is used in the browser process to process IPC and network messages
- FILE thread is used in the browser process to interact with the file system
- RENDERER thread is the main thread in the renderer process
Can only called Initialize
once per process (application). Running multiple instances of your app is possible, you'll need to provide unique CachePath
for each instance, see CefSettings
section below.
See Request Context (Browser Isolation) for details on how to change settings at runtime, isolate browser instances, set different cache paths for different instances.
It's important to note that it's necessary to initialize the underlying CEF
library. This can be achieved in one of two ways, explicitly and implicitly. When you create a new instance of ChromiumWebBrowser
it will check if CEF has been initialized and if not, initialize it for you with the defaults. For those wishing to specify some custom settings then you can explicitly initialize CEF
yourself like below:
public static void Init()
{
var settings = new CefSettings();
// Increase the log severity so CEF outputs detailed information, useful for debugging
settings.LogSeverity = LogSeverity.Verbose;
// By default CEF uses an in memory cache, to save cached data e.g. passwords you need to specify a cache path
// NOTE: The executing user must have sufficient privileges to write to this folder.
settings.CachePath = "cache";
Cef.Initialize(settings);
}
For Cef.Shutdown the WinForms and WPF instances of ChromiumWebBrowser
the relevant Application Exit event is hooked and Cef.Shutdown() called by default. Set CefSharpSettings.ShutdownOnExit
= false; to disable this behavior. This value needs to be set before the first instance of ChromiumWebBrowser
is created as the event handlers are hooked in the static constructor for the ChromiumWebBrowser
class.
It's important to note CEF that Initialize
/Shutdown
MUST be called on your main application thread (typically the UI thread). If you call them on different threads, your application will hang.
An example of calling Initialize
/Shutdown
manually using WinForms
, the same can be applied to WPF
and console applications that use the CefSharp.OffScreen
package (The OffScreen
example at https://github.com/cefsharp/CefSharp.MinimalExample is an excellent place to start, there is also one in the main project repository that's a little more advanced).
public class Program
{
[STAThread]
public static void Main()
{
//For Windows 7 and above, best to include relevant app.manifest entries as well
Cef.EnableHighDPISupport();
//We're going to manually call Cef.Shutdown below, this maybe required in some complex scenarios
CefSharpSettings.ShutdownOnExit = false;
//Perform dependency check to make sure all relevant resources are in our output directory.
Cef.Initialize(new CefSettings(), performDependencyCheck: true, browserProcessHandler: null);
var browser = new BrowserForm();
Application.Run(browser);
//Shutdown before your application exists or it will hang.
Cef.Shutdown();
}
}
In summary
-
Cef.Initialize
andCef.Shutdown
can only called Initialize once per process(application). There is no way around this, so only callShutdown
when you're finished usingCefSharp
. -
Cef.Initialize
andCef.Shutdown
must be called on the same thread. -
Cef.Initialize
will be called for you implicitly if you create a newChromiumWebBrowser
instance and haven't already calledCef.Initialize
. - For the WinForms and WPF instances of
ChromiumWebBrowser
the relevant Application Exit event is hooked andCef.Shutdown()
called by default. SetCefSharpSettings.ShutdownOnExit = false;
to disable this behavior. This value needs to be set before the first instance ofChromiumWebBrowser
is created as the event handlers are hooked in the static constructor for theChromiumWebBrowser
class. - In
CefSharp.OffScreen
you must explicitly callCef.Shutdown()
before your application exists or it will hang.
The CefSettings
structure allows configuration of application-wide CEF settings. Some commonly configured members include:
-
BrowserSubprocessPath
The path to a separate executable that will be launched for sub-processes. Typically there is no need to change this. -
MultiThreadedMessageLoop
the default is True inCefSharp
, though it is possible to integrateCEF
into your apps existing message loop see https://github.com/cefsharp/CefSharp/issues/1748 -
CommandLineArgsDisabled
Set to true to disable configuration of browser process features using standard CEF and Chromium command-line arguments. See the “Command Line Arguments” section for more information. -
CachePath
The location where cache data will be stored on disk. If empty an in-memory cache will be used for some features and a temporary disk cache will be used for others. HTML5 databases such as localStorage will only persist across sessions if a cache path is specified. -
Locale
The locale string that will be passed to Blink. If empty the default locale of "en-US" will be used. Also configurable using the "lang" command-line switch. Change this to set the Context menu language as well. -
LogFile
The directory and file name to use for the debug log. If empty, the default name of "debug.log" will be used and the file will be written to the application directory. Also configurable using the "log-file" command-line switch. -
LogSeverity
The log severity. Only messages of this severity level or higher will be logged. Also configurable using the "log-severity" command-line switch with a value of "verbose", "info", "warning", "error", "error-report" or "disable". -
ResourcesDirPath
The fully qualified path for the resources directory. If this value is empty the cef.pak and/or devtools_resources.pak files must be located in the module directory. Also configurable using the "resources-dir-path" command-line switch. -
LocalesDirPath
The fully qualified path for the locales directory. If this value is empty the locales directory must be located in the module directory. This value is ignored on Mac OS X where pack files are always loaded from the app bundle Resources directory. Also configurable using the "locales-dir-path" command-line switch. -
RemoteDebuggingPort
Set to a value between 1024 and 65535 to enable remote debugging on the specified port. For example, if 8080 is specified the remote debugging URL will be http://localhost:8080. CEF can be remotely debugged from any CEF or Chrome browser window. Also configurable using the "remote-debugging-port" command-line switch.
There are many settings and command line arguments that can influence the way CEF behaves. Here are some examples:
public static void Init()
{
// Specify Global Settings and Command Line Arguments
var settings = new CefSettings();
// By default CEF uses an in memory cache, to save cached data e.g. passwords you need to specify a cache path
// NOTE: The executing user must have sufficient privileges to write to this folder.
settings.CachePath = "cache";
// There are many command line arguments that can either be turned on or off
// Enable WebRTC
settings.CefCommandLineArgs.Add("enable-media-stream", "1");
//Disable GPU Acceleration
settings.CefCommandLineArgs.Add("disable-gpu", "1");
// Don't use a proxy server, always make direct connections. Overrides any other proxy server flags that are passed.
// Slightly improves Cef initialize time as it won't attempt to resolve a proxy
settings.CefCommandLineArgs.Add("no-proxy-server", "1");
Cef.Initialize(settings);
}
There are some settings which can be applied to a specific ChromiumWebBrowser
instance. If you're using WPF
you should be able to set BrowserSettings
in XAML
(been a long time since I tried it, so there is no example, please contribute one if you get it working).
var browser = new ChromiumWebBrowser(url)
{
BrowserSettings =
{
DefaultEncoding = "UTF-8",
WebGl = CefState.Disabled
}
};
The IBrowser
and IFrame
objects are used for sending commands to the browser and for retrieving state information in callback methods. Each IBrowser
object will have a single main IFrame
object representing the top-level frame and zero or more IFrame
objects representing sub-frames.
For example, a browser that loads two HTML <iframe>
s will have three IFrame
objects (the top-level frame and the two <iframe>
s).
To load a URL in the browser main frame:
browser.MainFrame.LoadUrl(someurl);
CefSharp
provides many extension methods to make executing common tasks easier. See WebBrowserExtensions
for the source of these methods and to get a better understanding of how common tasks are performed.
IBrowserHost
represents the more low level browser methods.
CefSharp
provides some events for convenience like the following (See IWebBrowser source/doc for all common events and detailed information on their usage):
ConsoleMessage
StatusMessage
FrameLoadStart
FrameLoadEnd
LoadError
LoadingStateChanged
These are simple events that expose a small percentage of the underlying handlers that CEF
provides. These events are only called for the main browser, for popup handling you can access the notifications using IDisplayHandler
and ILoadHandler
.
To determine when a page has finished loading I recommend using LoadingStateChanged
over FrameLoadEnd
. It's important to remember that finished loading is different to finished rendering. There is currently no method of determining when a web page has finished rendering (and unlikely ever will be as with features like flash, dynamic content, animations, even simple tasks like moving your mouse or scrolling will cause new frames to be rendered).
IDialogHandler
, IDisplayHandler
, IDownloadHandler
, IContextMenuHandler
, ILifeSpanHandler
, ILoadHandler
and IRequestHandler
are some of the more common handlers (see the source/API doc for the rest). These simply
wrap the underlying CEF handlers in a convenient .NET fashion. For example CEF's CefDownloadHandler
is IDownloadHandler
in CefSharp
. Implementing these handlers will provide you access to the underlying events and callbacks that
are the foundation of CEF. A number of handlers' members can be executed in an async fashion using a callback. All the handlers follow a consistent pattern: those that return a bool
are asking you whether you'd like to handle this yourself. If no, then return false
for the default action. Return true
if you will handle it yourself.
They are basic interfaces which you implement and then assign to your ChromiumWebBrowser
instance. e.g.
browser.DownloadHandler = new DownloadHandler();
Ideally you should set handlers immediately after your ChromiumWebBrowser
instances have been instantiated. See the Example projects in the source for more detailed examples, there are currently no default implementations available so you have to implement every method. (If you wish to contribute default implementations then submit a pull request).
Some general notes about the handlers
- IDownloadHandler needs to be implemented to allow downloading of files, progress notifications, pause, cancel, etc
- IRequestHandler is for dealing with navigation, redirects, resource load notifications etc
- IDialogHandler is for file dialog notifications
- IDisplayHandler is for address change, status message, console message, fullscreen mode change notifications (and more)
- ILoadHandler is for load status some of these are mapped to events, use this for popups
- ILifeSpanHandler is for dealing with popups and close events
- IKeyboardHandler is for keyboard events
- IJsDialogHandler is for javascript message boxes/popups
- IDragHandler is for drag start
- IContextMenuHandler is for customising the context menu
- IFocusHandler is for focus related notifications
- IResourceHandlerFactory is for intercepting resource requests see RequestHandler section
- IGeolocationHandler is for gelocation requests
-
IRenderProcessMessageHandler is for custom
CefSharp
messages sent from the render process - IFindHandler is for find notifications
It is possible to modify the response using a ResponseFilter
. See section below.
CEF supports two approaches for handling network requests inside of an application. The scheme handler approach allows registration of a handler for requests targeting a particular origin (scheme + domain). The request interception approach allows handling of arbitrary requests at the application's discretion.
Use the HTTP(S) scheme instead of a custom scheme to avoid a range of potential issues.
If you choose to use a custom scheme (anything other than http://
, https://
, etc) you must register it with CEF so that it will behave as expected. If you would like your custom scheme to behave similar to HTTP (support POST requests and enforce HTTP access control (CORS) restrictions) then it should be registered as a "standard" scheme. If you are planning to perform cross-origin requests to other schemes or send POST requests via XMLHttpRequest
to your scheme handler then you should use the HTTP scheme instead of a custom scheme to avoid potential issues. IsSecure
and IsCorsEnabled
params were added recently
For examples, search the project source for RegisterScheme
.
Handlers can be used with both built-in schemes (http://
, https://
, etc) and custom schemes. When using a built-in scheme choose a domain name unique to your application (like myapp
or internal
). Implement the ISchemeHandlerFactory
and IResourceHandler
classes to handle the request and provide response data. See ResourceHandler
for the default implementation of IResourceHandler
, which has lots of useful static helper methods.
Handlers can be used with both built-in schemes (HTTP, HTTPS, etc) and custom schemes. When using a built-in scheme choose a domain name unique to your application (like “myapp” or “internal”). Implement the ISchemeHandlerFactory
and IResourceHandler
classes to handle the request and provide response data.
A scheme handler is registered via the CefSettings.RegisterScheme
function. For example, you can register a handler for “localfolder://cefsharp/” requests (there is another example below and there are working examples in the project source):
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = "localfolder",
DomainName = "cefsharp",
SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\..\CefSharp.Example\Resources",
hostName: "cefsharp", //Optional param no hostname/domain checking if null
defaultPage: "home.html") //Optional param will default to index.html
});
The FolderSchemeHandlerFactory
is a simple default implementation for reading files from disk using a scheme handler. You can use either a custom scheme (In other words, you can provide a URL in the form customscheme://folder/yourfile
) or a standard scheme (https://
, https://
).
An example of implementing your own factory might look like:
public class CefSharpSchemeHandlerFactory : ISchemeHandlerFactory
{
public const string SchemeName = "custom";
private static readonly IDictionary<string, string> ResourceDictionary;
static CefSharpSchemeHandlerFactory()
{
ResourceDictionary = new Dictionary<string, string>
{
{ "/home.html", Resources.home_html },
{ "/bootstrap/bootstrap.min.css", Resources.bootstrap_min_css },
{ "/bootstrap/bootstrap.min.js", Resources.bootstrap_min_js },
{ "/BindingTest.html", Resources.BindingTest },
{ "/ExceptionTest.html", Resources.ExceptionTest },
{ "/PopupTest.html", Resources.PopupTest },
{ "/SchemeTest.html", Resources.SchemeTest }
};
}
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
//Notes:
// - The 'host' portion is entirely ignored by this scheme handler.
// - If you register a ISchemeHandlerFactory for http/https schemes you should also specify a domain name
// - Avoid doing lots of processing in this method as it will affect performance.
// - Uses the Default ResourceHandler implementation
var uri = new Uri(request.Url);
var fileName = uri.AbsolutePath;
string resource;
if (ResourceDictionary.TryGetValue(fileName, out resource) && !string.IsNullOrEmpty(resource))
{
var fileExtension = Path.GetExtension(fileName);
return ResourceHandler.FromString(resource, fileExtension);
}
return null;
}
}
The ResourceHandler
is provided as a default implementation of IResourceHandler
and contains many static helper methods for creating classes. A few examples are below,
ResourceHandler.FromStream(stream, mimeType);
ResourceHandler.FromString(htmlString, includePreamble:true, mimeType:ResourceHandler.GetMimeType(fileExtension));
ResourceHandler.FromFilePath("CefSharp.Core.xml", mimeType);
Finally, you have to register this scheme handler using some code like this:
public static void Init()
{
// Pseudo code; you probably need more in your CefSettings also.
var settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = "custom",
SchemeHandlerFactory = new CefSharpSchemeHandlerFactory()
});
Cef.Initialize(settings);
}
It's important that the scheme registration takes place before Cef.Initialize()
is called.
IRequestHandlerFactory.GetResourceHandler()
supports the interception of arbitrary requests. It uses the same IResourceHandler
class as the scheme handler approach. The DefaultResourceHandlerFactory
class is used internally and can be used as a reference for implementing your own if required.
The IWebBrowser.RegisterResourceHandler
and IWebBrowser.UnRegisterResourceHandler
extension methods provide a simple means of providing an IResourceHandler
for a given Url
without having to implement your own factory.
You could for example request a fictitious URL and provide a response just as if the site was real.
Both ISchemeHandlerFactory
handlers and IResourceHandlerFactory
use the IResourceHandler
interface to represent the response (stream + headers + status codes, etc). There is a default implementation of IResourceHandler
which is simply ResourceHandler
. This class contains many static methods for convenience like FromStream
, FromFile
and FromString
, to simplify your life. Overriding ResourceHandler
will give you more control of how the underlying Stream
and response is populated, this is useful for async
operations. See the examples for how to implement ProcessRequestAsync
. If you require complete control then implement IResourceHandler
, however in most cases this is not necessary.
The source contains examples of both ISchemeHandlerFactory
and IResourceHandlerFactory
implementations.
IRequestHandler.GetResourceResponseFilter()
supports filtering of data received in response to requests. You can retrieve the raw response data, you can append data to a response, like injecting some custom CSS at the end of a file. You can rewrite a response if required. Can be used to receive the response of any request, AJAX(XHRHttpRequest
)/POST/GET.
See the example for some basic implementations of IResourceFilter
. This is a relatively new feature and quite complex to implement. Make sure you read over and debug the existing examples before asking any questions.
The basic workflow for obtaining raw data is
- Provide filter in
IRequestHandler.GetResourceResponseFilter()
- Store
IResponseFilter
usingIRequest.Identifier
as key (store in dictionary etc.) - Copy data to some sort of buffer/stream/etc
- Access data in
IRequestHandler.OnResourceLoadComplete
based onIRequest.Identifier
as key
Commit demonstrates a very simple example where the response data for the example projects custom scheme is made avalaible.
There are a few extension methods provided as a convenience.
-
LoadHtml(this IWebBrowser browser, string html, bool base64Encode = false)
: Load a data encoded Uri -
LoadHtml(this IWebBrowser browser, string html, string url)
: Register a ResourceHandler with theDefaultResourceHandlerFactory
and calls Load -
RegisterResourceHandler(this IWebBrowser browser, string url, Stream stream, string mimeType = ResourceHandler.DefaultMimeType)
: Register a resource handler with theDefaultResourceHandlerFactory
-
UnRegisterResourceHandler(this IWebBrowser browser, string url)
: Unregister a resource handler with theDefaultResourceHandlerFactory
For more information on data:
encoded URI, which contains the body of the request in the URI iteself see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
Generating your own Data URI
would look something like:
const string html = "<html><head><title>Test</title></head><body><h1>Html Encoded in URL!</h1></body></html>";
var base64EncodedHtml = Convert.ToBase64String(Encoding.UTF8.GetBytes(html));
browser.Load("data:text/html;base64," + base64EncodedHtml);
I strongly advise against using file:///
when loading from local disk. Different security restrictions apply and there are many limitations. I'd suggest using a Scheme
handler or implementing your own IResourceHandlerFactory
. (Loading a data:
encoded URI is also pretty handy, specially for the OffScreen
project).
If you choose to ignore this advice you'll have to resolve any issues you have using file:///
yourself. ceforum
is the best resource.
Proxy settings are configured in CEF using the same command-line flags as Google Chrome. Proxy settings can be changed at run time using the Preferences feature.
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-proxy-resolution
If the proxy requires authentication the IRequestHandler.GetAuthCredentials()
callback will be executed with an isProxy
value of true
to retrieve the username and password.
Examples of setting proxy using Preferences
in CefSharp
are available at http://stackoverflow.com/questions/36095566/cefsharp-3-set-proxy-at-runtime
Setting preferences must be done on the CEF UI
thread (see above for threading info). It is possible to specify the proxy settings on a per Request Context
basis (see below).
You can isolate browser instances, including providing custom cache paths, different proxy settings, different cookie managers, and many other things using a RequestContext
. In newer versions loading of PPAPI plugins is governed at the RequestContext
level. In CEF
terms the underlying class is CefRequestContext
.
Here are some key points:
- By default a Global Request Context will be used (settings shared by all browsers)
- You can change some (not all) settings at run time using
Preferences
- Don't use the command line argument if you wish to change the value using
SetPreference
-
WinForms
: set theRequestContext
immediately after you create your browser instance -
OffScreen
: PassRequestContext
into the constructor -
WPF
: Set in yourControl
/Window
constructor afterInitializeComponent()
is called - Plugin load notifications are handled through the
IRequestContextHandler
interface - Set
RequestContextSettings.CachePath
to persist cookies, data, localStorage, etc
//WinForms Examples - WPF and OffScreen are similar, see notes above.
//Default implementation of RequestContext
//Default settings will be used, this means an in-memory cache (no data persisted)
browser = new ChromiumWebBrowser();
browser.RequestContext = new RequestContext();
//CustomRequestContextHanler needs to implement `IRequestContextHandler`
//Default settings will be used, this means an in-memory cache (no data persisted)
browser = new ChromiumWebBrowser();
browser.RequestContext = new RequestContext(new CustomRequestContextHandler());
//Custom settings and CustomRequestContextHandler
//Use the specified cache path (if empty, in memory cache will be used). To share the global
//browser cache and related configuration set this value to match the CefSettings.CachePath
//value.
var requestContextSettings = new RequestContextSettings { CachePath = cachePath };
browser = new ChromiumWebBrowser();
browser.RequestContext = new RequestContext(requestContextSettings, new CustomRequestContextHandler());
See the project source for more detailed examples.
string errorMessage;
//You can set most preferences using a `.` notation rather than having to create a complex set of dictionaries.
//The default is true, you can change to false to disable
context.SetPreference("webkit.webprefs.plugins_enabled", true, out errorMessage);
The CEF API only exposes limited support for printing. There is currently no support for printing in Kiosk Mode (printing to the default without a dialog). The suggested workaround is to print to PDF
then use a 3rd party
application to print the PDF
.
If you need better printing support then you should discuss that on ceforum
. There are already open discussions and an open issue on the CEF Issue Tracker.
- http://magpcss.org/ceforum/viewtopic.php?f=7&t=14196
- https://bitbucket.org/chromiumembedded/cef/issues/1283/adding-print-options-to-cef3
- http://magpcss.org/ceforum/viewtopic.php?f=6&t=12567&p=27604
To enable High DPI support you'll need to add the relevant entries to your app.manifest
to let Windows know your app is High DPI Aware. The relevant MSDN article is https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx it's also recommended that you read https://docs.microsoft.com/en-us/windows/desktop/sbscs/application-manifests for details on how to set the entries in the app.manifest
It's a very long MSDN article, but it's necessary reading if your app needs to be run on high DPI displays.
Note If you experience black boxes with rendering/resizing then your app likely needs changes to support High DPI
Prevent browser from scaling on High DPI use settings.CefCommandLineArgs.Add("force-device-scale-factor", "1");
command line flag.
Add the relevant app.manifest
entries and call Cef.EnableHighDPISupport()
. It's best make that the first call in your application entry point.
The commit where support was added to the CefSharp.MinimalExample.WinForms
project is https://github.com/cefsharp/CefSharp.MinimalExample/commit/1197f37035549ad98c7682399c12e40be677426a. Check it out for a working example.
Add the relevant app.manifest
entries. It should roughly look like the following (You need to add some xmlns entries to the top level root element, see https://github.com/cefsharp/CefSharp/blob/cefsharp/63/CefSharp.Wpf.Example/app.manifest for a working example)
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true/PM</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
Add the relevant app.manifest
entries (see above for an example)
CefSharp
by default uses setting.MultiThreadedMessageLoop = true
. This enables your application to get up and running very quickly, there are some important things to note and this may not be suitable for everyone. It is configurable.
- Uses a different thread for the message pump.
- The CEF UI thread is different to your application's UI thread, which can cause some disconnects in message processing. One example is opening a menu and clicking within the browser control with the menu staying open.
- The WinForms control will not receive any of the standard mouse/keyboard events you'd typically see.
It is possible to integrate CEF into your app's existing message loop (see https://github.com/cefsharp/CefSharp/issues/1748). Typically this would only be required for WinForms developers, as most of the WPF integration is forwarding WPF events to CEF, so they're basically proxied. There are working examples of integrating into your applications message loop in the source.
You can hook the message loop whilst using MultiThreadedMessageLoop
, though this is quite complex. The project source contains an example at https://github.com/cefsharp/CefSharp/blob/v53.0.0/CefSharp.WinForms.Example/BrowserTabUserControl.cs#L224
A common request is to control popup creation. Implement ILifeSpanHandler.OnBeforePopup
to control how popups are created. To cancel popup creation altogether simply return true;
.
bool ILifeSpanHandler.OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
//Set newBrowser to null unless you're attempting to host the popup in a new instance of ChromiumWebBrowser
newBrowser = null;
return true; //Return true to cancel the popup creation
}
You can cancel popup creation and open the URL in a new ChromiumWebBrowser
instance of your choosing using this method. It is important to note the parent-child relationship will not exist using this method. So in general it's not recommended.
EXPERIMENTAL OPTION 1:
Allows you to host the popup using the newBrowser
param in OnBeforePopup
. There are some known issues (search on the GitHub
project). If you use this method and experience a problem then you will have to take responsibility and get the problem resolved with the CEF project. It's also important to note that the events like LoadingStateChanged
etc are not called for popups. If you use this method, implement the relevant handler.
EXPERIMENTAL OPTION 2:
Use IWindowInfo.SetAsChild
to specify the parent handle. To use this in WPF you would need to use the WinForms host.
Using this method you would need to handle the move and resize events. This would roughly look like:
- Grab the
IBrowserHost
from the newly createdIBrowser
instance that represents the popup then subscribe to window move notifications and callNotifyMoveOrResizeStarted
- Call
SetWindowPos
on the browser HWND when the size changes (set to 0,0 when hidden to stop rendering)
There are examples in the project source though they are EXPERIMENTAL and there are no guarantees they're working. Option 2 has an incomplete example though reports suggest it works well, though the person never contributed a working example.
Simple code may look something like this:
var script = string.Format("document.body.style.background = '{0}'", colors[color_index++]);
if (color_index >= colors.Length)
{
color_index = 0;
}
browser.GetMainFrame().ExecuteJavaScriptAsync(script);
JavaScript
can only be executed within a V8Context. The IRenderProcessMessageHandler.OnContextCreated
and IRenderProcessMessageHandler.OnContextReleased
provide a boundry for when javascript can be executed. Currently these are only called for the main frame, it's created first, so should be sufficent for 99%
of cases.
It's tempting to start trying to access the DOM
in OnFrameLoadStart
, whilst the V8Context
will have been created and you will be able to execute a script the DOM
will not have finished loading. If you need to access the DOM
at it's earliest possible point then subscribe to DOMContentLoaded
, some examples of executing JavaScript
are below.
browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();
public class RenderProcessMessageHandler : IRenderProcessMessageHandler
{
// Wait for the underlying JavaScript Context to be created. This is only called for the main frame.
// If the page has no JavaScript, no context will be created.
void IRenderProcessMessageHandler.OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
{
const string script = "document.addEventListener('DOMContentLoaded', function(){ alert('DomLoaded'); });";
frame.ExecuteJavaScriptAsync(script);
}
}
//Wait for the page to finish loading (all resources will have been loaded, rendering is likely still happening)
browser.LoadingStateChanged += (sender, args) =>
{
//Wait for the Page to finish loading
if (args.IsLoading == false)
{
browser.ExecuteJavaScriptAsync("alert('All Resources Have Loaded');");
}
}
//Wait for the MainFrame to finish loading
browser.FrameLoadEnd += (sender, args) =>
{
//Wait for the MainFrame to finish loading
if(args.Frame.IsMain)
{
args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");
}
};
Some notes about executing JavaScript
:
- Scripts are executed at the frame level, and every page has at least one frame (
MainFrame
). - The
IWebBrowser.ExecuteScriptAsync
extension method is left for backwards compatibility, you can use it as a shortcut to executejs
on the main frame. - If a frame does not contain Javascript then no
V8Context
will be created. - For a frame that doesn't have a context executing a script once the frame has loaded it's possible to create a V8Context using
IFrame.ExecuteJavaScriptAsync
. - The
DOM
won't have finished loading whenOnFrameLoadStart
is fired -
IRenderProcessMessageHandler.OnContextCreated/OnContextReleased
are only called for the main frame.
If you need to evaluate code which returns a value, use the Task<JavascriptResponse> EvaluateScriptAsync(string script, TimeSpan? timeout)
method. JavaScript code is executed asynchronously and as such uses the .Net Task
class to return a response, which contains error message, result and a success (bool
) flag.
// Get Document Height
var task = frame.EvaluateScriptAsync("(function() { var body = document.body, html = document.documentElement; return Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); })();", null);
task.ContinueWith(t =>
{
if (!t.IsFaulted)
{
var response = t.Result;
EvaluateJavaScriptResult = response.Success ? (response.Result ?? "null") : response.Message;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
For a more detailed example check out this Gist
Notes
- Scripts are executed at the frame level, and every page has at least one frame (
MainFrame
) - Only trivial values can be returned (like int, bool, string etc) - not a complex (user-defined) type which you have defined yourself. This is because there is no (easy) way to expose a random JavaScript object to the .NET world, at least not today. However, one possible technique is to turn the JavaScript object you wish to return to your .NET code into a JSON string with the JavaScript
JSON.toStringify()
method and return that string to your .NET code. Then you can decode that string into a .NET object with something like JSON.NET. See this MSDN link for more information. (https://msdn.microsoft.com/en-us/library/ie/cc836459(v=vs.94).aspx)
JavaScript Binding (JSB
) allows for communication between JavaScript
and .Net
. There are two distinct implementations available currently, the Async
version and the older Sync
version.
NOTE This document has been updated and is relevant for version 63
and greater, for older versions see https://github.com/cefsharp/CefSharp/wiki/General-Usage/156732a0d567915551b9e162d93067fa23cf89e3#3-how-do-you-expose-a-net-class-to-javascript
- Uses
Native Chromium IPC
to pass messages back and forth between the Browser Process and Render Process., and as such is very fast. - Only
methods
are supported as theNative Chromium IPC
is message based (Property
get/sets cannot be done in an async fashion) -
Methods
can return simple objects,structs
andclasses
are supported, only a copy of theProperties
is transferred toJavaScript
. Think of it like making awebservice/ajax
call, you get a response object. - Supports
JavaScript callbacks
, through theIJavascriptCallback
interface, example below - Non blocking, calls return a standard JavaScript Promise (
await
is supported)
If your not familiar with all Chromium
has to offer when it comes to async programming
here are some very useful articles
- https://developers.google.com/web/fundamentals/primers/async-functions
- https://developers.google.com/web/fundamentals/primers/promises
Binding is initiated by JavaScript, the CefSharp.BindObjectAsync
method returns a Promise
that is resolved when bound objects are available. Objects are created in the global context (properties of the window
object). If you call CefSharp.BindObjectAsync
without any params then all registered objects will be bound. Binding by name is the more descriptive options.
Bind the object that matches objectName, the IJavascriptObjectRepository.ResolveObject
will be raised with args.ObjectName
equal to objectName. If no objectName is provided then All
will be passed as args.ObjectName
Param | Optional | Remarks |
---|---|---|
settings | yes | A set of key/value pairs that configure the default binding settings. |
objectName | yes | Name of the object you wish to bind, if no name is provided then All objects will be bound |
Returns: a Promise
that can be awaited
.
Settings | Optional | Remarks |
---|---|---|
NotifyIfAlreadyBound | yes | boolean (true/false). If true then triggers the IJavascriptObjectRepository.ObjectBoundInJavascript event even if the object is already bound (by default the event will not be called if an object is already bound). . |
IgnoreCache | yes | If true then the local cache will be ignored and the request to the IJavascriptObjectRepository will be made |
Example 1
(async function()
{
//`IJavascriptObjectRepository.ResolveObject` will be called with `args.ObjectName` of `All`.
await CefSharp.BindObjectAsync();
//Objects will have been bound, you can now access them
})();
Example 2
(async function()
{
await CefSharp.BindObjectAsync("boundAsync");
//Object with name boundAsync will have been bound, you can now access
})();
Example 3
(async function()
{
await CefSharp.BindObjectAsync({ NotifyIfAlreadyBound: true, IgnoreCache: true }, "boundAsync2");
//Object with name boundAsync2 will have been bound, you can now access them.
// Cache will have been ignored and notification in `.Net` will have been provided through `IJavascriptObjectRepository.ObjectBoundInJavascript`
})();
Deletes the object that matches objectName,
Param | Optional | Remarks |
---|---|---|
objectName | no | Name of the object to be deleted |
Returns: bool, true if successful otherwise false
Example 1
CefSharp.DeleteBoundObject("boundAsync");
Removes the object that matches objectName from the cache
Param | Optional | Remarks |
---|---|---|
objectName | no | Name of the object to be removed from the cache |
Returns: bool, true if successful otherwise false
Example 1
CefSharp.RemoveObjectFromCache("boundAsync");
//You can use lowercase version if you like, exactly the same function
cefSharp.removeObjectFromCache("boundAsync");
Does an object with name objectName
exist in the cache
Param | Optional | Remarks |
---|---|---|
objectName | no | Name of the object |
Returns: bool, true if cached otherwise false
Example 1
CefSharp.IsObjectCached("boundAsync") === true;
//You can use lowercase version if you like, exactly the same function
cefSharp.IsObjectCached("boundAsync") === true;
The simple workflow would look like:
-
Step 1 Create a class that you wish to expose to javascript (don't use your
Form/Window
orControl
) -
Step 2 Call
CefSharp.BindObjectAsync
with the name of the object you wish to register, e.g.CefSharp.BindObjectAsync("myObject");
(Objects will only be avaliable after thePromise
has resolved. -
Step 3 Register your object with the
JavaScriptObjectRepository
Remember Only Methods are supported. If you need to set a property, then create Get
/Set
methods. You can use a IJavascriptCallback
to pass a function pointer into a method. See the Sync JavaScript Binding
section below for an example. This feature can be used in both methods. The async feature uses Promise
s, you can throw an exception and pass that to your function. For more details on Promise
check https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
Step 1
A simple class would look like this:
public class BoundObject
{
//We expect an exception here, so tell VS to ignore
[DebuggerHidden]
public void Error()
{
throw new Exception("This is an exception coming from C#");
}
//We expect an exception here, so tell VS to ignore
[DebuggerHidden]
public int Div(int dividend, int divisor)
{
return dividend / divisor;
}
}
Step 2 Call CefSharp.BindObjectAsync
, Some examples below of Binding
an object look like:
NOTE This is a two part process, see below the examples for details
<script type="text/javascript">
(async function()
{
await CefSharp.BindObjectAsync("boundAsync");
boundAsync.div(16, 2).then(function (actualResult)
{
const expectedResult = 8;
assert.equal(expectedResult, actualResult, "Divide 16 / 2 resulted in " + expectedResult);
});
boundAsync.error().catch(function (e)
{
var msg = "Error: " + e + "(" + Date() + ")";
});
})();
(async () =>
{
await CefSharp.BindObjectAsync("boundAsync");
boundAsync.hello('CefSharp').then(function (res)
{
assert.equal(res, "Hello CefSharp")
});
})();
//Can use both cefSharp.bindObjectAsync and CefSharp.BindObjectAsync, both do the same
Step 3
The second part of the process is registering the object with the JavascriptObjectRepository
(accessible though the browser.JavascriptObjectRepository
property). You have two options for registering an object in .Net
, the first is registered in advance, this is usually done immediately after you create a ChromiumWebBrowser
instance. The second options is more flexible and allows objects to be Resolved
when required.
When a CefSharp.BindObjectAsync
call is made, the JavascriptObjectRepository
is queries to see if an object with the given name is specified is already registered, if no matching object is found then the ResolveObject
event is raised. For calls to CefSharp.BindObjectAsync
without any params, then if objects have already been registered then they will all be bound, if no objects have been registered then ResolveObject
will be called with the ObjectName
set to All
.
//When a
browser.JavascriptObjectRepository.ResolveObject += (sender, e) =>
{
var repo = e.ObjectRepository;
if (e.ObjectName == "boundAsync")
{
BindingOptions bindingOptions = null; //Binding options is an optional param, defaults to null
bindingOptions = BindingOptions.DefaultBinder //Use the default binder to serialize values into complex objects, CamelCaseJavascriptNames = true is the default
bindingOptions = new BindingOptions { CamelCaseJavascriptNames = false, Binder = new MyCustomBinder() }); //No camelcase of names and specify a default binder
repo.Register("boundAsync", new BoundObject(), isAsync: true, options: bindingOptions);
}
};
To register objects in advance simply use the following
//For async object registration (equivalent to the old RegisterAsyncJsObject)
browser.JavascriptObjectRepository.Register("boundAsync", new BoundObject(), true, options);
To be notified in .Net
when objects have been bound in JavaScript
then you can subscribe to the ObjectBoundInJavascript event
browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) =>
{
var name = e.ObjectName;
Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
};
For reference the set of QUnit
test cases used are https://github.com/cefsharp/CefSharp/blob/master/CefSharp.Example/Resources/BindingTest.html
A simply Multiply
example is available at https://github.com/cefsharp/CefSharp.MinimalExample/commit/9f817888fad8e5f7c602f90170102cec2db29a5f
- Uses a
WCF
service for communication - Supports both methods and properties
- Calls are executed in a
sync
fashion and are blocking, long running calls will block theRender Process
and make your app appear slow or become unresponsive. - Supports semi complex object structures
- Occasionally the
WCF
service doesn't shutdown cleanly and slows down application shutdown - It's reccomended that anyone creating a new application use the
Async
version as it's under active development.
Binding is initiated by JavaScript, the CefSharp.BindObjectAsync
method returns a Promise
that is resolved when bound objects are available. Objects are created in the global context (properties of the window
object). If you call CefSharp.BindObjectAsync
without any params then all registered objects will be bound. Binding by name is the more descriptive options.
The simple workflow would look like:
-
Step 1 Create a class that you wish to expose to javascript (don't use your
Form/Window
orControl
) -
Step 2 Call
CefSharp.BindObjectAsync
with the name of the object you wish to register, e.g.CefSharp.BindObjectAsync("myObject");
(Objects will only be avaliable after thePromise
has resolved. -
Step 3 Register your object with the
JavaScriptObjectRepository
Step 1
public class BoundObject
{
public string MyProperty { get; set; }
public void MyMethod()
{
// Do something really cool here.
}
public void TestCallback(IJavascriptCallback javascriptCallback)
{
const int taskDelay = 1500;
Task.Run(async () =>
{
await Task.Delay(taskDelay);
using (javascriptCallback)
{
//NOTE: Classes are not supported, simple structs are
var response = new CallbackResponseStruct("This callback from C# was delayed " + taskDelay + "ms");
await javascriptCallback.ExecuteAsync(response);
}
});
}
}
Step 2 Call CefSharp.BindObjectAsync
, Some examples below of Binding
an object look like:
NOTE This is a two part process, see below the examples for details
<script type="text/javascript">
(async function()
{
await CefSharp.BindObjectAsync("boundAsync");
boundAsync.div(16, 2).then(function (actualResult)
{
const expectedResult = 8
assert.equal(expectedResult, actualResult, "Divide 16 / 2 resulted in " + expectedResult);
});
boundAsync.error().catch(function (e)
{
var msg = "Error: " + e + "(" + Date() + ")";
});
})();
(async () =>
{
await CefSharp.BindObjectAsync("boundAsync");
boundAsync.hello('CefSharp').then(function (res)
{
assert.equal(res, "Hello CefSharp")
});
})();
CefSharp.BindObjectAsync("boundAsync2").then(function(result)
{
boundAsync2.hello('CefSharp').then(function (res)
{
assert.equal(res, "Hello CefSharp")
// NOTE the ability to delete a bound object
assert.equal(true, CefSharp.DeleteBoundObject("boundAsync2"), "Object was unbound");
assert.ok(window.boundAsync2 === undefined, "boundAsync2 is now undefined");
});
});
</script>
Step 3
The second part of the process is registering the object with the JavascriptObjectRepository
(accessible though the browser.JavascriptObjectRepository
property). You have two options for registering an object in .Net
, the first is registered in advance, this is usually done immediately after you create a ChromiumWebBrowser
instance. The second options is more flexible and allows objects to be Resolved
when required.
When a CefSharp.BindObjectAsync
call is made, the JavascriptObjectRepository
is queries to see if an object with the given name is specified is already registered, if no matching object is found then the ResolveObject
event is raised. For calls to CefSharp.BindObjectAsync
without any params, then if objects have already been registered then they will all be bound, if no objects have been registered then ResolveObject
will be called with the ObjectName
set to All
.
//When a
browser.JavascriptObjectRepository.ResolveObject += (sender, e) =>
{
var repo = e.ObjectRepository;
if (e.ObjectName == "boundAsync2")
{
BindingOptions bindingOptions = null; //Binding options is an optional param, defaults to null
bindingOptions = BindingOptions.DefaultBinder //Use the default binder to serialize values into complex objects, CamelCaseJavascriptNames = true is the default
bindingOptions = new BindingOptions { CamelCaseJavascriptNames = false, Binder = new MyCustomBinder() }); //No camelcase of names and specify a default binder
repo.Register("bound", new BoundObject(), isAsync: false, options: bindingOptions);
}
};
In the actual JS code, you would use the object like this (default is to CamelCaseJavascriptNames, this is controllable via binding options, see above for an example).
bound.myProperty; // use this syntax to access the property
bound.myMethod(); // use this to call the method.
bound.testCallback(callback); //Pass a function in to use as a callback
Please note:
- DO NOT REGISTER YOUR FORM/WINDOW/CONTROL. Create a class and proxy calls if required.
- By default, methods and properties are changed into camelCase (i.e. the first letter is lower-cased) to make its usage be natural in JavaScript code. For
39.0.1
and greater this is optional and this behavior can be specified. - Complex objects are supported for properties (where applicable) so you can now do
bound.subObject.myFunction()
andbound.subObject.myProperty = 1
. - Complex object support for functions is now possible thorugh the
IBinder
interface, you can implement your own or use theDefaultBinder
e.g.repo.Register("bound", new BoundObject(), BindingOptions.DefaultBinder);
Due to Chromium
changes the older RegisterAsyncJsObject
method of registering an object no longer works as it previously did, bound objects are no longer available after cross-site
navigation.
RegisterAsyncJsObject
is only avaliable using the legacy behavior which will still work for Single Page Applications
and where only a single domain is used. You will need to set CefSharpSettings.LegacyJavascriptBindingEnabled = true
before you register your first object. Objects are bound to a V8Context
upon creation and as such are immediately avaliable
//For legacy biding set the following before your create a `ChromiumWebBrowser` instance
CefSharpSettings.LegacyJavascriptBindingEnabled = true;
browser.RegisterAsyncJsObject("boundAsync", new BoundAsyncObject());
For full details see https://github.com/cefsharp/CefSharp/wiki/General-Usage/156732a0d567915551b9e162d93067fa23cf89e3#registerasyncjsobject
Due to Chromium
changes the older RegisterJsObject
method of registering an object no longer works as it previously did, bound objects are no longer available after cross-site
navigation.
RegisterJsObject
is only avaliable using the legacy behavior which will still work for Single Page Applications
and where only a single domain is used. You will need to set CefSharpSettings.LegacyJavascriptBindingEnabled = true
before you register your first object. Objects are bound to a V8Context
upon creation and as such are immediately avaliable
//For legacy biding set the following before your create a `ChromiumWebBrowser` instance
CefSharpSettings.LegacyJavascriptBindingEnabled = true;
browser.RegisterJsObject("bound", new BoundObject());
This method is old and uses WCF to communicate between the Browser Process and Render Process. Calls are made in a synchronous fashion, so long running method calls will block the render process and must be avoided. You can use a IJavascriptCallback
to execute in an async
fashion.
For full details see https://github.com/cefsharp/CefSharp/wiki/General-Usage/156732a0d567915551b9e162d93067fa23cf89e3#RegisterJsObject
A system-wide installation of Pepper Flash that will be automatically discovered and loaded by CefSharp
can be downloaded from Adobe. Choose the FP for Opera and Chromium -- PPAPI version from the drop down list. To test flash is working, simply load http://www.adobe.com/software/flash/about/.
Technically it's possible to load the Pepper Flash
DLL directly using command line arguments. I'm only providing the example below as a reference. I make no guarantees that it works or will continue to work. If you experience problems, switch to using the system-wide installer as per above.
//Load the pepper flash player that comes with Google Chrome - may be possible to load these values from the registry and query the DLL for it's version info (Step 2 not strictly required it seems)
var cefSettings = new CefSettings();
cefSettings.CefCommandLineArgs.Add("ppapi-flash-path", @"C:\Program Files (x86)\Google\Chrome\Application\47.0.2526.106\PepperFlash\pepflashplayer.dll"); //Load a specific pepper flash version (Step 1 of 2)
cefSettings.CefCommandLineArgs.Add("ppapi-flash-version", "20.0.0.228"); //Load a specific pepper flash version (Step 2 of 2). Without this step, calling Cef.GetPlugins() will return "11.2.999.999" by default.
It's also technically possible to obtain the pepflashplayer.dll
from the system-wide installer and include that with your app, using the command line arguments above. Before you go down this path, contact Adobe to make sure you're not breaching their licensing requirements for distribution.
Note When opening flash for the first time a console window will appear for a split second that says NOT SANDBOXED
. There is an Issue on the Chromium
issue tracker, though unfortunately Google
have marked it as WontFix
. A few clever people have hacked together some workarounds. They're complex and I've never tried them. Follow the link in https://github.com/cefsharp/CefSharp/issues/1259 for details.
The WPF and OffScreen versions use the OffScreen Rendering (OSR) rendering mode. In OSR mode each frame is rendered to a buffer and then either drawn on the screen as in the case of WPF, or made available as a Bitmap
in the OffScreen
.
For the WPF control, user input (mouse clicks/moves and key presses) is forwarded to the underlying browser though methods on the IBrowserHost
interface. It is possible to obtain access to each Bitmap
as it's rendered.
A special note should be made about hosting the ChromiumWebBrowser
within a ViewBox
. This is far from ideal, as every frame is rendered then post-processing happens to resize/scale the image. This is a huge performance hit and often reduces quality (it's usually quite blurry). You can adjust the resize quality using RenderOptions.SetBitmapScalingMode
. It's best to avoid using a ViewBox
. You can scale the content contained within the browser by adjusting the ZoomLevel
, which is by far the most performant option.
For the CefSharp.OffScreen
package, each frame is rendered to a Bitmap
and exposed for use. If you wish to interact with the browser via keyboard or mouse, you can use methods on the IBrowser
host interface. Simulating key presses and mouse clicks/moves can be quite complex. You can use the WPF control as a starting example as it uses the same methods (add debugging to see what sequence of events is required). Key presses and mouse clicks/moves are often made up of multiple parts, up
/down
with a number of other possible combinations.
The UserAgent
can only be set globally and is not changeable at run-time. See http://magpcss.org/ceforum/viewtopic.php?f=6&t=14685&p=33024 for the official response from the CEF maintainer. You can modify the User-Agent
HTTP header in IRequestHandler.OnBeforeResourceLoad
, which would need to be done for every request. What it doesn't do is change the UserAgent
the browser reports to JavaScript.
https://github.com/cefsharp/CefSharp/issues/534#issuecomment-60694502
You can open DevTools from within CefSharp. Not all features work. Anything that's missing needs to be implemented in CEF.
browser.ShowDevTools();
You can connect Chrome to a running instance. This will likely give you more options (not all that exist within Chrome unfortunately).
var settings = new CefSettings();
settings.RemoteDebuggingPort = 8088;
Cef.Initialize(settings);
Open http://localhost:8088
in Chrome.
The underlying CEF web browser is not particularly well suited to taking screenshots. Here are some notes and caveats:
Both Offscreen
and WPF
use Offscreen Rendering (OSR) where every frame is rendered to a bitmap. It is still a web browser under the hood and not particularly well suited for this scenario. Here are some notes:
- Lower frame rate, to make it easier to capture frames may be worth considering
- You'll need to wait a period of time after the page has finished loading to allow the browser to render
- There is currently no method of determining when a web page has finished rendering (and unlikely ever will be as with features like flash, dynamic content, animations, even simple tasks like moving your mouse or scrolling will cause new frames to be rendered).
- A hack method to determine when rendering has approximately finished is to have a timer that's reset every time a frame is rendered, when no additional frames are rendered then the timer will file (not ideal)
Here are some examples of taking screen shots under windows
When using the 32bit
version make sure your application is large address aware (handle addresses larger than 2gb)
As per suggestion in http://magpcss.org/ceforum/viewtopic.php?f=6&t=15120#p34802 it appears that setting The Large Address Aware linker setting on your application executable when running as a 32bit application may now be necessary where high memory load is experienced.
https://msdn.microsoft.com/en-us/library/wz223b1z.aspx
The default x86 SubProcess shipped with CefSharp is large address aware, you should make your application aware as well.
After applying the Large Address Aware linker setting to your executable, if your still experiencing the exact same problem then discuss your issue at http://magpcss.org/ceforum/viewtopic.php?f=6&t=15120
Use IFrame.LoadRequest
, here is a basic example
public void LoadCustomRequestExample()
{
var frame = WebBrowser.GetMainFrame();
//Create a new request knowing we'd like to use PostData
var request = frame.CreateRequest(initializePostData:true);
request.Method = "POST";
request.Url = "custom://cefsharp/PostDataTest.html";
request.PostData.AddData("test=123&data=456");
frame.LoadRequest(request);
}
By default CefSettings.Locale
will dictate which dictionary is used, the default being en-US
. It is possible to configure many aspects of spell checking enable/disable
on the fly, change dictionary
on the fly, even enable multiple dictionaries. Use RequestContext.SetPreference
(See the RequestContext
section of this document for details on how to set a preference).
Spellcheck can only be changed dynamically using spellcheck.dictionaries
preference (important to use the plural version)
https://bitbucket.org/chromiumembedded/cef/issues/2222/spell-checking-language-cannot-be-changed#comment-38338016
Here are some userful links
http://magpcss.org/ceforum/viewtopic.php?f=6&t=14911&p=33882&hilit=spellcheck#p33882 https://cs.chromium.org/chromium/src/components/spellcheck/browser/pref_names.cc?type=cs&q=%22spellcheck.dictionary%22&l=11 https://cs.chromium.org/chromium/src/components/spellcheck/browser/pref_names.cc?type=cs&q=%22spellcheck.dictionary%22&l=15
Not all language support spell checking, see https://magpcss.org/ceforum/viewtopic.php?f=6&t=16508#p40684
Is enabled by default in newer builds see https://www.chromestatus.com/feature/5453022515691520
For older versions you need to manually enable WebAssembly
see https://bitbucket.org/chromiumembedded/cef/issues/2101/add-webassembly-support
settings.javascript_flags
translates to settings.JavascriptFlags = "--expose-wasm";
Capturing unmanaged exceptions is difficult and CEF
could potentially be in a corrupted state requiring your application to terminate and restart. As this is a general programming topic and outside the scope of CefSharp
specifically here are some resources to get you started researching this for yourself.
http://stackoverflow.com/questions/233255/how-does-setunhandledexceptionfilter-work-in-net-winforms-applications https://msdn.microsoft.com/en-us/library/windows/desktop/ms680634(v=vs.85).aspx https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,8243b844777a16c3 https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,3192
Capturing unhandled exceptions in a mixed native/CLR environment http://www.ikriv.com/blog/?p=1440
Has a very simple class that checks to see if the all the relevant unmanaged resources are present.
- Cef.Initialize
- Call it directly yourself
It's not 100%
foolproof, if your having problems and all resources exist then disable dependency checking. There are certain scenarios when it's not going to work.
https://github.com/cefsharp/CefSharp/wiki/Output-files-description-table-%28Redistribution%29
CEF
and subsequently CefSharp
only supports freely available audio and video codecs. To see what the version of CefSharp
you are working with supports open http://html5test.com/ in a ChromiumWebBrowser
instance.
-
MP3
patent has expired and as a result is supported in version65.0.0
on wards. -
H264/AAC
are classed asProprietary Codecs
and are not supported, they require you to obtain a license. Sites likeNetflix/Twitter/Instagram
useH264
and as a result their videos won't play. See https://www.fsf.org/licensing/h264-patent-license for some comments from theFree Software Foundation
on the subject.
Compiling CEF
with support for H264/AAC
is outside the scope of this project. The following are provided for reference only, please don't ask for support for compiling CEF
.
- http://magpcss.org/ceforum/viewtopic.php?f=6&t=13515
- https://github.com/cefsharp/CefSharp/issues/1934#issuecomment-279305821
- https://github.com/mitchcapper/CefSharpDockerfiles
The WinForms
version has built in support for onscreen keyboard, it has been reported that on occasion it doesn't always popup correctly, using disable-usb-keyboard-detect
command line argument
https://github.com/cefsharp/CefSharp/issues/1691#issuecomment-323603277 has reported to resolve this problem.
The WPF
does not have built in support for onscreen (virtual) keyboard, starting with version 73
a new VirtualKeyboardRequested
event now provides notification when you application should display a virtual keyboard. Unfortunately it's difficult to provide a default implementation that supports Windows 7, 8.1 and 10
as there is no .Net API
for display a virtual keyboard. A Windows 10 Only
example was added in https://github.com/cefsharp/CefSharp/commit/0b57e526158e57e522d46671404c557256529416 If you need to support Windows 8 and 10
then https://github.com/maximcus/WPFTabTip might be useful. For Windows 7
https://stackoverflow.com/questions/1168203/incorporating-the-windows-7-onscreen-keyboard-into-a-wpf-app has some suggestions.