Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// GenAPI Version: 5.0.2.37403
Expand Down Expand Up @@ -85,6 +85,7 @@ public static void EnableWaitForBrowsersToClose() { }
public static bool Initialize(CefSharp.Core.CefSettingsBase cefSettings, bool performDependencyCheck, CefSharp.IApp cefApp) { throw null; }
public static bool Initialize(CefSharp.Core.CefSettingsBase cefSettings, bool performDependencyCheck, CefSharp.IBrowserProcessHandler browserProcessHandler) { throw null; }
public static bool IsWindows10OrGreaterEx() { throw null; }
public static int GetCefIdForCommandIdName(string name) { throw null; }
public static CefSharp.UrlParts ParseUrl(string url) { throw null; }
public static bool PostAction(CefSharp.CefThreadIds threadId, System.Action action) { throw null; }
public static bool PostDelayedAction(CefSharp.CefThreadIds threadId, System.Action action, int delayInMs) { throw null; }
Expand Down
14 changes: 14 additions & 0 deletions CefSharp.Core.Runtime/Cef.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
#include "versionhelpers.h";
#include <msclr/lock.h>
#include <msclr/marshal.h>
#include <msclr/marshal_cppstd.h>
#include <include/cef_version.h>
#include <include/cef_origin_whitelist.h>
#include <include/cef_crash_util.h>
#include <include/cef_parser.h>
#include <include/cef_task.h>
#include <include/internal/cef_types.h>
#include <include/cef_id_mappers.h>

#include "Internals/CefSharpApp.h"
#include "Internals/CefTaskScheduler.h"
Expand Down Expand Up @@ -973,6 +975,18 @@ namespace CefSharp
{
return IsWindows10OrGreater();
}

static int GetCefIdForCommandIdName(String^ name)
{
if (String::IsNullOrEmpty(name))
{
return -1;
}

std::string nativeString = marshal_as<std::string>(name);

return cef_id_for_command_id_name(nativeString.c_str());
}
};
}
}
Expand Down
29 changes: 29 additions & 0 deletions CefSharp.Core.Runtime/CefIdMappers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright © 2026 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

#include "Stdafx.h"

#include "CefIdMappers.h"
#include "include\cef_id_mappers.h"
#include <msclr/marshal_cppstd.h>

using namespace msclr::interop;

namespace CefSharp
{
namespace Core
{
int CefIdMappers::CefIdForCommandIdName(String^ name)
Comment thread
SLT-World marked this conversation as resolved.
{
if (String::IsNullOrEmpty(name))
{
return -1;
}

std::string nativeString = marshal_as<std::string>(name);

return cef_id_for_command_id_name(nativeString.c_str());
}
};
}
21 changes: 21 additions & 0 deletions CefSharp.Core.Runtime/CefIdMappers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright © 2026 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

#pragma once

#include "Stdafx.h"

namespace CefSharp
{
namespace Core
{
/// <exclude />
[System::ComponentModel::EditorBrowsableAttribute(System::ComponentModel::EditorBrowsableState::Never)]
public ref class CefIdMappers sealed
{
public:
static int CefIdForCommandIdName(String^ name);
};
}
}
19 changes: 19 additions & 0 deletions CefSharp.Core.Runtime/Internals/CefBrowserHostWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,25 @@ void CefBrowserHostWrapper::SetAudioMuted(bool mute)
_browserHost->SetAudioMuted(mute);
}

bool CefBrowserHostWrapper::CanExecuteChromeCommand(int commandId)
{
ThrowIfDisposed();

if (CefCurrentlyOn(TID_UI))
{
return _browserHost->CanExecuteChromeCommand(commandId);
}

throw gcnew InvalidOperationException("This method can only be called directly on the CEF UI Thread. Use CanExecuteChromeCommandAsync or use Cef.UIThreadTaskFactory to marshal the call onto the CEF UI Thread.");
}

