Skip to content

Commit 1a17b7f

Browse files
linesightclaude
andcommitted
Merge branch 146-linux into 146-macos
Bring in the Linux fixes (accessibility location-change grace period, Ozone/X11 switches, GLib init, sandbox flags) while keeping all macOS- specific changes (Mach port rendezvous workarounds, use-mock-keychain, single-process, rpath fix, ARM build fixes). Conflict resolutions: - src/cefpython.pyx: use IF UNAME_SYSNAME != "Linux" guard so the post-Initialize() message-loop pump is skipped on Linux (where gtk_main() drives it) but still runs on macOS/Windows. - unittests/main_test.py, osr_test.py: keep both the Linux-specific switches added by 146-linux (ozone-platform, no-zygote, disable/ enable-features, password-store) and the macOS if MAC: block from 146-macos; they guard separate platforms and do not overlap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2 parents b643615 + fbb2a7d commit 1a17b7f

19 files changed

Lines changed: 846 additions & 50 deletions

.github/workflows/ci-linux.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ jobs:
126126
cp -r build/artifacts/. cefpython3/
127127
chmod +x cefpython3/subprocess
128128
129+
- name: Resize /dev/shm for Chromium shared memory
130+
run: sudo mount -o remount,size=512m /dev/shm
131+
129132
- name: Run unit tests
130133
run: xvfb-run python unittests/_test_runner.py
131134
env:

.github/workflows/ci-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI Windows
22

33
on:
44
push:
5-
branches: [ "github-actions-ci", "master", "146-linux" ]
5+
branches: [ "github-actions-ci", "master" ]
66
pull_request:
77
branches: [ "master" ]
88
workflow_dispatch:

cefpython3/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
os.environ["CEFPYTHON3_PATH"] = package_dir
2121

2222
if platform.system() == "Linux":
23+
# Force GDK to use X11/XWayland backend before libcef.so loads and
24+
# initialises GDK. Without this, GDK picks the Wayland backend on
25+
# GNOME/Wayland sessions, making gdk_x11_window_get_xid() return 0.
26+
os.environ.setdefault("GDK_BACKEND", "x11")
2327
_ld = os.environ.get("LD_LIBRARY_PATH", "")
2428
os.environ["LD_LIBRARY_PATH"] = (
2529
package_dir + os.pathsep + _ld if _ld else package_dir

examples/hello_world.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
# Setting DPI awareness programmatically via a call to cef.DpiAware.EnableHighDpiSupport
99
# is problematic in Python, may not work and can cause display glitches.
1010

11+
import sys
12+
1113
from cefpython3 import cefpython as cef
1214
import platform
13-
import sys
1415
from packaging.version import Version as parse_version
1516

1617

1718
def main():
1819
check_versions()
19-
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
20-
cef.Initialize()
20+
sys.excepthook = cef.ExceptHook # shut down all CEF processes on error
21+
settings = {}
22+
cef.Initialize(settings=settings)
2123
cef.CreateBrowserSync(url="https://www.google.com/",
2224
window_title="Hello World!")
2325
cef.MessageLoop()
@@ -32,7 +34,8 @@ def check_versions():
3234
print("[hello_world.py] Python {ver} {arch}".format(
3335
ver=platform.python_version(),
3436
arch=platform.architecture()[0]))
35-
assert parse_version(cef.__version__) >= parse_version("57.0"), "CEF Python v57.0+ required to run this"
37+
assert parse_version(cef.__version__) >= parse_version("57.0"), \
38+
"CEF Python v57.0+ required to run this"
3639

3740

3841
if __name__ == '__main__':

src/cefpython.pyx

Lines changed: 108 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,11 @@ cdef py_bool g_cef_initialized = False
307307
cdef py_bool g_context_initialized = False
308308
cdef list g_pending_browsers = []
309309

310+
IF UNAME_SYSNAME == "Linux":
311+
# Keeps ctypes callback objects alive for the duration of gtk_main() and
312+
# any pending one-shot GLib timers (Xwayland XReparentWindow scheduling).
313+
g_linux_reparent_callbacks = []
314+
310315
cdef dict g_globalClientCallbacks = {}
311316

312317
# -----------------------------------------------------------------------------
@@ -571,14 +576,47 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs):
571576
if "use-angle" not in g_commandLineSwitches:
572577
g_commandLineSwitches["use-angle"] = "gl"
573578

