@@ -307,6 +307,11 @@ cdef py_bool g_cef_initialized = False
307307cdef py_bool g_context_initialized = False
308308cdef 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+
310315cdef 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
833919def 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
843932def MessageLoopWork ():
844933 # Perform a single iteration of CEF message loop processing.
@@ -864,6 +953,9 @@ def SingleMessageLoop():
864953
865954def 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
0 commit comments