2017-10-21 04:37:01 +02:00
using System ;
2017-10-23 19:08:10 +02:00
using System.Threading.Tasks ;
2025-12-07 11:20:43 +01:00
using ElectronNET.API.Entities ;
using ElectronNET.Common ;
2025-11-09 03:50:24 +01:00
2025-11-05 15:06:41 +00:00
// ReSharper disable InconsistentNaming
2017-10-21 04:37:01 +02:00
2023-03-24 02:17:09 +01:00
namespace ElectronNET.API ;
/// <summary>
/// Render and control web pages.
/// </summary>
2025-11-15 08:05:31 +01:00
public class WebContents : ApiBase
2017-10-21 04:37:01 +02:00
{
2025-11-12 10:43:32 +00:00
protected override SocketTaskEventNameTypes SocketTaskEventNameType = > SocketTaskEventNameTypes . DashesLowerFirst ;
protected override SocketTaskMessageNameTypes SocketTaskMessageNameType = > SocketTaskMessageNameTypes . DashesLowerFirst ;
protected override SocketEventNameTypes SocketEventNameType = > SocketEventNameTypes . CamelCase ;
2017-10-21 04:37:01 +02:00
/// <summary>
2023-03-24 02:17:09 +01:00
/// Gets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
2025-11-12 10:43:32 +00:00
public override int Id { get ; protected set ; }
2023-03-24 02:17:09 +01:00
/// <summary>
/// Manage browser sessions, cookies, cache, proxy settings, etc.
/// </summary>
public Session Session { get ; internal set ; }
/// <summary>
/// Emitted when the renderer process crashes or is killed.
2017-10-21 04:37:01 +02:00
/// </summary>
2023-03-24 02:17:09 +01:00
public event Action < bool > OnCrashed
2017-10-21 04:37:01 +02:00
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-03-24 02:17:09 +01:00
}
/// <summary>
/// Emitted when the navigation is done, i.e. the spinner of the tab has
/// stopped spinning, and the onload event was dispatched.
/// </summary>
public event Action OnDidFinishLoad
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-03-24 02:17:09 +01:00
}
2017-10-21 04:37:01 +02:00
2023-11-03 00:23:37 +01:00
/// <summary>
/// Emitted when any frame (including main) starts navigating.
/// </summary>
public event Action < string > OnDidStartNavigation
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-11-03 00:23:37 +01:00
}
/// <summary>
/// Emitted when a main frame navigation is done.
/// This event is not emitted for in-page navigations, such as clicking anchor links or updating the window.location.hash. Use did-navigate-in-page event for this purpose.
/// </summary>
public event Action < OnDidNavigateInfo > OnDidNavigate
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-11-03 00:23:37 +01:00
}
/// <summary>
/// Emitted when a server side redirect occurs during navigation. For example a 302 redirect.
/// This event will be emitted after OnDidStartNavigation and always before the OnDidRedirectNavigation event for the same navigation.
/// </summary>
public event Action < string > OnWillRedirect
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-11-03 00:23:37 +01:00
}
/// <summary>
/// Emitted after a server side redirect occurs during navigation. For example a 302 redirect.
/// </summary>
public event Action < string > OnDidRedirectNavigation
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-11-03 00:23:37 +01:00
}
/// <summary>
/// This event is like OnDidFinishLoad but emitted when the load failed.
/// </summary>
public event Action < OnDidFailLoadInfo > OnDidFailLoad
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-11-03 00:23:37 +01:00
}
2023-03-24 02:17:09 +01:00
/// <summary>
/// Emitted when an input event is sent to the WebContents.
/// </summary>
public event Action < InputEvent > InputEvent
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-03-24 02:17:09 +01:00
}
2017-10-21 04:37:01 +02:00
2023-09-24 16:36:41 +02:00
/// <summary>
/// Emitted when the document in the top-level frame is loaded.
/// </summary>
public event Action OnDomReady
{
2025-11-12 10:43:32 +00:00
add = > AddEvent ( value , Id ) ;
remove = > RemoveEvent ( value , Id ) ;
2023-09-24 16:36:41 +02:00
}
2023-03-24 02:17:09 +01:00
internal WebContents ( int id )
{
Id = id ;
Session = new Session ( id ) ;
}
2019-12-15 09:56:14 +01:00
2023-03-24 02:17:09 +01:00
/// <summary>
/// Opens the devtools.
/// </summary>
public void OpenDevTools ( )
{
2025-12-04 17:52:01 +00:00
BridgeConnector . Socket . Emit ( "webContents-openDevTools" , Id ) ;
2023-03-24 02:17:09 +01:00
}
2019-12-15 09:56:14 +01:00
2023-03-24 02:17:09 +01:00
/// <summary>
/// Opens the devtools.
/// </summary>
/// <param name="openDevToolsOptions"></param>
public void OpenDevTools ( OpenDevToolsOptions openDevToolsOptions )
{
2025-12-04 17:52:01 +00:00
BridgeConnector . Socket . Emit ( "webContents-openDevTools" , Id , openDevToolsOptions ) ;
}
/// <summary>
/// Toggles the devtools.
/// </summary>
public void ToggleDevTools ( )
{
BridgeConnector . Socket . Emit ( "webContents-toggleDevTools" , Id ) ;
}
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Closes the devtools.
/// </summary>
public void CloseDevTools ( )
{
BridgeConnector . Socket . Emit ( "webContents-closeDevTools" , Id ) ;
}
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Returns boolean - Whether the devtools is opened.
/// </summary>
/// <returns></returns>
public bool IsDevToolsOpened ( )
{
return Task . Run ( ( ) = > InvokeAsync < bool > ( ) ) . Result ;
}
/// <summary>
/// Returns boolean - Whether the devtools view is focused.
/// </summary>
/// <returns></returns>
public bool IsDevToolsFocused ( )
{
return Task . Run ( ( ) = > InvokeAsync < bool > ( ) ) . Result ;
2023-03-24 02:17:09 +01:00
}
2019-12-15 09:56:14 +01:00
2023-03-24 02:17:09 +01:00
/// <summary>
/// Get system printers.
/// </summary>
/// <returns>printers</returns>
2025-12-07 11:20:43 +01:00
public Task < PrinterInfo [ ] > GetPrintersAsync ( ) = > this . InvokeAsyncWithTimeout < PrinterInfo [ ] > ( 8. seconds ( ) ) ;
2017-10-23 19:08:10 +02:00
2023-03-24 02:17:09 +01:00
/// <summary>
/// Prints window's web page.
/// </summary>
/// <param name="options"></param>
/// <returns>success</returns>
2025-11-15 10:09:51 +01:00
public Task < bool > PrintAsync ( PrintOptions options ) = > this . InvokeAsync < bool > ( options ) ;
2025-11-15 08:05:31 +01:00
2025-11-12 10:43:32 +00:00
/// <summary>
/// Prints window's web page.
/// </summary>
/// <returns>success</returns>
2025-11-15 10:09:51 +01:00
public Task < bool > PrintAsync ( ) = > this . InvokeAsync < bool > ( string . Empty ) ;
2018-03-16 22:32:56 +03:00
2023-03-24 02:17:09 +01:00
/// <summary>
/// Prints window's web page as PDF with Chromium's preview printing custom
2025-11-15 08:05:31 +01:00
/// settings.The landscape will be ignored if @page CSS at-rule is used in the web page.
/// By default, an empty options will be regarded as: Use page-break-before: always;
2023-03-24 02:17:09 +01:00
/// CSS style to force to print to a new page.
/// </summary>
/// <param name="path"></param>
/// <param name="options"></param>
/// <returns>success</returns>
public Task < bool > PrintToPDFAsync ( string path , PrintToPDFOptions options = null )
{
2025-11-10 10:40:51 +01:00
var tcs = new TaskCompletionSource < bool > ( ) ;
2018-03-16 22:32:56 +03:00
2025-11-10 10:40:51 +01:00
BridgeConnector . Socket . Once < bool > ( "webContents-printToPDF-completed" , tcs . SetResult ) ;
2020-04-23 03:29:52 +02:00
2025-11-09 03:50:24 +01:00
if ( options = = null )
2023-03-24 02:17:09 +01:00
{
BridgeConnector . Socket . Emit ( "webContents-printToPDF" , Id , "" , path ) ;
}
else
2020-04-23 03:29:52 +02:00
{
2025-11-09 12:05:07 +01:00
BridgeConnector . Socket . Emit ( "webContents-printToPDF" , Id , options , path ) ;
2020-04-23 03:29:52 +02:00
}
2025-11-10 10:40:51 +01:00
return tcs . Task ;
2023-03-24 02:17:09 +01:00
}
2023-09-24 16:36:49 +02:00
/// <summary>
/// Evaluates script code in page.
/// </summary>
/// <param name="code">The code to execute.</param>
/// <param name="userGesture">if set to <c>true</c> simulate a user gesture.</param>
/// <returns>The result of the executed code.</returns>
/// <remarks>
/// <para>
/// In the browser window some HTML APIs like `requestFullScreen` can only be
/// invoked by a gesture from the user. Setting `userGesture` to `true` will remove
/// this limitation.
/// </para>
/// <para>
/// Code execution will be suspended until web page stop loading.
/// </para>
/// </remarks>
2025-11-09 12:05:07 +01:00
public Task < T > ExecuteJavaScriptAsync < T > ( string code , bool userGesture = false )
2023-09-24 16:36:49 +02:00
{
2025-11-10 10:40:51 +01:00
var tcs = new TaskCompletionSource < T > ( ) ;
2023-09-24 16:36:49 +02:00
2025-11-10 10:40:51 +01:00
BridgeConnector . Socket . Once < T > ( "webContents-executeJavaScript-completed" , tcs . SetResult ) ;
2023-09-24 16:36:49 +02:00
BridgeConnector . Socket . Emit ( "webContents-executeJavaScript" , Id , code , userGesture ) ;
2025-11-10 10:40:51 +01:00
return tcs . Task ;
2023-09-24 16:36:49 +02:00
}
2023-03-24 02:17:09 +01:00
/// <summary>
/// Is used to get the Url of the loaded page.
/// It's usefull if a web-server redirects you and you need to know where it redirects. For instance, It's useful in case of Implicit Authorization.
/// </summary>
/// <returns>URL of the loaded page</returns>
public Task < string > GetUrl ( )
{
2025-11-10 10:40:51 +01:00
var tcs = new TaskCompletionSource < string > ( ) ;
2020-04-23 03:29:52 +02:00
2025-11-12 10:43:32 +00:00
BridgeConnector . Socket . Once < string > ( "webContents-getUrl" + Id , tcs . SetResult ) ;
2023-03-24 02:17:09 +01:00
BridgeConnector . Socket . Emit ( "webContents-getUrl" , Id ) ;
2020-04-23 03:29:52 +02:00
2025-11-10 10:40:51 +01:00
return tcs . Task ;
2023-03-24 02:17:09 +01:00
}
2020-04-23 03:29:52 +02:00
2023-03-24 02:17:09 +01:00
/// <summary>
2025-11-15 08:05:31 +01:00
/// The async method will resolve when the page has finished loading,
2023-03-24 02:17:09 +01:00
/// and rejects if the page fails to load.
///
/// A noop rejection handler is already attached, which avoids unhandled rejection
/// errors.
///
/// Loads the `url` in the window. The `url` must contain the protocol prefix, e.g.
/// the `http://` or `file://`. If the load should bypass http cache then use the
/// `pragma` header to achieve it.
/// </summary>
/// <param name="url"></param>
public Task LoadURLAsync ( string url )
{
return LoadURLAsync ( url , new LoadURLOptions ( ) ) ;
}
2020-04-23 03:29:52 +02:00
2023-03-24 02:17:09 +01:00
/// <summary>
2025-11-15 08:05:31 +01:00
/// The async method will resolve when the page has finished loading,
2023-03-24 02:17:09 +01:00
/// and rejects if the page fails to load.
///
/// A noop rejection handler is already attached, which avoids unhandled rejection
/// errors.
///
/// Loads the `url` in the window. The `url` must contain the protocol prefix, e.g.
/// the `http://` or `file://`. If the load should bypass http cache then use the
/// `pragma` header to achieve it.
/// </summary>
/// <param name="url"></param>
/// <param name="options"></param>
public Task LoadURLAsync ( string url , LoadURLOptions options )
{
2025-11-17 13:58:22 +01:00
var tcs = new TaskCompletionSource ( ) ;
2020-04-23 03:29:52 +02:00
2025-11-10 10:40:51 +01:00
BridgeConnector . Socket . Once ( "webContents-loadURL-complete" + Id , ( ) = >
2021-04-26 12:41:14 -04:00
{
2023-03-24 02:17:09 +01:00
BridgeConnector . Socket . Off ( "webContents-loadURL-error" + Id ) ;
2025-11-17 13:58:22 +01:00
tcs . SetResult ( ) ;
2023-03-24 02:17:09 +01:00
} ) ;
2021-04-26 12:41:14 -04:00
2025-11-15 08:05:31 +01:00
BridgeConnector . Socket . Once < string > ( "webContents-loadURL-error" + Id , ( error ) = > { tcs . SetException ( new InvalidOperationException ( error ) ) ; } ) ;
2023-03-24 02:17:09 +01:00
2025-11-09 12:05:07 +01:00
BridgeConnector . Socket . Emit ( "webContents-loadURL" , Id , url , options ) ;
2023-03-24 02:17:09 +01:00
2025-11-10 10:40:51 +01:00
return tcs . Task ;
2017-10-21 04:37:01 +02:00
}
2023-03-24 02:17:09 +01:00
/// <summary>
/// Inserts CSS into the web page.
/// See: https://www.electronjs.org/docs/api/web-contents#contentsinsertcsscss-options
/// Works for both BrowserWindows and BrowserViews.
/// </summary>
/// <param name="isBrowserWindow">Whether the webContents belong to a BrowserWindow or not (the other option is a BrowserView)</param>
/// <param name="path">Absolute path to the CSS file location</param>
public void InsertCSS ( bool isBrowserWindow , string path )
{
BridgeConnector . Socket . Emit ( "webContents-insertCSS" , Id , isBrowserWindow , path ) ;
}
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Returns number - The current zoom factor.
/// </summary>
/// <returns></returns>
public Task < double > GetZoomFactorAsync ( ) = > InvokeAsync < double > ( ) ;
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Changes the zoom factor to the specified factor.
/// Zoom factor is zoom percent divided by 100, so 300% = 3.0.
/// The factor must be greater than 0.0.
/// </summary>
/// <param name="factor"></param>
public void SetZoomFactor ( double factor )
{
BridgeConnector . Socket . Emit ( "webContents-setZoomFactor" , Id , factor ) ;
}
/// <summary>
/// Returns number - The current zoom level.
/// </summary>
/// <returns></returns>
public Task < int > GetZoomLevelAsync ( ) = > InvokeAsync < int > ( ) ;
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Changes the zoom level to the specified level.
/// The original size is 0 and each increment above or below represents zooming 20% larger or smaller to default limits of 300% and 50% of original size, respectively.
/// </summary>
/// <param name="level"></param>
public void SetZoomLevel ( int level )
{
BridgeConnector . Socket . Emit ( "webContents-setZoomLevel" , Id , level ) ;
}
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Sets the maximum and minimum pinch-to-zoom level.
/// </summary>
/// <param name="minimumLevel"></param>
/// <param name="maximumLevel"></param>
public Task SetVisualZoomLevelLimitsAsync ( int minimumLevel , int maximumLevel )
{
var tcs = new TaskCompletionSource ( ) ;
BridgeConnector . Socket . Once ( "webContents-setVisualZoomLevelLimits-completed" , tcs . SetResult ) ;
BridgeConnector . Socket . Emit ( "webContents-setVisualZoomLevelLimits" , Id , minimumLevel , maximumLevel ) ;
return tcs . Task ;
}
2025-12-04 18:38:12 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Returns boolean - Whether this page has been muted.
/// </summary>
/// <returns></returns>
public Task < bool > IsAudioMutedAsync ( ) = > InvokeAsync < bool > ( ) ;
2025-12-04 19:07:26 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Returns boolean - Whether audio is currently playing.
/// </summary>
/// <returns></returns>
public Task < bool > IsCurrentlyAudibleAsync ( ) = > InvokeAsync < bool > ( ) ;
2025-12-04 19:07:26 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Mute the audio on the current web page.
/// </summary>
/// <param name="muted"></param>
public void SetAudioMuted ( bool muted )
{
BridgeConnector . Socket . Emit ( "webContents-setAudioMuted" , Id , muted ) ;
}
2025-12-04 19:07:26 +00:00
2025-12-04 17:52:01 +00:00
/// <summary>
/// Returns string - The user agent for this web page.
/// </summary>
/// <returns></returns>
2025-12-07 11:20:43 +01:00
public Task < string > GetUserAgentAsync ( ) = > InvokeAsyncWithTimeout < string > ( 3. seconds ( ) ) ;
2025-12-04 17:52:01 +00:00
/// <summary>
/// Overrides the user agent for this web page.
/// </summary>
/// <param name="userAgent"></param>
public void SetUserAgent ( string userAgent )
{
BridgeConnector . Socket . Emit ( "webContents-setUserAgent" , Id , userAgent ) ;
}
2025-11-15 08:05:31 +01:00
}