579+
IF UNAME_SYSNAME == "Linux":
580+
# Initialize GTK so GDK has a display connection before CefInitialize.
581+
_linux_gtk_init()
582+
# Auto-apply switches/settings required for CEF 146 on Linux/Xwayland.
583+
# Uses setdefault so user-supplied values are never overwritten.
584+
_linux_apply_initialize_defaults(application_settings,
585+
g_commandLineSwitches)
586+
# Pre-seed Chrome profile files to prevent the profile-picker keepalive
587+
# from blocking OnContextInitialized (Chrome 146).
588+
if application_settings.get("cache_path"):
589+
_linux_setup_profile(application_settings["cache_path"])
590+
574591
cdef CefRefPtr[CefApp] cefApp = <CefRefPtr[CefApp]?>new CefPythonApp()
575592

576593
IF UNAME_SYSNAME == "Windows":
577594
cdef HINSTANCE hInstance = GetModuleHandle(NULL)
578595
cdef CefMainArgs cefMainArgs = CefMainArgs(hInstance)
579596
ELIF UNAME_SYSNAME == "Linux":
580-
# TODO: use the CefMainArgs(int argc, char** argv) constructor.
581-
cdef CefMainArgs cefMainArgs
597+
# Build a complete argv so the browser process sees all switches.
598+
# OnBeforeChildProcessLaunch only reaches child processes; switches
599+
# like --in-process-gpu and --single-process must be in the browser
600+
# process's own command line to take effect.
601+
_cefMainArgvPyList = [sys.executable.encode('utf-8')]
602+
for _cefArgK, _cefArgV in g_commandLineSwitches.items():
603+
if _cefArgV:
604+
_cefMainArgvPyList.append(
605+
("--{}={}".format(_cefArgK, _cefArgV)).encode('utf-8'))
606+
else:
607+
_cefMainArgvPyList.append(
608+
("--{}".format(_cefArgK)).encode('utf-8'))
609+
cdef int _cefMainArgc = len(_cefMainArgvPyList)
610+
cdef char** _cefMainArgvC = \
611+
<char**>malloc(_cefMainArgc * sizeof(char*))
612+
cdef bytes _cefMainArgvItem
613+
cdef int _cefMainArgvI
614+
for _cefMainArgvI in range(_cefMainArgc):
615+
_cefMainArgvItem = _cefMainArgvPyList[_cefMainArgvI]
616+
_cefMainArgvC[_cefMainArgvI] = _cefMainArgvItem
617+
cdef CefMainArgs cefMainArgs = CefMainArgs(_cefMainArgc, _cefMainArgvC)
618+
# _cefMainArgvPyList keeps the bytes alive; freed below after
619+
# CefInitialize() has processed the command line.
582620
ELIF UNAME_SYSNAME == "Darwin":
583621
# TODO: use the CefMainArgs(int argc, char** argv) constructor.
584622
cdef CefMainArgs cefMainArgs
@@ -594,8 +632,19 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs):
594632
g_applicationSettings[key] = copy.deepcopy(application_settings[key])
595633

596634
cdef CefSettings cefApplicationSettings
597-
# No sandboxing for the subprocesses
598-
cefApplicationSettings.no_sandbox = 1
635+
IF UNAME_SYSNAME == "Linux":
636+
# On Linux, leave no_sandbox=0 so Chrome's startup code registers the
637+
# Mojo IPC bootstrap fd (GlobalDescriptors key 7) for every subprocess.
638+
# Setting no_sandbox=1 would cause BasicStartupComplete() to append
639+
# --no-sandbox before fd registration, causing all subprocesses to crash
640+
# with "Failed global descriptor lookup: 7". Sandbox behaviour is instead
641+
# controlled via --disable-setuid-sandbox / --disable-namespace-sandbox
642+
# command-line switches passed by the caller.
643+
pass
644+
ELSE:
645+
# On Windows/macOS the sandbox helper binary is not shipped with
646+
# cefpython, so disable sandboxing entirely.
647+
cefApplicationSettings.no_sandbox = 1
599648
SetApplicationSettings(application_settings, &cefApplicationSettings)
600649

601650
# External message pump
@@ -611,6 +660,8 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs):
611660
cdef cpp_bool ret
612661
with nogil:
613662
ret = CefInitialize(cefMainArgs, cefApplicationSettings, cefApp, NULL)
663+
IF UNAME_SYSNAME == "Linux":
664+
free(_cefMainArgvC)
614665

