Skip to content

Commit 48705a2

Browse files
linesightclaude
andcommitted
CEF 146: add crossdomain_bindings.py snippet and update README
- crossdomain_bindings.py: demonstrates JS binding behaviour across a cross-domain auth flow (app -> auth domain -> back to app); uses a URL-based state machine to avoid misfire from multiple OnLoadEnd fires, and readyState fallback so the callback is not missed if window.load already fired before JS is injected - README-snippets.md: add entry for crossdomain_bindings.py and update onpagecomplete.py description to reflect window.load + requestAnimationFrame Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3b0de46 commit 48705a2

2 files changed

Lines changed: 134 additions & 2 deletions

File tree

examples/snippets/README-snippets.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ directory. If looking for non-trivial examples then see the
2828

2929
- [cookies.py](cookies.py) - Shows how to fetch all cookies,
3030
all cookies for a given url and how to delete a specific cookie.
31+
- [crossdomain_bindings.py](crossdomain_bindings.py) - Test Javascript
32+
bindings across a cross-domain navigation flow. Simulates an SSO/auth
33+
redirect (app page → auth domain → back to app) and demonstrates that
34+
bindings only fire for the intended target domain.
3135
- [javascript_bindings.py](javascript_bindings.py) - Communicate
3236
between Python and Javascript asynchronously using
3337
inter-process messaging with the use of Javascript Bindings.
@@ -41,8 +45,9 @@ directory. If looking for non-trivial examples then see the
4145
to execute custom code before browser window closes.
4246
- [ondomready.py](ondomready.py) - Execute custom Python code
4347
on a web page as soon as DOM is ready.
44-
- [onpagecomplete.py](onpagecomplete.py) - Execute custom
45-
Python code on a web page when page loading is complete.
48+
- [onpagecomplete.py](onpagecomplete.py) - Execute custom Python
49+
code on a web page after all visible content is loaded and painted,
50+
using window.load and requestAnimationFrame.
4651
- [setcookie.py](setcookie.py) - Shows how to set a cookie
4752
- [window_size.py](window_size.py) - Set initial window size
4853
without use of any third party GUI framework.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Test JavaScript bindings across a cross-domain navigation flow.
3+
4+
Simulates an SSO/auth redirect scenario:
5+
Step 1 - Target app page loads (example.com)
6+
OnContextCreated detects target domain -> injects JS -> binding fires
7+
Step 2 - Simulated redirect to auth/login domain (python.org)
8+
OnContextCreated detects non-target domain -> skips injection -> no callback
9+
Step 3 - Simulated auth complete, return to target app page (example.com)
10+
OnContextCreated detects target domain again -> binding fires again
11+
12+
Key points demonstrated:
13+
- JS bindings (V8 globals) are registered for every page in the browser instance,
14+
including intermediate auth/login pages on other domains.
15+
- It is the user's responsibility to filter in OnContextCreated by URL/domain before
16+
injecting JS so that callbacks only fire for the intended landing page.
17+
- Each domain's V8 context is isolated: globals registered for example.com are not
18+
accessible to python.org and vice versa.
19+
"""
20+
21+
import threading
22+
from cefpython3 import cefpython as cef
23+
24+
TARGET_HOST = "example.com"
25+
AUTH_URL = "https://www.python.org/" # visually distinct: simulates Okta/login page
26+
TARGET_URL = "https://example.com/"
27+
NAV_DELAY_SEC = 3.0
28+
29+
# Navigation state machine: avoid re-triggering on multiple OnLoadEnd fires
30+
STATE_INIT = "init"
31+
STATE_ON_TARGET_1 = "on_target_1" # first example.com load complete
32+
STATE_GOING_AUTH = "going_auth" # timer fired, navigating to auth
33+
STATE_ON_AUTH = "on_auth" # auth page load complete
34+
STATE_GOING_TARGET = "going_target" # timer fired, navigating back
35+
STATE_ON_TARGET_2 = "on_target_2" # final example.com load complete
36+
37+
38+
def main():
39+
print(__doc__)
40+
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)
48+
cef.MessageLoop()
49+
del handler
50+
del browser
51+
cef.Shutdown()
52+
53+
54+
class CrossDomainHandler(object):
55+
def __init__(self, browser):
56+
self.browser = browser
57+
self._state = STATE_INIT
58+
59+
def __getitem__(self, key):
60+
return getattr(self, key)
61+
62+
def OnContextCreated(self, browser, frame, **_):
63+
if not frame.IsMain():
64+
return
65+
url = frame.GetUrl()
66+
if not url or url == "about:blank":
67+
return
68+
if TARGET_HOST in url:
69+
print("[OnContextCreated] Target domain — injecting JS binding: %s" % url)
70+
browser.ExecuteJavascript("""
71+
if (document.readyState === "complete"
72+
|| document.readyState === "interactive") {
73+
requestAnimationFrame(function() { OnTargetPageReady(); });
74+
} else {
75+
window.addEventListener("load", function() {
76+
requestAnimationFrame(function() { OnTargetPageReady(); });
77+
});
78+
}
79+
""")
80+
else:
81+
print("[OnContextCreated] Non-target domain — skipping JS: %s" % url)
82+
83+
def OnLoadEnd(self, browser, frame, http_code, **_):
84+
if not frame.IsMain():
85+
return
86+
url = frame.GetUrl()
87+
if not url or url == "about:blank":
88+
return
89+
print("[OnLoadEnd] state=%s url=%s" % (self._state, url))
90+
91+
if self._state == STATE_INIT and TARGET_HOST in url:
92+
self._state = STATE_ON_TARGET_1
93+
print("[OnLoadEnd] Step 1 done. Redirecting to auth in %gs..." % NAV_DELAY_SEC)
94+
threading.Timer(NAV_DELAY_SEC, self._navigate_auth).start()
95+
96+
elif self._state == STATE_GOING_AUTH and TARGET_HOST not in url:
97+
self._state = STATE_ON_AUTH
98+
print("[OnLoadEnd] Step 2 done (auth page). Returning to app in %gs..." % NAV_DELAY_SEC)
99+
threading.Timer(NAV_DELAY_SEC, self._navigate_target).start()
100+
101+
elif self._state == STATE_GOING_TARGET and TARGET_HOST in url:
102+
self._state = STATE_ON_TARGET_2
103+
print("[OnLoadEnd] Step 3 done. Close window to exit.")
104+
105+
def _navigate_auth(self):
106+
self._state = STATE_GOING_AUTH
107+
print("[Nav] Navigating to auth domain: %s" % AUTH_URL)
108+
self.browser.GetMainFrame().LoadUrl(AUTH_URL)
109+
110+
def _navigate_target(self):
111+
self._state = STATE_GOING_TARGET
112+
print("[Nav] Navigating back to target: %s" % TARGET_URL)
113+
self.browser.GetMainFrame().LoadUrl(TARGET_URL)
114+
115+
def _OnTargetPageReady(self):
116+
url = self.browser.GetMainFrame().GetUrl()
117+
print("[Python callback] OnTargetPageReady fired! state=%s url=%s"
118+
% (self._state, url))
119+
self.browser.ExecuteJavascript(
120+
'setTimeout(function(){'
121+
' alert("JS binding works!\\nState: %s\\nURL: " + window.location.href);'
122+
' }, 0);' % self._state
123+
)
124+
125+
126+
if __name__ == "__main__":
127+
main()

0 commit comments

Comments
 (0)