Skip to content

Commit f797c0b

Browse files
linesightclaude
andcommitted
Create browser from OnContextInitialized instead of polling for context init
Under CEF's Chrome runtime the browser context initializes asynchronously: CefBrowserProcessHandler::OnContextInitialized() fires after the message loop starts, not when CefInitialize() returns (upstream CEF issue #2969). The Chrome runtime became the only runtime after the Alloy runtime was removed in CEF M128, so a browser can no longer be created immediately after cef.Initialize() -- doing so brings the renderer up before its host bindings are wired and the page renders blank. Replace the polling / deferred-creation workaround in CefInitialize() and CreateBrowserSync() with the pattern CEF's own cefsimple sample uses: create the browser from an "OnContextInitialized" global client callback, registered before cef.Initialize(). CreateBrowserSync() stays synchronous and now raises a clear, actionable error when called before the context is initialized instead of silently rendering a blank page. - Expose "OnContextInitialized" as a global client callback (SetGlobalClientCallback), invoked from the browser process handler. - Migrate the unit-test harness (main_test, osr_test, issue517) and all examples. Windowed GUI examples embed the browser only once both the window is realized and the context is initialized (event-driven, no polling). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b66c04e commit f797c0b

26 files changed

Lines changed: 556 additions & 230 deletions

examples/gtk2.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,28 @@
3333
MESSAGE_LOOP_CEF = 2 # Pass --message-loop-cef flag to script on Linux
3434
g_message_loop = None
3535

36+
# Under CEF's Chrome runtime the browser context initializes asynchronously,
37+
# so a browser can only be created after OnContextInitialized has fired. These
38+
# module-level helpers coordinate that: embed_browser() defers itself until the
39+
# context is ready, and the callback below runs any deferred embed.
40+
g_context_initialized = False
41+
g_pending_embed = None
42+
43+
44+
def _on_context_initialized():
45+
global g_context_initialized
46+
g_context_initialized = True
47+
if g_pending_embed is not None:
48+
g_pending_embed()
49+
3650

3751
def main():
3852
check_versions()
3953
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
4054
configure_message_loop()
55+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
56+
cef.SetGlobalClientCallback("OnContextInitialized",
57+
_on_context_initialized)
4158
cef.Initialize()
4259
gobject.threads_init()
4360
Gtk2Example()
@@ -108,6 +125,13 @@ def __init__(self):
108125
gobject.timeout_add(10, self.on_timer)
109126

110127
def embed_browser(self):
128+
global g_pending_embed
129+
if not g_context_initialized:
130+
# Chrome runtime initializes the browser context asynchronously;
131+
# embed once OnContextInitialized fires (see main()).
132+
g_pending_embed = self.embed_browser
133+
return
134+
g_pending_embed = None
111135
windowInfo = cef.WindowInfo()
112136
size = self.main_window.get_size()
113137
rect = [0, 0, size[0], size[1]]

examples/gtk3.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@
3434
from gi.repository import GdkX11
3535

3636

