Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork3k
General Usage
This guide introduces the general concepts involved when developing an application usingCefSharp. It's important to remember thatCefSharp is a .Net wrapper around theChromium 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 inChromium are currently available. For example, support for extensions is limited, only a subset of theExtension API is implemented.
TheCEF project has its ownwebsite andSupport Forum 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 whichCEF version you're using.CefSharp versions look like73.1.130, which maps toCEF 73.1.13+g6e3c989+chromium-73.0.3683.75. Openchrome://version/ or check yourpackages.config to easily determine the version. Post onCEF Support Forum before opening an issue on theCEF Issue Tracker.
It'simportant to remember thatCefSharp is limited by theAPI thatCEF exposes, and even then not all of theCEF API is currently implemented. If you are chasing a particular feature then check through theCEF C++ headers orCEF API Doc. If there is a piece of theCEF API that is not currently exposed, then you can implement it yourself and submit aPR for inclusion in the main project.
This document is based onCEF General Usage Wiki
CefSharp provides four different implementations,WinForms,WPF,WPF.HwndHost andOffScreen. TheWPF andOffScreen versions use theOffScreen Rendering(OSR) rendering mode. InOSR mode each frame is rendered to a buffer and then either drawn on the screen as in the case ofWPF or available as aBitmap inOffScreen. Performance of theWPF version is slower than theWinForms version.Wpf.HwndHost implementation is equivilent ot hosting theWinForms version inWPF with support forWPF Data Binding.
All versions use theCefSharp,CefSharp.Core andCefSharp.Core.Runtime libraries, as a result much of theAPI is used exactly the same within all three flavors. This limits code duplication and reduces the maintenance burden of adding a new feature.
- 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
- Cookie Manager
Release notes are available for each version athttps://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 theKnown 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).EachCefSharp release has it's own branch, seehttps://github.com/cefsharp/CefSharp#release-branches for details and requirements for each branch are listed there.
CefSharp requires:
- Microsoft.Net 4.6.2 or greater
- Microsoft Visual C++ Redistributable Package (either
x86orx64depending on your application). To determine which versions ofVisual C++you require seehttps://github.com/cefsharp/CefSharp#release-branches
Notes:
- You can package the
VC++ Redist Dll'swith your application seehttps://github.com/cefsharp/CefSharp/wiki/Frequently-asked-questions#Including_vcredist for details
Newer versions now support targetingAnyCPU, seehttps://github.com/cefsharp/CefSharp/issues/1714 for details on how to implement this. The same technique can be used to movelibcef.dll, etc to a different folder or common location on disk.
- Specifying a
CachePathis required for persistence of cookies, saving of passwords, etc, anIn-Memorycache is used by default (similar toIncognito). SeeInitialize and Shutdown section below for an example of Initializing CEF with aCachePathbelow. - You can clear the disk cache using seeNetwork.clearBrowserCache see#3158 for details on executing
DevToolscommands. - Add an
app.manifestto your app forHiDPIsupport, app compatibility (running onWindows 10) and tooltips inWinForms. The examples contain sampleapp.manifestfiles.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.manifestwith the relevantcompatibilityentries. CEFcan only beInitialized/Shutdownonce per process, see the section below for full details, this is a limitation of the underlyingChromiumframework.- Only runs in the defaultAppDomain, there are some workarounds like those athttps://github.com/flole/CefSharp.AppDomain andhttps://github.com/stever/AppHostCefSharp
- Due to limited resources only a single version is supported at a time, seehttps://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
Windowsand noApp Storeversion. - .Net Core is supported though additional steps are required seehttps://github.com/cefsharp/CefSharp.MinimalExample#net-core-support
Sandboxinghas not been implemented as it's technically infeasible to add support directly intoCefSharp, see #697 for details.WinFormson screen keyboard potentially can benefit fromdisable-usb-keyboard-detectcommand line argumenthttps://github.com/cefsharp/CefSharp/issues/1691#issuecomment-323603277WPFusers withHigh DPImonitors are recommended to install.Net 4.6on their target machines, as there is a bug in the.Net Frameworkthat can potentially cause aMILERR_WIN32ERROR Exceptionsee #2035 for detailsGoogle Earthcurrently requiresSharedBufferArraywhich is disabled by default seehttps://github.com/cefsharp/CefSharp/discussions/3826#discussioncomment-1383028 on how to enable.
TheCefSharp source code contains examples of many different features. There is also theMinimalExample project which uses the latestNuget packages to provide very simpleBrowser implementations. TheMinimalExample 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 defaultCEF maintains its own log file ('Debug.log') in your application's executing folder e.g.bin. To disable logging changesettings.LogSeverity, and to change the file name/path usesettings.LogFile.
When debugging a problem, the first place to check is this log file as it contains low levelChromium messages. If you see errors or warnings then search onhttp://magpcss.org/ceforum/index.php andhttps://bitbucket.org/chromiumembedded/cef/issues?status=new&status=open
CEF uses the same multiprocess model asChromium. The main process which handles window creation and painting is referred to as thebrowser 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 separaterender process. Some application logic, such as JavaScript bindings, will also run in the render process.
Render Processes(--type=renderer) - V8(Javascript) and renderingGPU Process(--type=gpu-process) - handles GPU accelerated compositingNetwork Service(--type=utility --utility-sub-type=network.mojom.NetworkService) - Entire Chromium network stack runs in this processStorage Service(--type=utility --utility-sub-type=storage.mojom.StorageService) - html5 storage e.g. localStorageAudio Service(--type=utility --utility-sub-type=audio.mojom.AudioService) - Playing of audioCrashPad Handler(--type=crashpad-handler) - creates crash dumps
By default a spare renderer process may be created on initial browser creation or cross-origin navigation. This spare process may be used with a future cross-origin navigation or discarded on whenCef.Shutdown has been called.
CefSharp comes with a process calledCefSharp.BrowserSubProcess.exe which will be spawned multiple times to represent separate processes as described above.OpeningTask Manager and seeing multiple instances ofCefSharp.BrowserSubProcess.exe is perfectly normal and required for the browser to function correctly.It is possible as of version51.0.0 to provide your own customBrowserSubProcess. An example of self hosting the BrowserSubProcess using your own executableusingWinForms is available as part of theMinimalExample
Resources:
- https://www.chromium.org/servicification
- https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/servicification.md#overview
- https://bitbucket.org/chromiumembedded/cef/issues/2498/add-support-for-site-per-process#comment-54186905 has details on the current default process model.
CEF uses multiple threads for different levels of processing. Thebrowser process for example contains the following commonly-referenced threads:
- UI thread is the main thread in the browser process. By default
CefSharpusessetting.MultiThreadedMessageLoop = trueso theCEF UIthread 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 calledInitialize once per process (application). Running multiple instances of your app is possible, you'll need to provide uniqueCachePath for each instance, seeCefSettings section below.
SeeRequest 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 underlyingCEF library. This can be achieved in one of two ways, manually callingCef.Initialize before creating your firstChromiumWebBrowser instance, secondly if you don't need to specify any custom settings then the firstChromiumWebBrowser instance you create will callCef.Initialize if it hasn't been called already. For those wishing to specify some custom settings then you can initializeCEF yourself like below:
publicstaticvoidInit(){varsettings=newCefSettings();// Increase the log severity so CEF outputs detailed information, useful for debuggingsettings.LogSeverity=LogSeverity.Verbose;// By default CEF uses an in memory cache, to save cached data e.g. to persist cookies you need to specify a cache path// NOTE: The executing user must have sufficient privileges to write to this folder.settings.CachePath=Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"CefSharp\\Cache");Cef.Initialize(settings);}
ForCef.Shutdown the WinForms and WPF instances ofChromiumWebBrowser the relevant Application Exit event is hooked and Cef.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.
It's important to note CEF thatInitialize/ShutdownMUST 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 callingInitialize/Shutdown manually usingWinForms, the same can be applied toWPF and console applications that use theCefSharp.OffScreen package (TheOffScreen example athttps://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).
publicclassProgram{[STAThread]publicstaticvoidMain(){//We're going to manually call Cef.Shutdown below, this maybe required in some complex scenariosCefSharpSettings.ShutdownOnExit=false;//Perform dependency check to make sure all relevant resources are in our output directory.Cef.Initialize(newCefSettings(),performDependencyCheck:true,browserProcessHandler:null);varbrowser=newBrowserForm();Application.Run(browser);//Shutdown before your application exists or it will hang.Cef.Shutdown();}}
In summary
Cef.InitializeandCef.Shutdowncan only called Initialize once per process(application). There is no way around this, so only callShutdownwhen you're finished usingCefSharp.Cef.InitializeandCef.Shutdownmust be called on the same thread.Cef.Initializewill be called for you implicitly if you create a newChromiumWebBrowserinstance and haven't already calledCef.Initialize.- For the WinForms and WPF instances of
ChromiumWebBrowserthe 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 ofChromiumWebBrowseris created as the event handlers are hooked in the static constructor for theChromiumWebBrowserclass. - In
CefSharp.OffScreenyou must explicitly callCef.Shutdown()before your application exists or it will hang.
TheCefSettings structure allows configuration of application-wide CEF settings. Some commonly configured members include:
AcceptLanguageListcomma delimited ordered list of language codes without any whitespace that will be used in the "Accept-Language" HTTP header.BrowserSubprocessPathThe path to a separate executable that will be launched for sub-processes. Typically there is no need to change this.MultiThreadedMessageLoopthe default is True inCefSharp, though it is possible to integrateCEFinto your apps existing message loop seeMultiThreadedMessageLoop Section below.CommandLineArgsDisabledSet to true to disable configuration of browser process features using standard CEF and Chromium command-line arguments.RootCachePathThe root directory that allCefSettings.CachePathandRequestContextSettings.CachePathvalues must have in common. If this value is empty andCefSettings.CachePathis non-empty then it will default to theCefSettings.CachePathvalue. If this value is non-empty then it must be an absolute path. A non-empty RootCachePath can be used in conjunction with an empty CefSettings.CachePath in instances where you would like browsers attached to the Global RequestContext (the default) created in "incognito mode" and instances created with a custom RequestContext using a disk based cache. See theRequestContext section below for more details.CachePathThe location where data for the global browser cache will be stored on disk. In this value is non-empty then it must be an absolute path that is must be either equal to or a child directory of CefSettings.RootCachePath (if RootCachePath is empty it will default to this value). If the value is empty then browsers will be created in "incognito mode" where in-memory caches are used for storage and no data is persisted to disk. HTML5 databases such as localStorage will only persist across sessions if a cache path is specified. Can be overridden for individualRequestContextinstances via theRequestContextSettings.CachePathvalue. See theRequestContext section below for more details.LocaleThe 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.LogFileThe 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.LogSeverityThe 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".ResourcesDirPathThe 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.LocalesDirPathThe 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.RemoteDebuggingPortSet 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 behttp://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:
publicstaticvoidInit(){// Specify Global Settings and Command Line Argumentsvarsettings=newCefSettings();// By default CEF uses an in memory cache, to save cached data e.g. to persist cookies you need to specify a cache path// NOTE: The executing user must have sufficient privileges to write to this folder.settings.CachePath=Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"CefSharp\\Cache");;// There are many command line arguments that can either be turned on or off// Enable WebRTCsettings.CefCommandLineArgs.Add("enable-media-stream");//Disable GPU Accelerationsettings.CefCommandLineArgs.Add("disable-gpu");// 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 proxysettings.CefCommandLineArgs.Add("no-proxy-server");Cef.Initialize(settings);}
There are some settings which can be applied to a specificChromiumWebBrowser instance. If you're usingWPF, you can specifyBrowserSettings inXAML.
varbrowser=newChromiumWebBrowser(url){BrowserSettings={DefaultEncoding="UTF-8",WebGl=CefState.Disabled}};
<!--xmlns:cefSharpCore="clr-namespace:CefSharp;assembly=CefSharp.Core"--><!--xmlns:cefSharpWpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"--><cefSharpWpf:ChromiumWebBrowser> <cefSharpWpf:ChromiumWebBrowser.BrowserSettings> <cefSharpCore:BrowserSettingsDefaultEncoding="UTF-8"/> </cefSharpWpf:ChromiumWebBrowser.BrowserSettings></cefSharpWpf:ChromiumWebBrowser>
TheIBrowser andIFrame objects are used for sending commands to the browser and for retrieving state information in callback methods. EachIBrowser object will have a single mainIFrame object representing the top-level frame and zero or moreIFrame objects representing sub-frames.
For example, a browser that loads two HTML<iframe>s will have threeIFrame 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. SeeWebBrowserExtensions 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 (SeeIWebBrowser API doc for all common events and detailed information on their usage):
These are simple events that expose a small percentage of the underlying handlers thatCEF provides. These events are only called for the main browser, for popup handling you can access the notifications usingIDisplayHandler andILoadHandler.
To determine when a page has finished loading I recommend usingLoadingStateChanged overFrameLoadEnd. It's important to remember thatfinished loading is different tofinished 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 andIRequestHandler are some of the more common handlers (see the source/API doc for the rest). These simplywrap the underlying CEF handlers in a convenient .NET fashion. For example CEF'sCefDownloadHandler isIDownloadHandler inCefSharp. Implementing these handlers will provide you access to the underlying events and callbacks thatare 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 abool are asking you whether you'd like to handle this yourself. If no, then returnfalse for the default action. Returntrue if you will handle it yourself.
They are basic interfaces which you implement and then assign to yourChromiumWebBrowser instance. e.g.
browser.DownloadHandler=newDownloadHandler();
Ideally you should set handlers immediately after yourChromiumWebBrowser 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
- IResourceRequestHandlerFactory is an interface unique to CefSharp and allows for intercepting resource requests without having to implement a IRequestHandler and IResourceRequestHandler.
- IRenderProcessMessageHandler is for custom
CefSharpmessages sent from the render process - IFindHandler is for find notifications
It is possible to modify the response using aResponseFilter. See section below.
CEF supports two approaches for loading resource requests from memory/disk/database
- TheScheme Handler approach allows registration of a handler for requests targeting a particular origin (scheme + domain).
- TheRequest 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 thanhttp://,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 viaXMLHttpRequest to your scheme handler then you should use the HTTP scheme instead of a custom scheme to avoid potential issues.IsSecure andIsCorsEnabled params were added recently.
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 (likemyapp orinternal). Implement theISchemeHandlerFactory andIResourceHandler classes to handle the request and provide response data. SeeResourceHandler for the default implementation ofIResourceHandler, 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 (likemyapp orinternal). Implement theISchemeHandlerFactory andIResourceHandler classes to handle the request and provide response data. SeeResourceHandler for the default implementation ofIResourceHandler, which has lots of useful static helper methods.
A scheme handler is registered via theCefSettings.RegisterScheme function. For example, you can register a handler for 'https://cefsharp.test/' requests (there is another example below and there are working examples in the project source):
settings.RegisterScheme(newCefCustomScheme{SchemeName="https",DomainName="cefsharp.test",SchemeHandlerFactory=newFolderSchemeHandlerFactory(rootFolder:@"..\..\..\..\CefSharp.Example\Resources",hostName:"cefsharp.test",//Optional param no hostname/domain checking if nulldefaultPage:"home.html")//Optional param will default to index.html});
TheFolderSchemeHandlerFactory 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 formcustomscheme://folder/yourfile) or a standard scheme (https://,https://).
An example of implementing your own factory might look like:
publicclassCefSharpSchemeHandlerFactory:ISchemeHandlerFactory{publicconststringSchemeName="custom";privatestaticreadonlyIDictionary<string,string>ResourceDictionary;staticCefSharpSchemeHandlerFactory(){ResourceDictionary=newDictionary<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}};}publicIResourceHandlerCreate(IBrowserbrowser,IFrameframe,stringschemeName,IRequestrequest){//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 implementationvaruri=newUri(request.Url);varfileName=uri.AbsolutePath;stringresource;if(ResourceDictionary.TryGetValue(fileName,outresource)&&!string.IsNullOrEmpty(resource)){varfileExtension=Path.GetExtension(fileName);returnResourceHandler.FromString(resource,,mimeType:Cef.GetMimeType(fileExtension));}returnnull;}}
TheResourceHandler is provided as a default implementation ofIResourceHandler and contains many static helper methods for creating classes. See theResource Handler section below for further details.
A few examples of using the static methods are:
ResourceHandler.FromStream(stream,mimeType);ResourceHandler.FromString(htmlString,includePreamble:true,mimeType:Cef.GetMimeType(fileExtension));ResourceHandler.FromFilePath("CefSharp.Core.xml",mimeType);
Finally, you have to register this scheme handler using some code like this:
publicstaticvoidInit(){// Pseudo code; you probably need more in your CefSettings also.varsettings=newCefSettings();settings.RegisterScheme(newCefCustomScheme{SchemeName="custom",SchemeHandlerFactory=newCefSharpSchemeHandlerFactory()});Cef.Initialize(settings);}
It'simportant that the scheme registration takes place beforeCef.Initialize() is called.
IResourceRequestHandler.GetResourceRequestHandler supports the interception of arbitrary requests. It uses the sameIResourceHandler class as the scheme handler approach. TheResourceHandler is provided as a default implementation ofIResourceHandler and contains many static helper methods for creating classes. See theResource Handler section below for further details.
publicclassCustomResourceRequestHandler:CefSharp.Handler.ResourceRequestHandler{protectedoverrideIResourceHandlerGetResourceHandler(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest){//ResourceHandler has many static methods for dealing with Streams,// byte[], files on disk, strings// Alternatively ou can inheir from IResourceHandler and implement// a custom behaviour that suites your requirements.returnResourceHandler.FromString("Welcome to CefSharp!",mimeType:Cef.GetMimeType("html"));}}publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideIResourceRequestHandlerGetResourceRequestHandler(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,boolisNavigation,boolisDownload,stringrequestInitiator,refbooldisableDefaultHandling){//Only intercept specific Url'sif(request.Url=="http://cefsharp.test/"||request.Url=="https://cefsharp.test/"){returnnewCustomResourceRequestHandler();}//Default behaviour, url will be loaded normally.returnnull;}}browser.RequestHandler=newCustomRequestHandler();
TheIWebBrowser.RegisterResourceHandler andIWebBrowser.UnRegisterResourceHandler extension methods provide a simple means of providing anIResourceHandler for a givenUrl.
You could for example request a fictitious URL and provide a response just as if the site was real.
BothISchemeHandlerFactory andIResourceRequestHandler.GetResourceHandler use theIResourceHandler interface to represent the response (stream + headers + status codes, etc). There is a default implementation ofIResourceHandler which is simplyResourceHandler.
ResourceHandler contains many static methods for convenience
- ResourceHandler.FromString
- ResourceHandler.FromByteArray
- ResourceHandler.ForErrorMessage
- ResourceHandler.FromStream
- ResourceHandler.FromFilePath
A few examples of using the static methods are:
ResourceHandler.FromStream(stream,mimeType);ResourceHandler.FromString(htmlString,includePreamble:true,mimeType:Cef.GetMimeType(fileExtension));ResourceHandler.FromFilePath("CefSharp.Core.xml",mimeType);
The source contains detailed examples ofISchemeHandlerFactory.
Examples of how to implementResourceHandler.ProcessRequestAsync. If you require complete control then implementIResourceHandler, however in most cases this is not necessary.
//A simple example of a ResourceHandler that downloads a file from the internet.//A task is called to perform processing then executes the callback when the response is ready.//You could do Database queries + html generation or other backend type operationpublicclassExampleResourceHandler:ResourceHandler{publicoverrideCefReturnValueProcessRequestAsync(IRequestrequest,ICallbackcallback){Task.Run(()=>{using(callback){varhttpWebRequest=(HttpWebRequest)WebRequest.Create("https://raw.githubusercontent.com/cefsharp/CefSharp/master/README.md");varhttpWebResponse=(HttpWebResponse)httpWebRequest.GetResponse();// Get the stream associated with the response.varreceiveStream=httpWebResponse.GetResponseStream();varmime=httpWebResponse.ContentType;varstream=newMemoryStream();receiveStream.CopyTo(stream);httpWebResponse.Close();//Reset the stream position to 0 so the stream can be copied into the underlying unmanaged bufferstream.Position=0;//Populate the response valuesResponseLength=stream.Length;MimeType=mime;StatusCode=(int)HttpStatusCode.OK;Stream=stream;callback.Continue();}});returnCefReturnValue.ContinueAsync;}}
IResourceRequestHandler.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.
The basic example of getting the response as aUTF8 string is:
publicclassCustomResourceRequestHandler:CefSharp.Handler.ResourceRequestHandler{privatereadonlySystem.IO.MemoryStreammemoryStream=newSystem.IO.MemoryStream();protectedoverrideIResponseFilterGetResourceResponseFilter(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,IResponseresponse){returnnewCefSharp.ResponseFilter.StreamResponseFilter(memoryStream);}protectedoverridevoidOnResourceLoadComplete(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,IResponseresponse,UrlRequestStatusstatus,longreceivedContentLength){//You can now get the data from the streamvarbytes=memoryStream.ToArray();if(response.Charset=="utf-8"){varstr=System.Text.Encoding.UTF8.GetString(bytes);}else{//Deal with different encoding here}}}publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideIResourceRequestHandlerGetResourceRequestHandler(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,boolisNavigation,boolisDownload,stringrequestInitiator,refbooldisableDefaultHandling){//Only intercept specific Url'sif(request.Url=="http://cefsharp.github.io/"||request.Url=="https://cefsharp.github.io/"){returnnewCustomResourceRequestHandler();}//Default behaviour, url will be loaded normally.returnnull;}}browser.RequestHandler=newCustomRequestHandler();
CurrentlyStreamResponseFilter is the only filter provided as part of the framework.
A simple response filter that copies dataIn to dataOut. Data is streamed in chunks, typically 64kb in size.
/// <summary>/// PassThruResponseFilter - copies all data from DataIn to DataOut./// Upstream documentation link/// https://magpcss.org/ceforum/apidocs3/projects/(default)/CefResponseFilter.html#Filter(void*,size_t,size_t&,void*,size_t,size_t&)/// </summary>publicclassPassThruResponseFilter:IResponseFilter{boolIResponseFilter.InitFilter(){returntrue;}FilterStatusIResponseFilter.Filter(StreamdataIn,outlongdataInRead,StreamdataOut,outlongdataOutWritten){if(dataIn==null){dataInRead=0;dataOutWritten=0;returnFilterStatus.Done;}//Calculate how much data we can read, in some instances dataIn.Length is//greater than dataOut.LengthdataInRead=Math.Min(dataIn.Length,dataOut.Length);dataOutWritten=dataInRead;varreadBytes=newbyte[dataInRead];dataIn.Read(readBytes,0,readBytes.Length);dataOut.Write(readBytes,0,readBytes.Length);//If we read less than the total amount avaliable then we need//return FilterStatus.NeedMoreData so we can then write the restif(dataInRead<dataIn.Length){returnFilterStatus.NeedMoreData;}returnFilterStatus.Done;}publicvoidDispose(){}}
See theCefSharp.Exampleproject within the source for additional example implementations ofIResourceFilter. This feature isquite complex to implement. Make sure you read over and debug the existing examples before asking any questions.
There are a few extension methods provided as a convenience in theCefSharp.WebBrowserExtensions class.
//Load a data encoded Uri//NOTE There are limits to the size of a Data Uri, use the overload that takes a Url if you need to load large filesLoadHtml(thisIWebBrowserbrowser,stringhtml,boolbase64Encode=false);//Register a ResourceHandler with the `ResourceRequestHandlerFactory` and calls browser.LoadLoadHtml(thisIWebBrowserbrowser,stringhtml,stringurl)`;//Register a resource handler with the `ResourceRequestHandlerFactory`RegisterResourceHandler(thisIWebBrowserbrowser,stringurl,Streamstream,stringmimeType=ResourceHandler.DefaultMimeType);//Unregister a resource handler with the `ResourceRequestHandlerFactory`UnRegisterResourceHandler(thisIWebBrowserbrowser,stringurl);//In `WinForms` you can pass a `HtmlString` directly into the constructor and have it load as a Data UrinewChromiumWebBrowser((CefSharp.Web.HtmlString)"<html><body style='background:red;'>Data Uri Test</body></html>");
For more information ondata: encoded URI, which contains the body of the request in the URI itself seehttps://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
Generating your ownData URI would look something like:
conststringhtml="<html><head><title>Test</title></head><body><h1>Html Encoded in URL!</h1></body></html>";varbase64EncodedHtml=Convert.ToBase64String(Encoding.UTF8.GetBytes(html));browser.Load("data:text/html;base64,"+base64EncodedHtml);
RequestHandler andResourceRequestHandlercan be used to intercept network requests, add headers, add post data, read response headers, perform redirects, etc.
For loading resources from memory/disk/database seeResource Handling
The following provides an example of how to only allow loading of resources from a specific host.
publicclassCustomResourceRequestHandler:CefSharp.Handler.ResourceRequestHandler{protectedoverrideCefReturnValueOnBeforeResourceLoad(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,IRequestCallbackcallback){if(Uri.TryCreate(request.Url,uriKind:UriKind.Absolute,outUriresult)){if(string.Compare(result.Host,"www.google.com",StringComparison.OrdinalIgnoreCase)==0){returnCefReturnValue.Continue;}}returnCefReturnValue.Cancel;//If you wanted to perform some sort of database lookup to validate the Url then you should use a Task for processing//System.Threading.Tasks.Task.Run(() =>//{// using (callback)// {// if (Uri.TryCreate(request.Url, uriKind: UriKind.Absolute, out Uri uri))// {// //Query Database here and confirm Url matches// var validUrl = string.Compare(uri.Host, "www.google.com", StringComparison.OrdinalIgnoreCase) == 0;// //Continue if it's a valid Url// callback.Continue(validUrl);// }// else// {// //Failed to parse the Url, we'll cancel// callback.Cancel();// }// }//});//return CefReturnValue.ContinueAsync;}}publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideIResourceRequestHandlerGetResourceRequestHandler(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,boolisNavigation,boolisDownload,stringrequestInitiator,refbooldisableDefaultHandling){//Use our custom ResourceRequestHandler to hook OnBeforeResourceLoadreturnnewCustomResourceRequestHandler();//For the default behaviour, return null//url will be loaded normally.//return null;}}browser.RequestHandler=newCustomRequestHandler();
I strongly advise against usingfile:/// when loading from local disk. Different security restrictions apply and there are many limitations. I'd suggest using aScheme handler or implementing your ownIResourceRequestHandlerFactory. (Loading adata: encoded URI is also pretty handy, specially for theOffScreen project).
If you choose to ignore this advice you'll have to resolve any issues you have usingfile:/// yourself.ceforum is the best resource.
There are two options for configuring a proxy server.
- CEF using the same command-line flags as Google Chrome.
- Seehttps://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-proxy-resolution
- If you specify a proxy using command line args you will be unable to change it at runtime using
SetPreference. - All
ChromiumWebBrowserinstances will share the same proxy
- Proxy settings can be set/changed at runtime usingIRequestContext.SetPreference
- IRequestContext.SetPreference must be called on the
CEF UIthread. UseCef.UIThreadTaskFactory to spawn a task on theCEF UI Thread.IRequestContextHandlermethods are already called on theCEF UIthread so you can callSetPreferencedirectly. Alternatively callIRequestContext.SetPreferenceAsync which can be called on any Thread (will invoke onto the CEF UI Thread if required). - It is possible to specify the proxy settings on a per
Request Contextbasis allowing you to have differentChromiumWebBrowserinstances using different proxies. - Read theRequest Context section below for more details and a basic code example.
- IRequestContext.SetPreference must be called on the
//Create a new RequestContext that users a specific proxy server//By default an in memory cache is usedvarrequestContext=RequestContext.Configure().WithProxyServer("127.0.0.1",8080).Create();//Create a RequestContext with a proxy and cache path//Making sure that CachePath is equal to or a child of CefSettings.RootCachePath//See https://github.com/cefsharp/CefSharp/issues/3111#issuecomment-629713608 for more info on cache pathvarrequestContext=RequestContext.Configure().WithProxyServer("127.0.0.1",8080).WithCachePath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"CefSharp\\Cache")).Create();//Set the proxy at runtime, the RequestContext must be initializedCef.UIThreadTaskFactory.StartNew(delegate{stringerrorMessage;if(!requestContext.CanSetPreference("proxy")){//Unable to set proxy, if you set proxy via command line args it cannot be modified.}success=requestContext.SetProxy("127.0.0.1",8080,outerrorMessage);});
Some Additional examples of setting proxy usingPreferences inCefSharp are available athttp://stackoverflow.com/questions/36095566/cefsharp-3-set-proxy-at-runtime
If the proxy requires authentication theIRequestHandler.GetAuthCredentials() method will be executed with anisProxy value oftrue to retrieve the username and password.
Example of providing UserName/Password for Proxy
browser.RequestHandler=newCustomRequestHandler();publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideboolGetAuthCredentials(IWebBrowserchromiumWebBrowser,IBrowserbrowser,stringoriginUrl,boolisProxy,stringhost,intport,stringrealm,stringscheme,IAuthCallbackcallback){//Cast IWebBrowser to ChormiumWebBrowser if you need to access the UI control//You can Invoke onto the UI Thread if you need to show a dialogvarb=(ChromiumWebBrowser)chromiumWebBrowser;if(isProxy){using(callback){callback.Continue(username:"user",password:"pass");}returntrue;}//Return false to cancel the requestreturnfalse;}}
You can isolate browser instances, including providing custom cache paths, different proxy settings, different cookie managers, and many other things using aRequestContext. InCEF terms the underlying class isCefRequestContext.
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 SetPreferencemust be called on theCEF UI Thread. UseCef.UIThreadTaskFactory to spawn a task on theCEF UI ThreadWinForms: set theRequestContextimmediately after you create your browser instanceOffScreen: PassRequestContextinto the constructorWPF: Set in yourControl/Windowconstructor afterInitializeComponent()is called- SetRequestContextSettings.CachePath to persist cookies, data, localStorage, etc
- RequestContextSettings.CachePathmust be equal to or a child ofCefSettings.RootCachePath seehttps://github.com/cefsharp/CefSharp/issues/3111#issuecomment-629713608
//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=newChromiumWebBrowser();browser.RequestContext=newRequestContext();// Use RequestContext.Configure to create a RequestContext using a// Fluent style interface, can be used to set proxy, set preferences, cachePath// Use WithSharedSettings to share settings with another RequestContext// This one will share the same CachePath as the Global RequestContextvarctx=RequestContext.Configure().WithSharedSettings(Cef.GetGlobalRequestContext()).WithPreference("webkit.webprefs.minimum_font_size",24).Create();browser=newChromiumWebBrowser("www.google.com");browser.RequestContext=ctx;//CustomRequestContextHanler needs to implement `IRequestContextHandler`//Default settings will be used, this means an in-memory cache (no data persisted)browser=newChromiumWebBrowser();browser.RequestContext=newRequestContext(newCustomRequestContextHandler());//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.varrequestContextSettings=newRequestContextSettings{CachePath=cachePath};browser=newChromiumWebBrowser();browser.RequestContext=newRequestContext(requestContextSettings,newCustomRequestContextHandler());
See the project source for more detailed examples.
//When you are already on the CEF UI Thread you can use the followingstringerrorMessage;//You can set most preferences using a `.` notation rather than having to create a complex set of dictionaries.//Disable resizing of text areas//The default is true, you can change to false to disablecontext.SetPreference("webkit.webprefs.text_areas_are_resizable",true,outerrorMessage);//Change the minimum font size to 24ptcontext.SetPreference("webkit.webprefs.minimum_font_size",24,outerrorMessage);//To execute on the CEF UI Thread you can useCef.UIThreadTaskFactory.StartNew(delegate{stringerrorMessage;//Use this to check that settings preferences are working in your code//the browser variable is an instance of ChromiumWebBrowservarsuccess=browser.RequestContext.SetPreference("webkit.webprefs.minimum_font_size",24,outerrorMessage);});
Setting preferences in OnRequestContextInitialized (this approach is recommended for setting a proxy as it will be called before the browser attempts to load any web page)
publicclassRequestContextHandler:CefSharp.Handler.RequestContextHandler{protectedoverrideIResourceRequestHandlerGetResourceRequestHandler(IBrowserbrowser,IFrameframe,IRequestrequest,boolisNavigation,boolisDownload,stringrequestInitiator,refbooldisableDefaultHandling){// Return null for the default behaviour// Implement CefSharp.IResourceRequestHandler or inherit from CefSharp.Handler.ResourceRequestHandler// To handle resource requests at the RequestContext levelreturnnull;}protectedoverridevoidOnRequestContextInitialized(IRequestContextrequestContext){//You can set preferences here on your newly initialized request context.//Note, there is called on the CEF UI Thread, so you can directly call SetPreference//Use this to check that settings preferences are working in your code//You should see the minimum font size is now 24ptstringerrorMessage;varsuccess=requestContext.SetPreference("webkit.webprefs.minimum_font_size",24,outerrorMessage);//Set proxy to fixed_servers//Make sure to include using CefSharp; to access this extension methodvarsetProxySuccess=requestContext.SetProxy("scheme","host",port:8080,outstringerror);//Alternatively you can set the proxy with code similar to the code below//https://stackoverflow.com/questions/36095566/cefsharp-3-set-proxy-at-runtime has some additional examples//var v = new Dictionary<string, object>//{// ["mode"] = "fixed_servers",// ["server"] = "scheme://host:port"//};//string errorMessage;//bool success = requestContext.SetPreference("proxy", v, out errorMessage);}}
The CEF API only exposes limited support for printing. There is currently no support for printing inKiosk Mode (printing to the default without a dialog). The suggested workaround is to print toPDF then use a3rd party application to print thePDF.
If you need better printing support then you should discuss that onceforum. 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
Desktop applications usingWinForms/WPF need to be madeDPI Aware to run correctly on aHigh DPI Display (A display with aDPI Scale set greater than100%).
Note If you mouse cursor is incorrectly positioned in the browser or the browser displays black boxes/border with rendering/resizing then your app needs to be madeDPI Aware. Other parts of your application may also appear blurry or incorrectly-sized.
There are a number of options used to configure the DPI awareness of a process:
- Through an application manifest setting (This is generally the preferred option)
- Viaapp.config (WinForms Only where targeting .Net 4.7 and above).IMPORTANT There is a mistake in the
Microsoftdocumentation seehttps://github.com/dotnet/docs-desktop/issues/1485 - Programmatically through an API call. This is not recommended for WinForms or WPF.
Windows 10 1703 has additional improvements seehttps://blogs.windows.com/windowsdeveloper/2017/04/04/high-dpi-scaling-improvements-desktop-applications-windows-10-creators-update/ for more details.
Starting with the .NET Framework 4.7, Windows Forms includes enhancements for common high DPI and dynamic DPI scenarios. In previous versions of the .NET Framework, you used the app.manifest to add high DPI support. This approach is no longer recommended, since it overrides settings defined on the app.config file. Make sure to readHigh DPI support in Windows Forms for further details from Microsoft.IMPORTANT There is a mistake in theMicrosoft documentation seehttps://github.com/dotnet/docs-desktop/issues/1485
IMPORTANTIf you are targeting.Net 4.7 or aboveMicrosoft recommends configuringDPI Awareness via theapp.config rather thanapp.manifest. Make sure to readHigh DPI support in Windows Forms for further details from Microsoft.IMPORTANT There is a mistake in theMicrosoft documentation seehttps://github.com/dotnet/docs-desktop/issues/1485
Set the default awareness using theapplication manifest. The following example is PerMonitor DPI Aware on Win 10 1703 and above and PerMonitor DPI aware on older version. Make sure you readhttps://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows#dpi-awareness-mode which discusses the differentDPI Awareness options. If you project doesn't already have anapp.manifest use theVisual Studio New Item template to add one rather than doing so manually to ensure the relevant<ApplicationManifest/> entry in yourcsproj/vbproj file is added (it's a special type).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <dpiAware>true/PM</dpiAware> </windowsSettings></application></assembly>For WinForms use the app.config option if possible or app.manifest if using an older version. Setting DPI Awareness programatically is not reccomended.
Add the relevant entries seeTurn on Windows level Per monitor DPI awareness in app.manifest for Microsoft recommendations.
For a working example seehttps://github.com/cefsharp/CefSharp/blob/cefsharp/103/CefSharp.Wpf.Example/app.manifest for a working example.If you project doesn't already have anapp.manifest use theVisual Studio New Item template to add one rather than doing so manually to ensure the relevant<ApplicationManifest/> entry in yourcsproj/vbproj file is added (it's a special type).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <dpiAware>true/PM</dpiAware> </windowsSettings></application></assembly>WPF applications by default have an automatically generatedProgram.Main entry point which makes it harder to programmatically set theDPI. Seehttps://stackoverflow.com/a/26890426/4583726 for how to create aProgram.Main then you can callCef.EnableHighDPISupport();. Thismust be called very early on in your application execution, preferably the first call in your custom Program.Main.
Usingapp.manifest is preferred.
Add the relevantapp.manifest entries or callCef.EnableHighDPISupport().
Setting High DPI in code you can use theCef.EnableHighDPISupport(); helper method. This calls theChromiumbase::win::EnableHighDPISupport(); function. You then have exactly the same settings asChromium uses.
Cef.EnableHighDPISupport();must be called very early on in your application execution, preferably in your application entry point (Program.Main).
Chromium by default performs all rendering in separate sub-process. Specifically theGPU Compositor needs to have aDPI Awareness that matches your main application. Currently the default used by theCefSharp.BrowserSubprocess.exe isPer Monitor DPI Aware. As a workaround use thedisable-gpu-compositing command line arg and theDPI Awareness of your main application process will be used instead of theDPI Awareness specified by theGPU Process (which is used forGPU Compositing). DisablingGPU Compositing may have an impact on performance, whenhttps://github.com/cefsharp/CefSharp/issues/2927 is complete it will be possible to programmatically set theDPI Awareness used by theCefSharp.BrowserSubprocess.exe
varsettings=newCefSettings();settings.CefCommandLineArgs.Add("disable-gpu-compositing");Cef.Initialize(settings);
Alternatively you can try theforce-device-scale-factor command line flag. This should only be used in conjunction with the WinForms implementation.
varsettings=newCefSettings();settings.CefCommandLineArgs.Add("force-device-scale-factor","1");Cef.Initialize(settings);
- https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspxIt's a very long MSDN article, but it's necessary reading if your app needs to be run on high DPI displays.
- https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/
- https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process#setting-default-awareness-with-the-application-manifest
CefSharp by default usessetting.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.
- 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.
- Low level Win32 messages don't propagate between
CEFandWinForms
It is possible to integrate CEF into your app's existing message loop. A very minimal implementation of integrating CEF into your existing message loop involves using a timer that's called 30/60 times per second on the UI thread.
varsettings=newCefSettings();settings.MultiThreadedMessageLoop=false;//This defaults to trueCef.Initialize(settings);-For WPF use[DispatcherTimer](https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=netframework-4.8)-For WinForms use[Timer](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=netframework-4.8)//Set the timer Interval to 30 times per second, can be increased to 60 if required//For WPFtimer.Interval=TimeSpan.FromMilliseconds(1000/30);//For WinFormstimer.Interval=1000/30;timer.Tick+=UiThreadTimerTick;timer.Start();//Before closing your app//Calling Cef.DoMessageLoopWork() after Cef.Shutdown has been called will result in//an access violation, make sure you stop you timer first.timer.Tick-=UiThreadTimerTick;timer.Stop();privatevoid UiThreadTimerTick(objectsender,EventArgse){//Must be called on the UI Thread.Cef.DoMessageLoopWork();}
A more advanced option involves settingCefSettings.ExternalMessagePump = true; and implementingIBrowserProcessHandler.OnScheduleMessagePumpWork. This allowsCEF to notify when it needs to perform work, in some instances this may make your application more responsive. Seehttps://github.com/cefsharp/CefSharp/issues/1748 for additional details. There are more advanced/working examples contained within the projects source.
You can hook the message loop whilst usingMultiThreadedMessageLoop, though this is quite complex. The project source contains an example athttps://github.com/cefsharp/CefSharp/blob/v53.0.0/CefSharp.WinForms.Example/BrowserTabUserControl.cs#L224You can use this method to obtain Win32 mouse messages.
A common request is to control popup creation. ImplementILifeSpanHandler.OnBeforePopup to control how popups are created. To cancel popup creation altogether simplyreturn true;.You can use the defaultLifeSpanHandler implemention to avoid implementing the whole interface.
chromiumWebBrowser.LifeSpanHandler=newLifeSpanHandler();publicclassLifeSpanHandler:CefSharp.Handler.LifeSpanHandler{protectedoverrideboolOnBeforePopup(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,stringtargetUrl,stringtargetFrameName,WindowOpenDispositiontargetDisposition,booluserGesture,IPopupFeaturespopupFeatures,IWindowInfowindowInfo,IBrowserSettingsbrowserSettings,refboolnoJavascriptAccess,outIWebBrowsernewBrowser){newBrowser=null;//Return true to cancel the popup creationreturntrue;}}
You can cancel popup creation and open targetUrl in a newChromiumWebBrowser instance. It is important to note the parent-child relationship will not exist using this method. So in general it's not recommended.
For WinForms you can use the build inLifeSpanHandler implementation to host a popup browser as a TAB.
browser.LifeSpanHandler=CefSharp.WinForms.Handler.LifeSpanHandler.Create().OnPopupCreated((ctrl,targetUrl)=>{//Add popup to new parent control e.g. Tabparent.Controls.Add(ctrl);}).OnPopupDestroyed((ctrl,popupBrowser)=>{//If browser is disposed or the handle has been released then we don't//need to remove the tab (likely removed from menu)if(!ctrl.IsDisposed&&ctrl.IsHandleCreated){parent.Controls.Remove(ctrl);}}).Build();
Starting in M107 there is anexperimental LifeSpanHandler implementation for hosting the popup using a ChromiumWebBrowser instanceAllows you to host the popup using thenewBrowser param inOnBeforePopup in WPF.There are some knownUPSTREAM issues with this approach and will need to be fixed inCEF.
// Open popup (browser) in a new WPF Window// Can be used to host in TabControl/ContentControl/etcbrowser.LifeSpanHandler=CefSharp.Wpf.Experimental.LifeSpanHandler.Create().OnPopupCreated((ctrl,targetUrl,targetFrameName,windowInfo)=>{// Called on the WPF UI Thread// Now is the prefferred time to attach ctrl (ChormiumWebBrowser)// to it's parent}).OnPopupDestroyed((ctrl,popupBrowser)=>{// Remove ctrl (ChormiumWebBrowser) from Visual Tree// Called on the WPF UI Thread}).Build();
Simple code may look something like this:
//There are a number of extension methods that simplify execution, they all work on the main frame//They all exists in the CefSharp.WebBrowserExtensions class, make sure you add "using CefSharp;"browser.ExecuteScriptAsync("document.body.style.background = 'red';");// When executing multiple statements, group them together in an IIFE// https://developer.mozilla.org/en-US/docs/Glossary/IIFE// For Google.com pre-populate the search text box and click the search buttonbrowser.ExecuteJavaScriptAsync("(function(){ document.getElementsByName('q')[0].value = 'CefSharp Was Here!'; document.getElementsByName('btnK')[0].click(); })();");
If you have a web page with multiple frames you can execute the script on the sub frames
browser.GetBrowser().GetFrame("SubFrame").ExecuteJavaScriptAsync("document.body.style.background = 'red';");
JavaScript can only be executed within aV8Context. TheIRenderProcessMessageHandler.OnContextCreated andIRenderProcessMessageHandler.OnContextReleased provide a boundary for when JavaScript can be executed.OnContextCreated/OnContextReleased will be called once per frame, useframe.IsMain to check for the main frame.
It's tempting to start trying to access theDOM inOnFrameLoadStart, whilst theV8Context will have been created and you will be able to execute a script theDOM will not have finished loading. If you need to access theDOM at it's earliest possible point then subscribe toDOMContentLoaded, some examples of executingJavaScript are below.
browser.RenderProcessMessageHandler=newRenderProcessMessageHandler();publicclassRenderProcessMessageHandler: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.voidIRenderProcessMessageHandler.OnContextCreated(IWebBrowserbrowserControl,IBrowserbrowser,IFrameframe){conststringscript="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 loadingif(args.IsLoading==false){browser.ExecuteJavaScriptAsync("alert('All Resources Have Loaded');");}}//Wait for the MainFrame to finish loadingbrowser.FrameLoadEnd+=(sender,args)=>{//Wait for the MainFrame to finish loadingif(args.Frame.IsMain){args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");}};
Some notes about executingJavaScript:
- Scripts are executed at the frame level, and every page has at least one frame (
MainFrame). - The
IWebBrowser.ExecuteScriptAsyncextension method is left for backwards compatibility, you can use it as a shortcut to executejson the main frame. - If a frame does not contain JavaScript then no
V8Contextwill 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
DOMwon't have finished loading whenOnFrameLoadStartis fired IRenderProcessMessageHandler.OnContextCreated/OnContextReleasedare only called for the main frame.
If you need to call(evaluate) JavaScript which returns a value, use one of the following:
//An extension method that evaluates JavaScript against the main frame.JavascriptResponseresponse=awaitbrowser.EvaluateScriptAsync(script);//Evaluate javascript directly against a frameJavascriptResponseresponse=awaitframe.EvaluateScriptAsync(script);//An extension method that evaluates Javascript Promise against the main frame.//Uses Promise.resolve to return the script execution into a promise regardless of the return type//This method differs from EvaluateScriptAsync in that your script **must return** a value//Examples belowJavascriptResponseresponse=awaitbrowser.EvaluateScriptAsPromiseAsync(script);
JavaScript code is executed asynchronously and as such returnsTask which contains error message, result and a success (bool) flag. Here are the fundamentals you need to know when evaluatingJavaScript
- Pleasemake sure you readWhen can I start executing
JavaScript?. - Scripts are executed at the Frame level, and every page has at least one frame (
MainFrame). - Scripts are executed in the render process and are transmitted via
IPC, returnonly the data you require for performance reasons. - Primitive data types int, double, date, bool and string are supported.
- Objects are supported to a degree and will be returned as
IDictionary<string, object>, the use of thedynamickeyword is supported to make accessing the property values easier. - You cannot directly return a
DOMElement (or anything with a cyclic reference), you need to create a new object that has just the information you need to return. - Arrays containing the primitives listed above and objects are supported and will be returned as
IList<object>. Array Likeobjects likeHTMLCollection cannot be returned directly useArray.from to return and array- There are limits to the complexity of the object graph that can be returned (graphs with cyclic references aren't supported currently), in these cases you may need to turn the JavaScript object into a JSON string with the JavaScript
JSON.stringify()method and return that string to your .NET code. Then you can decode that string into a .NET object with something like JSON.NET. SeeMDN JSON.stringify for more details. Seehttps://stackoverflow.com/a/46881092/4583726 for some guidence on usingJSON.stringifywithHTMLElement.
//Start with something simple, the following will return the value 2 as type int//Don't use the `return` keyword//chromiumWebBrowser.EvaluateScriptAsync executes on the main frame, scripts can be executed//per frame if requiredJavascriptResponseresponse=awaitframe.EvaluateScriptAsync("1 + 1");JavascriptResponseresponse=awaitchromiumWebBrowser.EvaluateScriptAsync("1 + 1");varonePlusOne=(int)response.Result;//A javascript IFFE will be evaluated and it's result returned.//https://developer.mozilla.org/en-US/docs/Glossary/IIFE//If you want to execute multiple lines of javascript then an IIFE is recommended to//avoid any variable scoping issuesvarscript=@"(function() { let val = 1 + 1; return val; })();";JavascriptResponseresponse=awaitframe.EvaluateScriptAsync(script);varonePlusOne=(int)response.Result;//If your script uses a Promise then you must use the EvaluateScriptAsPromiseAsync method, it differs slightly//in that you must return the value.//The following will return a Promise that after one second resolves with a simple objecvarscript="return new Promise(function(resolve, reject) { setTimeout(resolve.bind(null, { a: 'CefSharp', b: 42, }), 1000); });"JavascriptResponsejavascriptResponse=awaitbrowser.EvaluateScriptAsPromiseAsync(script);//You can access the object using the dynamic keyword for convenience.dynamicresult=javascriptResponse.Result;vara=result.a;varb=result.b;//EvaluateScriptAsPromiseAsync calls Promise.resolve internally so even if your code doesn't//return a Promise it will still execute successfully.varscript=@"return (function() { return 1 + 1; })();";JavascriptResponseresponse=awaitframe.EvaluateScriptAsPromiseAsync(script);// An example that gets the Document Heightvartask=frame.EvaluateScriptAsync("(function() { var body = document.body, html = document.documentElement; return Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); })();");//Continue execution on the UI Threadtask.ContinueWith(t=>{if(!t.IsFaulted){varresponse=t.Result;EvaluateJavaScriptResult=response.Success?(response.Result??"null"):response.Message;}},TaskScheduler.FromCurrentSynchronizationContext());//HTMLElement/HTMLCollection Examples//As stated above, you cannot return a HTMLElement/HTMLCollection directly.//It's best to return only the data you require, here are some examples of using Array.from to convert a HTMLCollection into an array of objects//which can be returned to your .Net application.//Get all the span elements and create an array that contains their innerTextvarscript=@"Array.from(document.getElementsByTagName('span')).map(x => ( x.innerText));";JavascriptResponseresponse=awaitframe.EvaluateScriptAsync(script);//Get all the a tags and create an array that contains a list of objects//Second param is the mapping functionvarscript=@"Array.from(document.getElementsByTagName('a'), x => ({ innerText : x.innerText, href : x.href }));";JavascriptResponseresponse=awaitframe.EvaluateScriptAsync(script);//List of Links, click represents a function pointer which can be used to execute the link click)//In .Net the https://cefsharp.github.io/api/118.6.x/html/T_CefSharp_IJavascriptCallback.htm is used//to represent the function.varscript=@"Array.from(document.getElementsByTagName('a')).map(x => ({ innerText: x.innerText, click: x.click}));";JavascriptResponseresponse=awaitframe.EvaluateScriptAsync(script);//Execute the following against google.com to get the `I'm Feeling Lucky` button then click the button in .Net//NOTE: This is a simple example, you could return an aggregate object consisting of data from multiple html elements.conststringscript=@"(function(){ let element = document.getElementsByName('btnI')[0]; let obj = {}; obj.id = element.id; obj.nodeValue = element.nodeValue; obj.localName = element.localName; obj.tagName = element.tagName; obj.innerText = element.innerText; obj.click = element.click; obj.attributes = Array.from(element.attributes).map(x => ({name: x.name, value: x.value})); return obj;})();";varjavascriptResponse=awaitchromiumWebBrowser.EvaluateScriptAsync(script);dynamicresult=javascriptResponse.Result;varclickJavascriptCallback=(IJavascriptCallback)result.click;awaitclickJavascriptCallback.ExecuteAsync();//Dispose of the click callback when doneclickJavascriptCallback.Dispose();//Get all rows/cells for the first table in the DOM//dynamic can be used in c# to simplify accessing the resultvarresponse=awaitchromiumWebBrowser.EvaluateScriptAsync(@"(function (){let srcTable = document.getElementsByTagName('table')[0];return Array.from(srcTable.rows, row => Array.from(row.cells, cell => cell.innerText));})();");dynamicarr=response.Result;foreach(dynamicrowinarr){foreach(varcellinrow){vardata=(string)cell;}}
Additional examples seeGist
JavaScript Binding (JSB) allows for communication betweenJavaScript and.Net. There are two distinct implementations available currently, theAsync version and the olderSync version. TheSync version is no longer being actively developed, it relies onWCF which is not available in.Net Core or the upcoming.Net 5.0.
Summary
Uses
Native Chromium IPCto pass messages back and forth between the Browser Process and Render Process., and as such is very fast.Only
methodsare supported as theNative Chromium IPCis message based and can only be used in anasyncfashion (Propertyget/sets cannot be done in an async fashion)Methodscan return simple objects,structsandclassesare supported, only a copy of thePropertiesis transferred toJavaScript. Think of it like making awebservice/ajaxcall, you get a response object.Supports
JavaScript callbacks, through theIJavascriptCallbackAll method calls are non blocking and return a standardJavaScript Promise that can be
awaited.Method names are transformed to
CamelCase(the first letter is transformed to lowercase,MyFunctionbecomesmyFunction) by default. This is configurable by settingbrowser.JavascriptObjectRepository.NameConverter property before registering your objects, set to null to disable name conversion, detailed example below.JavaScript Binding API details the different methods available.
Exceptionsin.Netare caught and thePromisewill berejected.See theAdvanced Async JavaScript Binding (JSB)Wiki,please make sure you finish reading this first.
If you are not familiar with allChromium has to offer when it comes toasync programming here are some very useful articles
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://developers.google.com/web/fundamentals/primers/async-functions
- https://developers.google.com/web/fundamentals/primers/promises
TheCefSharp.BindObjectAsync method is called inJavascript to bind an object.CefSharp.BindObjectAsync returns aPromise that is resolved when bound object(s) are available. Objects are created in the global context (properties of thewindow object). If you callCefSharp.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/WindoworControl) - Step 2 Register an instance of your class with the
JavaScriptObjectRepository - Step 3 CallCefSharp.BindObjectAsync with the name of the object you wish to register, e.g.
CefSharp.BindObjectAsync("boundAsync");(Objects will only be available after thePromisehas resolved.
Only Methods are supported. If you need to set a property, then createGet/Set methods.
Step 1 Create a class
A simple class would look like this:
publicclassBoundObject{publicintAdd(inta,intb){returna+b;}}
Step 2 Register an instance of your class with theJavaScriptObjectRepository
The second part of the process is registering the object with theJavascriptObjectRepository (accessible though thebrowser.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 aChromiumWebBrowser instance. The second options is more flexible and allows objects to beResolved when required.
First Option:
//For async object registration (equivalent to the old RegisterAsyncJsObject)browser.JavascriptObjectRepository.Register("boundAsync",newBoundObject(),true,BindingOptions.DefaultBinder);
Second Option (Preferred):
browser.JavascriptObjectRepository.ResolveObject+=(sender,e)=>{varrepo=e.ObjectRepository;if(e.ObjectName=="boundAsync"){BindingOptionsbindingOptions=null;//Binding options is an optional param, defaults to nullbindingOptions=BindingOptions.DefaultBinder//Use the default binder to serialize values into complex objectsbindingOptions=newBindingOptions{Binder=newMyCustomBinder()});//Specify a custom binderrepo.NameConverter=null;//No CamelCase of Javascript Names//For backwards compatability reasons the default NameConverter doesn't change the case of the objects returned from methods calls.//https://github.com/cefsharp/CefSharp/issues/2442//Use the new name converter to bound object method names and property names of returned objects converted to camelCaserepo.NameConverter=newCamelCaseJavascriptNameConverter();repo.Register("boundAsync",newBoundObject(),isAsync:true,options:bindingOptions);}};
To be notified in.Net when objects have been bound inJavaScript then you can subscribe toObjectBoundInJavascript event or theObjectsBoundInJavascript event (both events are very similar obviously).
browser.JavascriptObjectRepository.ObjectBoundInJavascript+=(sender,e)=>{varname=e.ObjectName;Debug.WriteLine($"Object{e.ObjectName} was bound successfully.");};
Step 3 CallCefSharp.BindObjectAsync
<scripttype="text/javascript">(async function(){awaitCefSharp.BindObjectAsync("boundAsync");//The default is to camel case method names (the first letter of the method name is changed to lowercase)boundAsync.add(16,2).then(function(actualResult){constexpectedResult=18;assert.equal(expectedResult,actualResult,"Add 16 + 2 resulted in "+expectedResult);});})();
When aCefSharp.BindObjectAsync call is made, theJavascriptObjectRepository is queried to see if an object with the given name is already registered, if no matching object is found then theResolveObject event is raised. For calls toCefSharp.BindObjectAsync without any params, then if objects have already been registered then they will all be bound, if no objects have been registered thenResolveObject will be called with theObjectName set toAll.
Only the basics are covered in this section, there are many advanced options check outAdvanced Async Javascript Binding
If you'd like to see a working example then checkoutCefSharp MinimalExample Javascript Binding Demo branch, specificallycommit
THIS IS A LEGACY FEATURE - anyone creating a new application use theAsync JavaScript Binding (JSB) implementation as it's under active development. TheSync version will only receive bug fixes for regressions.
- Uses a
WCFservice for communication (Microsoft have not included support forWCFin.Net Core/.Net 5.0, there's no long term future forWCF). - Supports both methods and properties
- Calls are executed in a
syncfashion and are blocking, long running calls will block theRender Processand make your app appear slow or become unresponsive. - Supports semi complex object structures
- Occasionally the
WCFservice doesn't shutdown cleanly and slows down application shutdown
Binding is initiated by JavaScript, theCefSharp.BindObjectAsync method returns aPromise that is resolved when bound objects are available. Objects are created in the global context (properties of thewindow object). If you callCefSharp.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/WindoworControl) - Step 2 Call
CefSharp.BindObjectAsyncwith the name of the object you wish to register, e.g.CefSharp.BindObjectAsync("myObject");(Objects will only be available after thePromisehas resolved. - Step 3 Register your object with the
JavaScriptObjectRepository
Step 1
publicclassBoundObject{publicstringMyProperty{get;set;}publicvoidMyMethod(){// Do something really cool here.}publicvoidTestCallback(IJavascriptCallbackjavascriptCallback){constinttaskDelay=1500;Task.Run(async()=>{awaitTask.Delay(taskDelay);using(javascriptCallback){//NOTE: Classes are not supported, simple structs arevarresponse=newCallbackResponseStruct("This callback from C# was delayed "+taskDelay+"ms");awaitjavascriptCallback.ExecuteAsync(response);}});}}
Step 2 CallCefSharp.BindObjectAsync, Some examples below ofBinding an object look like:
NOTE This is a two part process, see below the examples for details
<scripttype="text/javascript">(async function(){awaitCefSharp.BindObjectAsync("boundAsync");boundAsync.div(16,2).then(function(actualResult){constexpectedResult=8assert.equal(expectedResult,actualResult,"Divide 16 / 2 resulted in "+expectedResult);});boundAsync.error().catch(function(e){varmsg="Error: "+e+"("+Date()+")";});})();(async () =>{awaitCefSharp.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 objectassert.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 theJavascriptObjectRepository (accessible though thebrowser.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 aChromiumWebBrowser instance. The second options is more flexible and allows objects to beResolved when required.
When aCefSharp.BindObjectAsync call is made, theJavascriptObjectRepository is queries to see if an object with the given name is specified is already registered, if no matching object is found then theResolveObject event is raised. For calls toCefSharp.BindObjectAsync without any params, then if objects have already been registered then they will all be bound, if no objects have been registered thenResolveObject will be called with theObjectName set toAll.
//When abrowser.JavascriptObjectRepository.ResolveObject+=(sender,e)=>{varrepo=e.ObjectRepository;if(e.ObjectName=="boundAsync2"){BindingOptionsbindingOptions=null;//Binding options is an optional param, defaults to nullbindingOptions=BindingOptions.DefaultBinder//Use the default binder to serialize values into complex objects,bindingOptions=newBindingOptions{Binder=newMyCustomBinder()});//No camelcase of names and specify a custom binder//For backwards compatability reasons the default NameConverter doesn't change the case of the objects returned from methods calls.//https://github.com/cefsharp/CefSharp/issues/2442//Use the new name converter to bound object method names and property names of returned objects converted to camelCaserepo.NameConverter=newCamelCaseJavascriptNameConverter();repo.Register("bound",newBoundObject(),isAsync:false,options:bindingOptions);}};
In the actual JS code, you would use the object like this (default is to CamelCase Javascript Names, this is controllable viaJavascriptObjectRepository.NameConverter, see above for an example).
bound.myProperty;// use this syntax to access the propertybound.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 intocamelCase (i.e. the first letter is lower-cased) to make its usage be natural in JavaScript code. To disable setbrowser.JavascriptObjectRepository.NameConverter to null
- 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
IBinderinterface, you can implement your own or use theDefaultBindere.g.repo.Register("bound", new BoundObject(), BindingOptions.DefaultBinder);
This method has been removed. SeeAsync JavaScript Binding (JSB) instead.
This has been removed. SeeSync JavaScript Binding (JSB) instead.
NOTE: Flash is now deprecated andChromium has removed support, seehttps://www.chromium.org/flash-roadmap#TOC-Upcoming-Changes for further details.
The WPF and OffScreen versions use theOffScreen 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 aBitmap in theOffScreen.
For the WPF control, user input (mouse clicks/moves and key presses) is forwarded to the underlying browser though methods on theIBrowserHost interface. It is possible to obtain access to eachBitmap as it's rendered.
A special note should be made about hosting theChromiumWebBrowser within aViewBox. 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 usingRenderOptions.SetBitmapScalingMode. It's best to avoid using aViewBox. You can scale the content contained within the browser by adjusting theZoomLevel, which is by far the most performant option.
For theCefSharp.OffScreen package, each frame is rendered to aBitmap and exposed for use. If you wish to interact with the browser via keyboard or mouse, you can use methods on theIBrowser 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.
- You can specify a custom UserAgent by setting theCefSettings.UserAgent property. This change is applied globally and will be the default for all browser instances.
varsettings=newCefSettings();settings.UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 - Testing 123";Cef.Initialize(settings);
- Starting with version
85, theUserAgentcan be changed at runtime using the DevTools protocol.
using(varclient=chromiumWebBrowser.GetDevToolsClient()){_=client.Network.SetUserAgentOverrideAsync("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 - Testing 123");}
To change theUserAgent at runtime before the browser has made it's first request seehttps://gist.github.com/amaitland/0b05701710064203171bfd05f5002514#file-setuseragentoverrideasync-cs for an example.
- You can modify the
User-AgentHTTP header inIResourceRequestHandler.OnBeforeResourceLoad, which would need to be done for every request. What it doesn't do is change theUserAgentthe browser reports to JavaScript.
publicclassCustomResourceRequestHandler:CefSharp.Handler.ResourceRequestHandler{protectedoverrideCefReturnValueOnBeforeResourceLoad(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,IRequestCallbackcallback){//Set the header by name, override the existing valuerequest.SetHeaderByName("user-agent","MyBrowser CefSharp Browser",true);returnCefReturnValue.Continue;}}publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideIResourceRequestHandlerGetResourceRequestHandler(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,boolisNavigation,boolisDownload,stringrequestInitiator,refbooldisableDefaultHandling){//Where possible only intercept specific Url's//Load https://www.whatismybrowser.com/detect/what-is-my-user-agent in the browser and you'll//see our custom user agentif(request.Url=="https://www.whatismybrowser.com/detect/what-is-my-user-agent"){returnnewCustomResourceRequestHandler();}//Default behaviour, url will be loaded normally.returnnull;}}browser.RequestHandler=newCustomRequestHandler();
TheChromium DevTools are available for debuggingJavascript, you can loadDevTools programatically using thechromiumWebBrowser.ShowDevTools() method (there is a correspondingchromiumWebBrowser.CloseDevTools() method also). Make sure to addusing CefSharp; to your usings statements.If you are new to DevTools check out the officialChrome guide.
Make sure you call this after the browser has been initialized.
usingCefSharp;//**OffScreen**browser.BrowserInitialized+=(sender,args)=>{browser.ShowDevTools();};usingCefSharp;//**WinForms**browser.IsBrowserInitializedChanged+=(sender,args)=>{browser.ShowDevTools();};//**WinForms (alternative)**// If you see the DevTools window flickering// it's likely vying for focus with your form// open DevTools on your UI Thread.usingCefSharp;browser.IsBrowserInitializedChanged+=(sender,args)=>{varbrowser=(ChromiumWebBrowser)sender;browser.BeginInvoke((Action)delegate{browser.ShowDevTools();});};usingCefSharp;//**WPF**browser.IsBrowserInitializedChanged+=(sender,args)=>{if((bool)args.NewValue){browser.ShowDevTools();}};
Alternatively you can connect Chrome to a running instance.
varsettings=newCefSettings();settings.RemoteDebuggingPort=8088;Cef.Initialize(settings);
Openhttp://localhost:8088 in Chrome.
You can capture a screenshot using the DevTools protocol, both PNG (default) and JPG formats are supported. This approach can be used in the WinForms/WPF/OffScreen versions.
- Page.CaptureScreenshotAsync
- DevToolsPage.captureScreenshot API for upstream documentation.
The example below shows capturing with default settings. Additionally you can specify viewport size, quality, and other options.
//Make sure to dispose of our observer registration when done//If you need to make multiple calls then reuse the devtools client//and Dispose when done.using(vardevToolsClient=chromiumWebBrowser.GetDevToolsClient()){varresult=awaitdevToolsClient.Page.CaptureScreenshotAsync();returnresult.Data;}
The example below queries the current size of the content and captures currently rendered content.Note: Pages that delay load their content will need to be forced to render before you can takea whole page screenshot.
using(vardevToolsClient=chromiumWebBrowser.GetDevToolsClient()){//Get the content sizevarlayoutMetricsResponse=awaitdevToolsClient.Page.GetLayoutMetricsAsync();varcontentSize=layoutMetricsResponse.ContentSize;varviewPort=newViewport(){Height=contentSize.Height,Width=contentSize.Width,X=0,Y=0,Scale=1};// https://bugs.chromium.org/p/chromium/issues/detail?id=1198576#c17varresult=awaitdevToolsClient.Page.CaptureScreenshotAsync(clip:viewPort,fromSurface:true,captureBeyondViewport:true);returnresult.Data;}
Additionally the DevTools protocol can be used to stream frames usingPage.screencastFrame.Page Domain API doc for methods/events currently avaliable.
BothOffscreen andWPF useOffscreen 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)
See exampleabove
When using the32bit version make sure your application is large address aware (handle addresses larger than 2gb)
As per suggestion inhttp://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
- https://www.nuget.org/packages/LargeAddressAware/
The default x86 SubProcess shipped with CefSharp is large address aware, you should make your application large address 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 athttp://magpcss.org/ceforum/viewtopic.php?f=6&t=15120
There are two methods for Loading a Url withPost Data, the first is to modify an existingRequest. In the example below we'll add post data to theRequest if we visithttp://httpbin.org/post
publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideIResourceRequestHandlerGetResourceRequestHandler(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,boolisNavigation,boolisDownload,stringrequestInitiator,refbooldisableDefaultHandling){//Where possible only intercept specific Url's//Load http://httpbin.org/post in the browser and you'll//see the post dataif(request.Url=="http://httpbin.org/post"){returnnewCustomResourceRequestHandler();}//Default behaviour, url will be loaded normally.returnnull;}}publicclassCustomResourceRequestHandler:CefSharp.Handler.ResourceRequestHandler{protectedoverrideCefReturnValueOnBeforeResourceLoad(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,IRequestCallbackcallback){//Modify the request to add post data//Make sure to read https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POSTvarpostData=newPostData();postData.AddData("test=123&data=456");request.Method="POST";request.PostData=postData;//Set the Content-Type header to whatever suites your requirementrequest.SetHeaderByName("Content-Type","application/x-www-form-urlencoded",true);//Set additional Request headers as required.returnCefReturnValue.Continue;}}//Load http://httpbin.org/post in the browser to see the post databrowser=newChromiumWebBrowser("http://httpbin.org/post");browser.RequestHandler=newCustomRequestHandler();
The second method is to useIFrame.LoadRequest, this method can only be used if you have first successfully performed to a domain of the same origin. For example, you must navigate to sayhttp://httpbin.org/ before you can callIFrame.LoadRequest forhttp://httpbin.org/post.
publicvoidLoadCustomRequestExample(){varframe=browser.GetMainFrame();//Create a new request knowing we'd like to use PostDatavarrequest=frame.CreateRequest(initializePostData:true);request.Method="POST";request.Url="http://httpbin.org/post";//Set AllowStoredCredentials so cookies are sent with Requestrequest.Flags=UrlRequestFlags.AllowStoredCredentials;request.PostData.AddData("test=123&data=456");frame.LoadRequest(request);}
Thebrowser.LoadUrlWithPostData extension method can be used for simple cases, it callsLoadRequest and the same restrictions regarding having performed a successful navigation apply.
By defaultCefSettings.Locale will dictate which dictionary is used, the default beingen-US. It is possible to configure many aspects of spell checkingenable/disable on the fly, changedictionary on the fly, even enable multiple dictionaries. UseRequestContext.SetPreference (See theRequestContext section of this document for details on how to set a preference).
Spellcheck can only be changed dynamically usingspellcheck.dictionaries preference (important to use the plural version)https://github.com/chromiumembedded/cef/issues/2222#issuecomment-1465009296
Here are some useful links
http://magpcss.org/ceforum/viewtopic.php?f=6&t=14911&p=33882&hilit=spellcheck#p33882https://cs.chromium.org/chromium/src/components/spellcheck/browser/pref_names.cc?type=cs&q=%22spellcheck.dictionary%22&l=11https://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, seehttps://magpcss.org/ceforum/viewtopic.php?f=6&t=16508#p40684
Is enabled by default in newer builds seehttps://www.chromestatus.com/feature/5453022515691520
For older versions you need to manually enableWebAssembly seehttps://bitbucket.org/chromiumembedded/cef/issues/2101/add-webassembly-support
settings.javascript_flags translates tosettings.JavascriptFlags = "--expose-wasm";
Capturing unmanaged exceptions is difficult andCEF 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 ofCefSharp 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-applicationshttps://msdn.microsoft.com/en-us/library/windows/desktop/ms680634(v=vs.85).aspxhttps://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,8243b844777a16c3https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,3192
Capturing unhandled exceptions in a mixed native/CLR environmenthttp://www.ikriv.com/blog/?p=1440
CefSharp has a very simple class that checks to see if the all the relevant unmanaged resources are present.
//Perform dependency check to make sure all relevant resources are in our output directory.//https://cefsharp.github.io/api/118.6.x/html/M_CefSharp_Cef_Initialize_1.htmCef.Initialize(settings,performDependencyCheck:true,browserProcessHandler:null);//Manually check//https://cefsharp.github.io/api/118.6.x/html/T_CefSharp_DependencyChecker.htmDependencyChecker.AssertAllDependenciesPresent(cefSettings.Locale,cefSettings.LocalesDirPath,cefSettings.ResourcesDirPath,cefSettings.PackLoadingDisabled,cefSettings.BrowserSubprocessPath);
It's not100% 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 subsequentlyCefSharp only supports freely available audio and video codecs. To see what the version ofCefSharp you are working with supports openhttp://html5test.com/ in aChromiumWebBrowser instance.
MP3patent has expired and as a result is supported in version65.0.0on wards.H264/AACare classed asProprietary Codecsand are not supported, they require you to obtain a license. Sites likeNetflix/Twitter/InstagramuseH264and as a result their videos won't play. Seehttps://www.fsf.org/licensing/h264-patent-license for some comments from theFree Software Foundationon the subject.
CompilingCEF with support forH264/AAC is outside the scope of this project. The following are provided for reference only, please don't ask for support for compilingCEF.
- http://magpcss.org/ceforum/viewtopic.php?f=6&t=13515
- https://github.com/cefsharp/CefSharp/issues/1934#issuecomment-279305821
- https://github.com/mitchcapper/CefSharpDockerfiles
TheWinForms version has built in support for onscreen keyboard, it has been reported that on occasion it doesn't always popup correctly, usingdisable-usb-keyboard-detect command line argumenthttps://github.com/cefsharp/CefSharp/issues/1691#issuecomment-323603277 has reported to resolve this problem.
TheWPF does not have built in support for onscreen (virtual) keyboard, starting with version73 a newVirtualKeyboardRequested event now provides notification when you application should display a virtual keyboard. Unfortunately it's difficult to provide a default implementation that supportsWindows 7, 8.1 and 10 as there is no.Net API for display a virtual keyboard. AWindows 10 Only example was added inhttps://github.com/cefsharp/CefSharp/commit/0b57e526158e57e522d46671404c557256529416 If you need to supportWindows 8 and 10 thenhttps://github.com/maximcus/WPFTabTip might be useful. ForWindows 7https://stackoverflow.com/questions/1168203/incorporating-the-windows-7-onscreen-keyboard-into-a-wpf-app has some suggestions.
The following example callsProcess.Start to open amailto link with the default associated mail client.
publicclassCustomRequestHandler:CefSharp.Handler.RequestHandler{protectedoverrideboolOnBeforeBrowse(IWebBrowserchromiumWebBrowser,IBrowserbrowser,IFrameframe,IRequestrequest,booluserGesture,boolisRedirect){//For security reasons you should perform validation on the url to confirm that it's safe before proceeding.if(request.Url.StartsWith("mailto:")){//Cancel the request and defer to Windows to open the Url with the associated//Handler (in this case the default email client should be opened).Process.Start(request.Url);returntrue;}returnbase.OnBeforeBrowse(chromiumWebBrowser,browser,frame,request,userGesture,isRedirect);}}browser.RequestHandler=newCustomRequestHandler();
It's possible to create/retrieve/update/delete cookies via theICookieManager. By default all browsers share the sameICookieManager which can be accessed viaCef.GetGlobalCookieManager.The cookie managers storage is created in an async fashion, whilst this method may return a cookie manager instance, there may be a short delay before you can create/retrieve/update/delete cookies.
UsingICookieManager.SetCookie.
- Using the name of an existing cookie will update it's value/expiry/etc.
- Cookie will be created in an async fashion, seeICookieManager.SetCookieAsync below if you need to wait for the cookie to be created.
usingCefSharp;varcookieManager=Cef.GetGlobalCookieManager();//success == false if an invalid URL is specified or if cookies cannot be accessed.//The cookie itself will be created/updated async, use SetCookieAsync (example below) to be sure//the cookie has been created/updated successfully.varsuccess=cookieManager.SetCookie("custom://cefsharp/home.html",newCookie{Name="CefSharpTestCookie",Value="ILikeCookies",Expires=DateTime.Now.AddDays(1)});
Cookies are created in an async fashion, usingICookieManager.SetCookieAsync if you need to make sure the cookie has been created before continuing then use
usingCefSharp;varcookieManager=Cef.GetGlobalCookieManager();//If success == true the cookie will have been created/updated successfully.varsuccess=awaitcookieManager.SetCookieAsync("custom://cefsharp/home.html",newCookie{Name="CefSharpTestCookie",Value="ILikeCookies",Expires=DateTime.Now.AddDays(1)});
UsingICookieManager.DeleteCookiesAsync orICookieManager.DeleteCookies you can delete just a single cookie by name, all cookies for a Url or all cookies for theICookieManager
usingCefSharp;varcookieManager=Cef.GetGlobalCookieManager();//Delete cookies by nameawaitcookieManager.DeleteCookiesAsync(url:"custom://cefsharp/home.html",name:"CefSharpTestCookie");//Delete all cookies for a UrlawaitcookieManager.DeleteCookiesAsync(url:"custom://cefsharp/home.html");//Delete all cookies for the cookie managerawaitcookieManager.DeleteCookiesAsync();
Get all cookies for a Url usingICookieManager.VisitUrlCookiesAsync
usingCefSharp;varcookieManager=Cef.GetGlobalCookieManager();//Using awaitvarresult=awaitcookieManager.VisitUrlCookiesAsync("custom://cefsharp/home.html",includeHttpOnly:false);varcookieCount=result.Count;//Using ContinueWithcookieManager.VisitUrlCookiesAsync("custom://cefsharp/home.html",includeHttpOnly:false).ContinueWith(t=>{if(t.Status==TaskStatus.RanToCompletion){//All the cookies for the specified URLvarcookies=t.Result;foreach(varcookieincookies){Debug.WriteLine("CookieName: "+cookie.Name);}}else{Debug.WriteLine("No Cookies found");}});
If you are using multipleRequestContexts then you can use theIWebBrowser.GetCookieManager extension method.(All instances ofChromiumWebBrowser implementIWebBrowser). This will also work when using the default Global Cookie Manager.
usingCefSharp;varcookieManager=chromiumWebBrowser.GetCookieManager();varresult=awaitcookieManager.VisitUrlCookiesAsync("custom://cefsharp/home.html",includeHttpOnly:false);varcookieCount=result.Count;
If you need to wait for the Cookie Store to initialize so you can set cookies before your firstChromiumWebBrowser is created there are a few options including:
usingCefSharp;publicclassBrowserProcessHandler:IBrowserProcessHandler{voidIBrowserProcessHandler.OnContextInitialized(){//The Global CookieManager has been initialized, you can now create/retrieve/update/delete cookiesvarcookieManager=Cef.GetGlobalCookieManager();}voidIBrowserProcessHandler.OnScheduleMessagePumpWork(longdelay){}publicvirtualvoidDispose(){}}varsettings=newCefSettings(){//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist dataCachePath=Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"CefSharp\\Cache")};Cef.Initialize(settings,performDependencyCheck:true,browserProcessHandler:newBrowserProcessHandler());
AlternativelyCef.GetGlobalCookieManager takes aICompletionCallbackparam which can be used to wait for the cookie store to be initialized.TaskCompletionCallback is a default implement that exposes a Task.
usingCefSharp;varcallback=newTaskCompletionCallback();varcookieManager=Cef.GetGlobalCookieManager(callback);awaitcallback.Task.ConfigureAwait(false);returncookieManager;
ImplementIRequestHandler.OnCertificateError - this method will be called for every invalid certificate. If you only wish to override a few methods ofIRequestHandler then you can inherit fromRequestHandler and override the methods you are interested in specifically, in this caseOnCertificateError
//Make sure you assign your RequestHandler instance to the `ChromiumWebBrowser`browser.RequestHandler=newExampleRequestHandler();publicclassExampleRequestHandler:RequestHandler{protectedoverrideboolOnCertificateError(IWebBrowserchromiumWebBrowser,IBrowserbrowser,CefErrorCodeerrorCode,stringrequestUrl,ISslInfosslInfo,IRequestCallbackcallback){//NOTE: I suggest wrapping callback in a using statement or explicitly execute callback.Dispose as callback wraps an unmanaged resource.//Example #1//Return true and call IRequestCallback.Continue() at a later time to continue or cancel the request.//In this instance we'll use a Task, typically you'd invoke a call to the UI Thread and display a Dialog to the userTask.Run(()=>{//NOTE: When executing the callback in an async fashion need to check to see if it's disposedif(!callback.IsDisposed){using(callback){//We'll allow the expired certificate from badssl.comif(requestUrl.ToLower().Contains("https://expired.badssl.com/")){callback.Continue(true);}else{callback.Continue(false);}}}});returntrue;//Example #2//Execute the callback and return true to immediately allow the invalid certificate//callback.Continue(true); //Callback will Dispose it's self once exeucted//return true;//Example #3//Return false for the default behaviour (cancel request immediately)//callback.Dispose(); //Dispose of callback//return false;}}
Set ignore-certificate-errors command line arg. This will ignore all certificate errors, no calls toIRequestHandler.OnCertificateError will be made.
varsettings=newCefSettings();settings.CefCommandLineArgs.Add("ignore-certificate-errors");Cef.Initialize(settings);
To enable saving of downloaded files you need to assign an instance ofIDownloadHandler to theChromiumWebBrowser.DownloadHandler property.
The examples below will use theFluent implementation for ease of use, you can use any of the following:
- Create an inline implementing usingCefSharp.Fluent.DownloadHandler.Create
- Create a class that inherits fromDownloadHandler and override only the methods you require.
- Create a class that directly implementsIDownloadHandler interface.
usingCefSharp.Fluent;//Save all files to temp folder, update to whatever suites you.varuserTempPath=System.IO.Path.GetTempPath();chromiumWebBrowser.DownloadHandler=DownloadHandler.UseFolder(userTempPath,(chromiumBrowser,browser,downloadItem,callback)=>{// To access the UI you'll need to use the Dispatcher.InvokeAsync (WPF)// or Control.BeginInvoke (WinForms)if(downloadItem.IsComplete){//TODO: Add code here}elseif(downloadItem.IsCancelled){//TODO: Add code here}});
usingCefSharp.Fluent;chromiumWebBrowser.DownloadHandler=Fluent.DownloadHandler.AskUser((chromiumBrowser,browser,downloadItem,callback)=>{// To access the UI you'll need to use the Dispatcher.InvokeAsync (WPF)// or Control.BeginInvoke (WinForms)if(downloadItem.IsComplete){//TODO: Add code here}elseif(downloadItem.IsCancelled){//TODO: Add code here}});
A simple WPF example might look something like the following:
usingCefSharp.Fluent;browser.DownloadHandler=CefSharp.Fluent.DownloadHandler.Create().CanDownload((chromiumWebBrowser,browser,url,requestMethod)=>{//All all downloadsreturntrue;}).OnBeforeDownload((chromiumWebBrowser,browser,downloadItem,callback)=>{UpdateDownloadAction("OnBeforeDownload",downloadItem);callback.Continue("",showDialog:true);}).OnDownloadUpdated((chromiumWebBrowser,browser,downloadItem,callback)=>{UpdateDownloadAction("OnDownloadUpdated",downloadItem);}).Build();privatevoidUpdateDownloadAction(stringdownloadAction,DownloadItemdownloadItem){// Invoke on the WPF UI Threadthis.Dispatcher.InvokeAsync(()=>{varviewModel=(BrowserTabViewModel)this.DataContext;viewModel.LastDownloadAction=downloadAction;viewModel.DownloadItem=downloadItem;});}
For downloads that open a popup window it maybe desirable to hide that popup once the download has started, closing once complete or cancelled.
[DllImport("user32.dll")][return:MarshalAs(UnmanagedType.Bool)]privatestaticexternboolIsWindowVisible(IntPtrhWnd);[DllImport("user32.dll")]privatestaticexternboolShowWindow(IntPtrhWnd,intnCmdShow);vartempPath=System.IO.Path.GetTempPath();browser.DownloadHandler=Fluent.DownloadHandler.UseFolder(tempPath,(chromiumBrowser,browser,downloadItem,callback)=>{// To access the UI you'll need to use the Dispatcher.InvokeAsync (WPF)// or Control.BeginInvoke (WinForms)if(downloadItem.IsComplete||downloadItem.IsCancelled){if(browser.IsPopup&&!browser.HasDocument){browser.GetHost().CloseBrowser(true);}}//TODO: You may wish to customise this condition to better suite your//requirements.elseif(downloadItem.ReceivedBytes<100){varpopupHwnd=browser.GetHost().GetWindowHandle();varvisible=IsWindowVisible(popupHwnd);if(visible){constintSW_HIDE=0;ShowWindow(popupHwnd,SW_HIDE);}}});