615666
global g_cef_initialized
616667
g_cef_initialized = True
@@ -622,15 +673,22 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs):
622673
# guarantees that CreateBrowserSync() can be called immediately after
623674
# Initialize() without hitting the deferred-creation path or getting
624675
# a null browser from CefBrowserHost::CreateBrowserSync().
625-
# OnContextInitialized typically fires within the first few iterations;
626-
# allow up to 30 seconds for slow CI environments.
676+
# Use a generous ceiling (30s) for CI environments where utility
677+
# subprocesses (storage service) crash and delay context initialization.
627678
if ret:
628-
for _ in range(3000):
629-
with nogil:
630-
CefDoMessageLoopWork()
631-
if g_context_initialized:
632-
break
633-
time.sleep(0.01)
679+
# On Linux, skip this pump entirely: the Ozone X11 backend needs
680+
# gtk_main() (a blocking GLib main loop) running before
681+
# OnContextInitialized can fire. The external caller (hello_world.py,
682+
# test harnesses) must enter gtk_main() immediately after Initialize()
683+
# and drive the loop via the GLib timer callback.
684+
# On Windows/macOS, pump up to 30 s as before.
685+
IF UNAME_SYSNAME != "Linux":
686+
for _ in range(3000):
687+
with nogil:
688+
CefDoMessageLoopWork()
689+
if g_context_initialized:
690+
break
691+
time.sleep(0.01)
634692
if not g_context_initialized:
635693
Debug("CefInitialize() WARNING: OnContextInitialized not received"
636694
" within 30 seconds")
@@ -671,8 +729,18 @@ def CreateBrowserSync(windowInfo=None,
671729
# Defer browser creation until OnContextInitialized fires inside MessageLoop.
672730
# In CEF 123+, browser creation before OnContextInitialized causes
673731
# blink.mojom.WidgetHost rejection and renderer shows no content.
674-
# Initialize() pumps the loop until OnContextInitialized fires, so this
675-
# path is only taken if CreateBrowserSync() is called before Initialize().
732+
# Initialize() pumps the loop for up to 30s; if still not initialized
733+
# (e.g. slow CI), pump an additional 30s before giving up.
734+
if not g_context_initialized:
735+
Debug("CreateBrowserSync(): OnContextInitialized not yet received,"
736+
" pumping message loop")
737+
IF UNAME_SYSNAME != "Linux":
738+
for _ in range(3000):
739+
with nogil:
740+
CefDoMessageLoopWork()
741+
if g_context_initialized:
742+
break
743+
time.sleep(0.01)
676744
if not g_context_initialized:
677745
Debug("CreateBrowserSync() deferred until OnContextInitialized")
678746
g_pending_browsers.append({
@@ -730,6 +798,18 @@ def CreateBrowserSync(windowInfo=None,
730798
elif not isinstance(windowInfo, WindowInfo):
731799
raise Exception("CreateBrowserSync() failed: windowInfo: invalid object")
732800

801+
# On Linux, when no parent window is given, auto-create a GTK toplevel
802+
# so callers need no GTK-specific code (same API as Windows/Mac).
803+
_linux_toplevel_state = None
804+
IF UNAME_SYSNAME == "Linux":
805+
if windowInfo.windowType == "child" and windowInfo.parentWindowHandle == 0:
806+
_linux_toplevel_state = _linux_create_toplevel(
807+
window_title or "CEF Browser")
808+
windowInfo.SetAsChild(
809+
_linux_toplevel_state['xid'],
810+
[0, 0, _linux_toplevel_state['width'],
811+
_linux_toplevel_state['height']])
812+
733813
if window_title and windowInfo.parentWindowHandle == 0:
734814
windowInfo.windowName = window_title
735815

@@ -828,6 +908,12 @@ def CreateBrowserSync(windowInfo=None,
828908
MacSetWindowTitle(cefBrowser,
829909
PyStringToChar(windowInfo.windowName))
830910

911+
IF UNAME_SYSNAME == "Linux":
912+
if windowInfo._linux_embed_info:
913+
_linux_schedule_xembed(pyBrowser, windowInfo._linux_embed_info)
914+
if _linux_toplevel_state is not None:
915+
_linux_register_window_callbacks(pyBrowser, _linux_toplevel_state)
916+
831917
return pyBrowser
832918

833919
def MessageLoop():
@@ -837,8 +923,11 @@ def MessageLoop():
837923
global g_MessageLoop_called
838924
g_MessageLoop_called = True
839925

840-
with nogil:
841-
CefRunMessageLoop()
926+
IF UNAME_SYSNAME == "Linux":
927+
_linux_message_loop()
928+
ELSE:
929+
with nogil:
930+
CefRunMessageLoop()
842931

843932
def MessageLoopWork():
844933
# Perform a single iteration of CEF message loop processing.
@@ -864,6 +953,9 @@ def SingleMessageLoop():
864953

865954
def QuitMessageLoop():
866955
Debug("QuitMessageLoop()")
956+
IF UNAME_SYSNAME == "Linux":
957+
import ctypes as _ct
958+
_ct.CDLL("libgtk-3.so.0").gtk_main_quit()
867959
with nogil:
868960
CefQuitMessageLoop()
869961

src/client_handler/x11.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ void SetX11WindowBounds(CefRefPtr<CefBrowser> browser,
3636
int x, int y, int width, int height) {
3737
::Window xwindow = browser->GetHost()->GetWindowHandle();
3838
::Display* xdisplay = cef_get_xdisplay();
39+
if (!xdisplay || !xwindow) return;
3940
XWindowChanges changes = {0};
4041
changes.x = x;
4142
changes.y = y;
@@ -48,12 +49,21 @@ void SetX11WindowBounds(CefRefPtr<CefBrowser> browser,
4849
void SetX11WindowTitle(CefRefPtr<CefBrowser> browser, char* title) {
4950
::Window xwindow = browser->GetHost()->GetWindowHandle();
5051
::Display* xdisplay = cef_get_xdisplay();
52+
if (!xdisplay || !xwindow) return;
5153
XStoreName(xdisplay, xwindow, title);
5254
}
5355

5456
GtkWindow* CefBrowser_GetGtkWindow(CefRefPtr<CefBrowser> browser) {
5557
// TODO: Should return NULL when using the Views framework
5658
// -- REWRITTEN FOR CEF PYTHON USE CASE --
59+
//
60+
// WARNING (CEF 146 Ozone X11): gtk_plug_new_for_display() below sends an
61+
// XEMBED_EMBEDDED_NOTIFY to the browser's X11 window, which causes GTK to
62+
// call XReparentWindow and move the browser window into a new GtkSocket.
63+
// This breaks embedded-window positioning. Only call this function when
64+
// showing a transient dialog (file chooser, print dialog) where the browser
65+
// window displacement is acceptable or the dialog is temporary.
66+
//
5767
// X11 window handle
5868
::Window xwindow = browser->GetHost()->GetWindowHandle();
5969
// X11 display
@@ -99,7 +109,7 @@ GtkWindow* CefBrowser_GetGtkWindow(CefRefPtr<CefBrowser> browser) {
99109
XImage* CefBrowser_GetImage(CefRefPtr<CefBrowser> browser) {
100110
::Display* display = cef_get_xdisplay();
101111
if (!display) {
102-
LOG(ERROR) << "XOpenDisplay failed in CefBrowser_GetImage";
112+
LOG(ERROR) << "cef_get_xdisplay() returned NULL in CefBrowser_GetImage";
103113
return NULL;
104114
}
105115
::Window browser_window = browser->GetHost()->GetWindowHandle();

src/extern/cef/cef_browser_static.pxd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
include "platform_cimports.pxi"
66

7+
from libcpp cimport bool as cpp_bool
78
from cef_ptr cimport CefRefPtr
89
# noinspection PyUnresolvedReferences
910
from cef_client cimport CefClient
@@ -18,6 +19,14 @@ from cef_string cimport CefString
1819
# Specifying namespace allows to import a static method.
1920
cdef extern from "include/cef_browser.h" namespace "CefBrowserHost":
2021

22+
cdef cpp_bool CreateBrowser(
23+
CefWindowInfo&,
24+
CefRefPtr[CefClient],
25+
CefString&,
26+
CefBrowserSettings&,
27+
CefRefPtr[CefDictionaryValue],
28+
CefRefPtr[CefRequestContext]) nogil
29+
2130
cdef CefRefPtr[CefBrowser] CreateBrowserSync(
2231
CefWindowInfo&,
2332
CefRefPtr[CefClient],

src/extern/cef/cef_command_line.pxd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ include "compile_time_constants.pxi"
99

1010
from cef_string cimport CefString
1111
from libcpp cimport bool as cpp_bool
12+
from libcpp.vector cimport vector as cpp_vector
13+
from libcpp.map cimport map as cpp_map
1214

1315
cdef extern from "include/cef_command_line.h":
1416
cdef cppclass CefCommandLine:
@@ -17,3 +19,9 @@ cdef extern from "include/cef_command_line.h":
1719
CefString GetCommandLineString()
1820
cpp_bool HasSwitch(const CefString& name)
1921
CefString GetSwitchValue(const CefString& name)
22+
void Reset()
23+
CefString GetProgram()
24+
void SetProgram(const CefString& program)
25+
void GetSwitches(cpp_map[CefString, CefString]& switches)
26+
void GetArguments(cpp_vector[CefString]& arguments)
27+
void AppendArgument(const CefString& argument)

src/extern/linux.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ cdef extern from "gtk/gtk.h" nogil:
66
ctypedef void* GtkWidget
77
cdef GtkWidget* gtk_plug_new(unsigned long socket_id)
88
cdef void gtk_widget_show(GtkWidget* widget)
9+
ctypedef void* GMainContext
10+
int g_main_context_iteration(GMainContext* context, int may_block)
911

1012
ctypedef char* XPointer
1113
ctypedef struct XImage:

0 commit comments

Comments
 (0)