37+
# Under CEF's Chrome runtime the browser context initializes asynchronously,
38+
# so a browser can only be created after OnContextInitialized has fired. These
39+
# module-level helpers coordinate that: embed_browser() defers itself until the
40+
# context is ready, and the callback below runs any deferred embed.
41+
g_context_initialized = False
42+
g_pending_embed = None
43+
44+
45+
def _on_context_initialized():
46+
global g_context_initialized
47+
g_context_initialized = True
48+
if g_pending_embed is not None:
49+
g_pending_embed()
50+
51+
3752
def main():
3853
print("[gkt3.py] CEF Python {ver}".format(ver=cef.__version__))
3954
print("[gkt3.py] Python {ver} {arch}".format(
@@ -47,6 +62,9 @@ def main():
4762
# > Python[57738:d07] _createMenuRef called with existing principal
4863
# > MenuRef already associated with menu
4964
sys.excepthook = cef.ExceptHook # To shutdown CEF processes on error
65+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
66+
cef.SetGlobalClientCallback("OnContextInitialized",
67+
_on_context_initialized)
5068
cef.Initialize()
5169
app = Gtk3Example()
5270
SystemExit(app.run(sys.argv))
@@ -112,6 +130,13 @@ def on_activate(self, *_):
112130
self.window.resize(*self.window.get_default_size())
113131

114132
def embed_browser(self):
133+
global g_pending_embed
134+
if not g_context_initialized:
135+
# Chrome runtime initializes the browser context asynchronously;
136+
# embed once OnContextInitialized fires (see main()).
137+
g_pending_embed = self.embed_browser
138+
return
139+
g_pending_embed = None
115140
window_info = cef.WindowInfo()
116141
# TODO: on Mac pass rect[x, y, width, height] to SetAsChild
117142
window_info.SetAsChild(self.get_handle())

examples/hello_world.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,20 @@ def main():
1818
check_versions()
1919
sys.excepthook = cef.ExceptHook # shut down all CEF processes on error
2020
settings = {}
21+
22+
def on_context_initialized():
23+
# Under CEF's Chrome runtime the browser context initializes
24+
# asynchronously, so the browser must be created from the
25+
# OnContextInitialized callback rather than immediately after
26+
# cef.Initialize(). This matches CEF's cefsimple sample.
27+
cef.CreateBrowserSync(url="https://www.google.com/",
28+
window_title="Hello World!")
29+
30+
# Register the callback before cef.Initialize(): OnContextInitialized can
31+
# fire during cef.Initialize() itself.
32+
cef.SetGlobalClientCallback("OnContextInitialized",
33+
on_context_initialized)
2134
cef.Initialize(settings=settings)
22-
cef.CreateBrowserSync(url="https://www.google.com/",
23-
window_title="Hello World!")
2435
cef.MessageLoop()
2536
cef.Shutdown()
2637

examples/pysdl2.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ def die(msg):
104104
" To install type: pip install pyobjc")
105105

106106

107+
# Under CEF's Chrome runtime the browser context initializes asynchronously,
108+
# so the (off-screen) browser can only be created after OnContextInitialized
109+
# has fired. This flag is set by the callback registered before
110+
# cef.Initialize().
111+
g_context_initialized = False
112+
113+
114+
def _on_context_initialized():
115+
global g_context_initialized
116+
g_context_initialized = True
117+
118+
107119
def main():
108120
"""
109121
Parses input, initializes everything and then runs the main loop of the
@@ -193,6 +205,9 @@ def main():
193205
# Tweaking OSR performance (Issue #240)
194206
"windowless_frame_rate": frameRate
195207
}
208+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
209+
cef.SetGlobalClientCallback("OnContextInitialized",
210+
_on_context_initialized)
196211
cef.Initialize(settings={"windowless_rendering_enabled": True},
197212
switches=switches)
198213

@@ -296,6 +311,14 @@ def _detect_device_scale_factor(window, renderer):
296311
# RenderHandler instances, creating SDL windows for popups lazily.
297312
renderHandler = MetaRenderHandler(renderer, width, height - headerHeight,
298313
deviceScaleFactor, rendererFlags)
314+
# Create the browser instance. Unlike the windowed examples (which embed
315+
# the browser from an event callback), this linear off-screen example
316+
# creates it inline, so ensure the CEF context has finished initializing
317+
# first. On most platforms it already has by this point (it initializes
318+
# during cef.Initialize()); otherwise let the message loop run until
319+
# OnContextInitialized fires.
320+
while not g_context_initialized:
321+
cef.MessageLoopWork()
299322
# Create the browser instance
300323
browser = cef.CreateBrowserSync(window_info,
301324
url="https://www.google.com/",

examples/qt.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,21 @@
7474
HEIGHT = 600
7575

7676

77+
# Under CEF's Chrome runtime the browser context initializes asynchronously,
78+
# so a browser can only be created after OnContextInitialized has fired. These
79+
# module-level helpers coordinate that: embedBrowser() defers itself until the
80+
# context is ready, and the callback below runs any deferred embed.
81+
g_context_initialized = False
82+
g_pending_embed = None
83+
84+
85+
def _on_context_initialized():
86+
global g_context_initialized
87+
g_context_initialized = True
88+
if g_pending_embed is not None:
89+
g_pending_embed()
90+
91+
7792
def main():
7893
check_versions()
7994
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
@@ -92,6 +107,9 @@ def main():
92107
"devtools": True,
93108
}
94109

110+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
111+
cef.SetGlobalClientCallback("OnContextInitialized",
112+
_on_context_initialized)
95113
cef.Initialize(settings)
96114
app = CefApplication(sys.argv)
97115
main_window = MainWindow()
@@ -248,6 +266,13 @@ def focusOutEvent(self, event):
248266
self.browser.SetFocus(False)
249267

250268
def embedBrowser(self):
269+
global g_pending_embed
270+
if not g_context_initialized:
271+
# Chrome runtime initializes the browser context asynchronously;
272+
# embed once OnContextInitialized fires (see main()).
273+
g_pending_embed = self.embedBrowser
274+
return
275+
g_pending_embed = None
251276
if LINUX and PYQT5:
252277
# On Linux with PyQt5, QX11EmbedContainer is gone; the Qt-native
253278
# equivalent is to host CEF in a QWindow (hidden_window) wrapped in

examples/screenshot.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,15 @@ def main():
9797
# Tweaking OSR performance (Issue #240)
9898
"windowless_frame_rate": 30, # Default frame rate in CEF is 30
9999
}
100+
def on_context_initialized():
101+
# Under CEF's Chrome runtime the browser context initializes
102+
# asynchronously, so create the browser here rather than right after
103+
# cef.Initialize().
104+
create_browser(browser_settings)
105+
106+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
107+
cef.SetGlobalClientCallback("OnContextInitialized", on_context_initialized)
100108
cef.Initialize(settings=settings, switches=switches)
101-
create_browser(browser_settings)
102109
cef.MessageLoop()
103110
cef.Shutdown()
104111
print("[screenshot.py] Opening screenshot with default application")

examples/snippets/cookies.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77

88

99
def main():
10+
def on_context_initialized():
11+
# Under CEF's Chrome runtime the browser context initializes
12+
# asynchronously, so create the browser here rather than right after
13+
# cef.Initialize().
14+
browser = cef.CreateBrowserSync(
15+
url="https://www.google.com/",
16+
window_title="Cookies")
17+
browser.SetClientHandler(LoadHandler())
18+
19+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
20+
cef.SetGlobalClientCallback("OnContextInitialized", on_context_initialized)
1021
cef.Initialize()
11-
browser = cef.CreateBrowserSync(
12-
url="https://www.google.com/",
13-
window_title="Cookies")
14-
browser.SetClientHandler(LoadHandler())
1522
cef.MessageLoop()
16-
del browser
1723
cef.Shutdown()
1824

1925

examples/snippets/crossdomain_bindings.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,24 @@
3737

3838
def main():
3939
print(__doc__)
40+
41+
def on_context_initialized():
42+
# Under CEF's Chrome runtime the browser context initializes
43+
# asynchronously, so create the browser here rather than right after
44+
# cef.Initialize().
45+
browser = cef.CreateBrowserSync(
46+
url=TARGET_URL,
47+
window_title="Cross-domain JS binding test")
48+
handler = CrossDomainHandler(browser)
49+
browser.SetClientHandler(handler)
50+
bindings = cef.JavascriptBindings()
51+
bindings.SetFunction("OnTargetPageReady", handler["_OnTargetPageReady"])
52+
browser.SetJavascriptBindings(bindings)
53+
54+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
55+
cef.SetGlobalClientCallback("OnContextInitialized", on_context_initialized)
4056
cef.Initialize()
41-
browser = cef.CreateBrowserSync(url=TARGET_URL,
42-
window_title="Cross-domain JS binding test")
43-
handler = CrossDomainHandler(browser)
44-
browser.SetClientHandler(handler)
45-
bindings = cef.JavascriptBindings()
46-
bindings.SetFunction("OnTargetPageReady", handler["_OnTargetPageReady"])
47-
browser.SetJavascriptBindings(bindings)
4857
cef.MessageLoop()
49-
del handler
50-
del browser
5158
cef.Shutdown()
5259

5360

examples/snippets/javascript_bindings.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,22 @@
3838

3939

4040
def main():
41+
def on_context_initialized():
42+
# Under CEF's Chrome runtime the browser context initializes
43+
# asynchronously, so create the browser here rather than right after
44+
# cef.Initialize().
45+
browser = cef.CreateBrowserSync(url=cef.GetDataUrl(g_htmlcode),
46+
window_title="Javascript Bindings")
47+
browser.SetClientHandler(LoadHandler())
48+
bindings = cef.JavascriptBindings()
49+
bindings.SetFunction("py_function", py_function)
50+
bindings.SetFunction("py_callback", py_callback)
51+
browser.SetJavascriptBindings(bindings)
52+
53+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
54+
cef.SetGlobalClientCallback("OnContextInitialized", on_context_initialized)
4155
cef.Initialize()
42-
browser = cef.CreateBrowserSync(url=cef.GetDataUrl(g_htmlcode),
43-
window_title="Javascript Bindings")
44-
browser.SetClientHandler(LoadHandler())
45-
bindings = cef.JavascriptBindings()
46-
bindings.SetFunction("py_function", py_function)
47-
bindings.SetFunction("py_callback", py_callback)
48-
browser.SetJavascriptBindings(bindings)
4956
cef.MessageLoop()
50-
del browser
5157
cef.Shutdown()
5258

5359

examples/snippets/javascript_errors.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,17 @@
4040

4141

4242
def main():
43+
def on_context_initialized():
44+
# Under CEF's Chrome runtime the browser context initializes
45+
# asynchronously, so create the browser here rather than right after
46+
# cef.Initialize().
47+
browser = cef.CreateBrowserSync(url=cef.GetDataUrl(g_htmlcode),
48+
window_title="Javascript Errors")
49+
browser.SetClientHandler(DisplayHandler())
50+
51+
# Register before cef.Initialize(); OnContextInitialized may fire during it.
52+
cef.SetGlobalClientCallback("OnContextInitialized", on_context_initialized)
4353
cef.Initialize()
44-
browser = cef.CreateBrowserSync(url=cef.GetDataUrl(g_htmlcode),
45-
window_title="Javascript Errors")
46-
browser.SetClientHandler(DisplayHandler())
4754
cef.MessageLoop()
4855
cef.Shutdown()
4956

0 commit comments

Comments
 (0)