void CefBrowserHostWrapper::ExecuteChromeCommand(int commandId, WindowOpenDisposition disposition)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically we'd create an enum from the command Id's to make it more user friendly, obviously that has some downsides, needs to be kept up to date, though I wonder how often commands change.

I think it's more user friendly than exposing CEF's string to int conversion method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've actually considered manually mapping the command IDs to an enum initially before discovering cef_id_for_command_id_name.

However, looking at the Chromium github mirror, these IDs are updated and changed almost every week or two.
https://github.com/chromium/chromium/commits/main/chrome/app/chrome_command_ids.h
Sometimes quite drastically, as seen in this commit:
chromium/chromium@8d255c6
Using an enum would likely require updates with every CefSharp release just to keep up, which is probably why CEF included the cef_id_for_command_id_name.

An enum is definitely more user-friendly, so I've implemented ChromeCommandIds enum as requested, but it did take quite a considerable amount of time.
Given the sheer number of commands, the time required to map them, and their frequent changes in Chromium, I do not recommend going forward with this enum.

It can be used by

(int)ChromeCommandIds.IDC_RELOAD

{
ThrowIfDisposed();

_browserHost->ExecuteChromeCommand(commandId, (cef_window_open_disposition_t)disposition);
}
Comment on lines +654 to +659
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does CEF CefBrowserHost::ExecuteChromeCommand require calling on CEF UI thread?

💡 Result:

Yes, the CefBrowserHost::ExecuteChromeCommand method must be called on the CEF UI thread [1]. According to the official Chromium Embedded Framework (CEF) documentation, this method specifically requires execution on the UI thread [1]. While many methods in the CefBrowserHost class may be called on any thread in the browser process unless otherwise indicated, ExecuteChromeCommand is explicitly documented with this thread-affinity requirement [1]. If your code is executing on a different thread, you must marshal the task to the UI thread, typically by using CefPostTask with the TID_UI thread identifier [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="CefSharp.Core.Runtime/Internals/CefBrowserHostWrapper.cpp"

# Show the relevant sections around ExecuteChromeCommand / CanExecuteChromeCommand
sed -n '630,720p' "$file" | nl -ba | sed -n '1,120p'

# Show the TryCloseBrowser mentioned in the comment for comparison (line ~158)
sed -n '120,220p' "$file" | nl -ba | sed -n '1,160p'

# Search for the helper used for non-UI thread checks
rg -n "ThrowIfExecutedOnNonCefUiThread|ExecutedOnNonCefUiThread|NonCefUiThread" "$file" || true

Repository: cefsharp/CefSharp

Length of output: 104


🌐 Web query:

CefBrowserHost::ExecuteChromeCommand must be called on the UI thread CEF docs

💡 Result:

Yes, CefBrowserHost::ExecuteChromeCommand must be called on the UI thread [1][2]. Official CEF documentation and implementation source code consistently specify that this method is intended for use with the Chrome runtime and has a strict UI thread requirement [1][2][3]. If you need to call this method from a different thread, you must use CEF task posting mechanisms to dispatch the call to the UI thread [3]. For example, in C++ you can use the following approach: if (!CefCurrentlyOn(TID_UI)) { CefPostTask(TID_UI, base::BindOnce(&CefBrowserHost::ExecuteChromeCommand, host, command_id, disposition)); return; } host->ExecuteChromeCommand(command_id, disposition); This pattern ensures that the command is executed safely within the context of the UI thread [3]. Failing to adhere to this requirement may result in threading violations or undefined behavior [4][3].

Citations:


🌐 Web query:

CefSharp CefBrowserHostWrapper ThrowIfExecutedOnNonCefUiThread TryCloseBrowser line 158

💡 Result:

In CefSharp, the method CefBrowserHostWrapper::TryCloseBrowser calls ThrowIfExecutedOnNonCefUiThread to enforce that the operation is performed on the CEF UI thread [1]. This restriction exists because many Chromium Embedded Framework (CEF) browser host operations are thread-sensitive and must be executed on the thread managed by CEF [2][3]. The specific reference to "line 158" in your query likely pertains to a version-specific implementation of the CefBrowserHostWrapper.cpp source file [1]. As codebase lines shift with updates, the exact location of this check may vary across different versions of CefSharp, but the underlying requirement remains consistent: 1. Thread Requirement: The IBrowserHost.TryCloseBrowser method must be called on the CEF UI thread [3]. 2. Enforcement: The wrapper class uses ThrowIfExecutedOnNonCefUiThread to verify the calling thread context [1]. If the method is called from a different thread (e.g., your application's main UI thread, if they differ), an exception will be thrown [1][4]. If you are encountering an exception originating from this check, you must ensure your call to TryCloseBrowser is dispatched to the CEF UI thread [4]. In WinForms or WPF, you can use the appropriate dispatcher or invoke mechanism provided by the browser control to ensure the code executes within the correct context [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="CefSharp.Core.Runtime/Internals/CefBrowserHostWrapper.cpp"

# Print ExecuteChromeCommand + surrounding code with line numbers (no `nl`)
sed -n '630,705p' "$file" | awk '{printf "%6d\t%s\n", NR+629, $0}'

# Print the earlier area to find TryCloseBrowser / thread checks
sed -n '120,220p' "$file" | awk '{printf "%6d\t%s\n", NR+119, $0}'

# Locate thread-check helpers used in this file
rg -n "ThrowIfExecutedOnNonCefUiThread|ExecutedOnNonCefUiThread|CefCurrentlyOn|TID_UI|On(Cef|NonCef).*UiThread" "$file" || true

# Locate CanExecuteChromeCommand region for context
sed -n '600,670p' "$file" | awk '{printf "%6d\t%s\n", NR+599, $0}'

Repository: cefsharp/CefSharp

Length of output: 8606


🌐 Web query:

site:cef-builds.spotifycdn.com CefBrowserHost ExecuteChromeCommand must be called on the UI thread

💡 Result:

Yes, the CefBrowserHost::ExecuteChromeCommand method must be called on the browser process UI thread [1][2][3]. This requirement is explicitly documented in the official Chromium Embedded Framework (CEF) API reference for the CefBrowserHost class [1][2][3]. If you are not currently on the UI thread, you must marshal the call to the UI thread using CefPostTask [4]. For example: CefPostTask(TID_UI, base::BindOnce([](CefRefPtr host) { host->ExecuteChromeCommand(IDC_YOUR_COMMAND, WINDOW_OPEN_DISPOSITION_CURRENT_TAB); }, browser->GetHost)); Always ensure that you are using version-safe mappings for command IDs, typically via the cef_id_for_command_id_name function, when dealing with Chrome style commands [3].

Citations:


Enforce CEF UI-thread for CefBrowserHostWrapper::ExecuteChromeCommand

ExecuteChromeCommand(int, WindowOpenDisposition) (CefBrowserHostWrapper.cpp:683-688) calls _browserHost->ExecuteChromeCommand(...) without a CEF UI-thread guard, while related wrapper APIs enforce it (e.g., CanExecuteChromeCommand throws unless CefCurrentlyOn(TID_UI), and TryCloseBrowser uses ThrowIfExecutedOnNonCefUiThread()). CEF’s API documentation requires CefBrowserHost::ExecuteChromeCommand to be executed on the CEF UI thread, so this wrapper should similarly check/marshal to TID_UI before invoking the underlying call. [minor]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CefSharp.Core.Runtime/Internals/CefBrowserHostWrapper.cpp` around lines 683 -
688, The wrapper ExecuteChromeCommand currently calls
_browserHost->ExecuteChromeCommand(...) without enforcing CEF UI-thread; add the
same UI-thread guard used by other wrappers by calling
ThrowIfExecutedOnNonCefUiThread() (or, if you prefer to marshal, post the call
to CefPostTask(TID_UI, ...) so the underlying _browserHost->ExecuteChromeCommand
runs on the CEF UI thread) before invoking _browserHost->ExecuteChromeCommand in
CefBrowserHostWrapper::ExecuteChromeCommand, keeping the existing
ThrowIfDisposed() check.


IntPtr CefBrowserHostWrapper::GetOpenerWindowHandle()
{
ThrowIfDisposed();
Expand Down
4 changes: 4 additions & 0 deletions CefSharp.Core.Runtime/Internals/CefBrowserHostWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ namespace CefSharp

virtual void SetAudioMuted(bool mute);

virtual bool CanExecuteChromeCommand(int commandId);

virtual void ExecuteChromeCommand(int commandId, WindowOpenDisposition disposition);

virtual IntPtr GetOpenerWindowHandle();

virtual void SendExternalBeginFrame();
Expand Down
16 changes: 16 additions & 0 deletions CefSharp.Core/Cef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -769,5 +769,21 @@ public static void AssertIsWindows10OrGreater()
if (!IsWindows10OrGreater())
throw new ApplicationException("Current OS version is less than Windows 10. Applications not manifested for Windows 10 throw this exception, even if the current operating system version is Windows 10. To manifest your applications for Windows 10, see https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1.");
}

/// <summary>
/// Returns the numeric ID value for an IDC <paramref name="name"/> from cef_command_ids.h or -1
/// if <paramref name="name"/> is unrecognized by the current CEF/Chromium build.
/// </summary>
/// <remarks>
/// This function provides version-safe mapping of command IDC names to version-specific
/// numeric ID values. Numeric ID values are likely to change across
/// CEF/Chromium versions but names generally remain the same.
/// </remarks>
/// <param name="name">String identifier of the Chromium command.</param>
/// <returns>version-specific numeric ID value for the command if recognized; otherwise, -1.</returns>
public static int GetCefIdForCommandIdName(string name)
{
return Core.Cef.GetCefIdForCommandIdName(name);
}
}
}
49 changes: 49 additions & 0 deletions CefSharp.Core/WebBrowserExtensionsEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,5 +265,54 @@ public static Task<T> EvaluateScriptAsync<T>(this IChromiumWebBrowserBase chromi

return chromiumWebBrowser.BrowserCore.EvaluateScriptAsync<T>(script, timeout);
}

/// <summary>
/// Asynchronously returns true if a Chrome command is supported and enabled. Only used with Chrome style.
/// </summary>
/// <param name="browser">The <see cref="IBrowser"/> instance this method extends.</param>
/// <param name="commandId">The command id.</param>
/// <returns>
/// <see cref="Task{T}"/> that can be awaited to obtain the availability of the Chrome command.
/// </returns>
public static Task<bool> CanExecuteChromeCommandAsync(this IBrowser browser, int commandId)
{
WebBrowserExtensions.ThrowExceptionIfBrowserNull(browser);

var host = browser.GetHost();
WebBrowserExtensions.ThrowExceptionIfBrowserHostNull(host);

if (Cef.CurrentlyOnThread(CefThreadIds.TID_UI))
{
var availability = host.CanExecuteChromeCommand(commandId);

return Task.FromResult<bool>(availability);
}

var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

Cef.UIThreadTaskFactory.StartNew(delegate
{
var availability = host.CanExecuteChromeCommand(commandId);

tcs.TrySetResult(availability);
});

return tcs.Task;
}

/// <summary>
/// Asynchronously returns true if a Chrome command is supported and enabled. Only used with Chrome style.
/// </summary>
/// <param name="browser">The ChromiumWebBrowser instance this method extends.</param>
/// <param name="commandId">The command id.</param>
/// <returns>
/// <see cref="Task{T}"/> that can be awaited to obtain the availability of the Chrome command.
/// </returns>
public static Task<bool> CanExecuteChromeCommandAsync(this IChromiumWebBrowserBase browser, int commandId)
{
WebBrowserExtensions.ThrowExceptionIfChromiumWebBrowserDisposed(browser);

return browser.BrowserCore.CanExecuteChromeCommandAsync(commandId);
}
}
}
Loading