55import base64
66import platform
77import sys
8+ import threading
89
910# HTML code. Browser will navigate to a Data uri created
1011# from this html code.
1112HTML_code = """
12- test
13+ <!DOCTYPE html>
14+ <html>
15+ <head>
16+ <style type="text/css">
17+ body,html { font-family: Arial; font-size: 11pt; }
18+ div.msg { margin: 0.2em; line-height: 1.4em; }
19+ b { background: #ccc; font-weight: bold; font-size: 10pt;
20+ padding: 0.1em 0.2em; }
21+ b.Python { background: #eee; }
22+ i { font-family: Courier new; font-size: 10pt; border: #eee 1px solid;
23+ padding: 0.1em 0.2em; }
24+ </style>
25+
26+ <script>
27+ function js_print(lang, event, msg) {
28+ msg = "<b class="+lang+">"+lang+": "+event+":</b> " + msg;
29+ console = document.getElementById("console")
30+ console.innerHTML += "<div class=msg>"+msg+"</div>";
31+ }
32+
33+ function js_callback_1(ret) {
34+ js_print("Javascript", "html_to_data_uri", ret);
35+ }
36+
37+ function js_callback_2(msg, py_callback) {
38+ js_print("Javascript", "js_callback", msg);
39+ py_callback("String sent from Javascript");
40+ }
41+
42+ window.onload = function(){
43+ js_print("Javascript", "window.onload", "Called");
44+ js_print("Javascript", "python_property", python_property);
45+ html_to_data_uri("test", js_callback_1);
46+ external.test_multiple_callbacks(js_callback_2);
47+ };
48+ </script>
49+ </head>
50+ <body>
51+ <h1>Tutorial example</h1>
52+ <div id="console"></div>
53+ </body>
54+ </html>
1355"""
1456
1557
1658def main ():
1759 check_versions ()
1860 sys .excepthook = cef .ExceptHook # To shutdown all CEF processes on error
19- settings = {"cache_path" : "webcache" }
20- cef .Initialize (settings = settings )
61+ cef .Initialize ()
2162 set_global_handler ()
2263 browser = cef .CreateBrowserSync (url = html_to_data_uri (HTML_code ),
2364 window_title = "Hello World!" )
@@ -34,10 +75,22 @@ def check_versions():
3475 assert cef .__version__ >= "56.1" , "CEF Python v56.1+ required to run this"
3576
3677
37- def html_to_data_uri (html ):
78+ def html_to_data_uri (html , js_callback = None ):
79+ # This function is called in two ways:
80+ # 1. From Python: in this case value is returned
81+ # 2. From Javascript: in this case value cannot be returned because
82+ # inter-process messaging is asynchronous, so must return value
83+ # by calling js_callback.
3884 html = html .encode ("utf-8" , "replace" )
3985 b64 = base64 .b64encode (html ).decode ("utf-8" , "replace" )
40- return "data:text/html;base64,{data}" .format (data = b64 )
86+ ret = "data:text/html;base64,{data}" .format (data = b64 )
87+ if js_callback :
88+ js_print (js_callback .GetFrame ().GetBrowser (),
89+ "Python" , "html_to_data_uri" ,
90+ "Called from Javascript. Will call Javascript callback now." )
91+ js_callback .Call (ret )
92+ else :
93+ return ret
4194
4295
4396def set_global_handler ():
@@ -59,45 +112,86 @@ def set_javascript_bindings(browser):
59112 external = External (browser )
60113 bindings = cef .JavascriptBindings (
61114 bindToFrames = False , bindToPopups = False )
115+ bindings .SetProperty ("python_property" , "This property was set in Python" )
62116 bindings .SetFunction ("html_to_data_uri" , html_to_data_uri )
63- bindings .SetProperty ("test_property" , "This property was set in Python" )
64117 bindings .SetObject ("external" , external )
65118 browser .SetJavascriptBindings (bindings )
66119
67120
68- def js_print (browser , msg ):
69- browser .ExecuteFunction ("js_print" , msg )
121+ def js_print (browser , lang , event , msg ):
122+ # Execute Javascript function "js_print"
123+ browser .ExecuteFunction ("js_print" , lang , event , msg )
70124
71125
72126class GlobalHandler (object ):
73127 def OnAfterCreated (self , browser , ** _ ):
74- js_print (browser ,
75- "Python: GlobalHandler._OnAfterCreated: browser id={id}"
76- .format (id = browser .GetIdentifier ()))
128+ # Issue #344 will fix this in next release, so that client
129+ # handlers are not called for Developer Tools windows.
130+ if browser .GetUrl ().startswith ("chrome-devtools://" ):
131+ return
132+ # DOM is not yet loaded. Using js_print at this moment will
133+ # throw an error: "Uncaught ReferenceError: js_print is not defined".
134+ # We make this error on purpose. This error will be intercepted
135+ # in DisplayHandler.OnConsoleMessage.
136+ js_print (browser , "Python" , "OnAfterCreated" ,
137+ "This will probably never display as DOM is not yet loaded" )
138+ # Delay print by 0.5 sec, because js_print is not available yet
139+ args = [browser , "Python" , "OnAfterCreated" ,
140+ "(Delayed) Browser id=" + str (browser .GetIdentifier ())]
141+ threading .Timer (0.5 , js_print , args ).start ()
77142
78143
79144class LoadHandler (object ):
80145 def OnLoadingStateChange (self , browser , is_loading , ** _ ):
146+ # Issue #344 will fix this in next release, so that client
147+ # handlers are not called for Developer Tools windows.
148+ if browser .GetUrl ().startswith ("chrome-devtools://" ):
149+ return
150+ # This callback is called twice, once when loading starts
151+ # (is_loading=True) and second time when loading ends
152+ # (is_loading=False).
81153 if not is_loading :
82- # Loading is complete
83- js_print (browser , "Python: LoadHandler. OnLoadingStateChange:"
84- "loading is complete" )
154+ # Loading is complete. DOM is ready.
155+ js_print (browser , "Python" , " OnLoadingStateChange" ,
156+ "Loading is complete" )
85157
86158
87159class DisplayHandler (object ):
88160 def OnConsoleMessage (self , browser , message , ** _ ):
161+ # Issue #344 will fix this in next release, so that client
162+ # handlers are not called for Developer Tools windows.
163+ if browser .GetUrl ().startswith ("chrome-devtools://" ):
164+ return
165+ # This will intercept js errors, see comments in OnAfterCreated
89166 if "error" in message .lower () or "uncaught" in message .lower ():
90- js_print (browser , "Python: LoadHandler.OnConsoleMessage: "
91- "intercepted Javascript error: {error}"
92- .format (error = message ))
167+ # Prevent infinite recurrence in case something went wrong
168+ if "js_print is not defined" in message .lower ():
169+ if hasattr (self , "js_print_is_not_defined" ):
170+ print ("Python: OnConsoleMessage: "
171+ "Intercepted Javascript error: " + message )
172+ return
173+ else :
174+ self .js_print_is_not_defined = True
175+ # Delay print by 0.5 sec, because js_print may not be
176+ # available yet due to DOM not ready.
177+ args = [browser , "Python" , "OnConsoleMessage" ,
178+ "(Delayed) Intercepted Javascript error: <i>{error}</i>"
179+ .format (error = message )]
180+ threading .Timer (0.5 , js_print , args ).start ()
93181
94182
95183class External (object ):
96184 def __init__ (self , browser ):
97185 self .browser = browser
98186
99- def test_function (self ):
100- pass
187+ def test_multiple_callbacks (self , js_callback ):
188+ """Test both javascript and python callbacks."""
189+ js_print (self .browser , "Python" , "test_multiple_callbacks" ,
190+ "Called from Javascript. Will call Javascript callback now." )
191+
192+ def py_callback (msg_from_js ):
193+ js_print (self .browser , "Python" , "py_callback" , msg_from_js )
194+ js_callback .Call ("String sent from Python" , py_callback )
101195
102196
103197if __name__ == '__main__' :
0 commit comments