diff --git a/README.md b/README.md
index 8e6259bc..9ecad822 100644
--- a/README.md
+++ b/README.md
@@ -469,6 +469,7 @@ Additional information for v31.2 release:
* [GetBrowserByIdentifier](api/cefpython.md#getbrowserbyidentifier)
* [GetBrowserByWindowHandle](api/cefpython.md#getbrowserbywindowhandle)
* [GetCommandLineSwitch](api/cefpython.md#getcommandlineswitch)
+ * [GetDataUrl](api/cefpython.md#getdataurl)
* [GetGlobalClientCallback](api/cefpython.md#getglobalclientcallback)
* [GetModuleDirectory](api/cefpython.md#getmoduledirectory)
* [GetVersion](api/cefpython.md#getversion)
diff --git a/api/API-index.md b/api/API-index.md
index d5e3efcb..9535cb5b 100644
--- a/api/API-index.md
+++ b/api/API-index.md
@@ -157,6 +157,7 @@
* [GetBrowserByIdentifier](cefpython.md#getbrowserbyidentifier)
* [GetBrowserByWindowHandle](cefpython.md#getbrowserbywindowhandle)
* [GetCommandLineSwitch](cefpython.md#getcommandlineswitch)
+ * [GetDataUrl](cefpython.md#getdataurl)
* [GetGlobalClientCallback](cefpython.md#getglobalclientcallback)
* [GetModuleDirectory](cefpython.md#getmoduledirectory)
* [GetVersion](cefpython.md#getversion)
diff --git a/api/cefpython.md b/api/cefpython.md
index 239bf400..a876bd69 100644
--- a/api/cefpython.md
+++ b/api/cefpython.md
@@ -16,6 +16,7 @@ Table of contents:
* [GetBrowserByIdentifier](#getbrowserbyidentifier)
* [GetBrowserByWindowHandle](#getbrowserbywindowhandle)
* [GetCommandLineSwitch](#getcommandlineswitch)
+ * [GetDataUrl](#getdataurl)
* [GetGlobalClientCallback](#getglobalclientcallback)
* [GetModuleDirectory](#getmoduledirectory)
* [GetVersion](#getversion)
@@ -140,6 +141,20 @@ Get browser by outer or inner window handle. An outer window handle is the one t
Returns the [CommandLineSwitches](CommandLineSwitches.md) switch that was passed to Initialize(). Returns None if key is not found.
+### GetDataUrl
+
+| Parameter | Type |
+| --- | --- |
+| data | string |
+| mediatype="html" (optional) | string |
+| __Return__ | object |
+
+Convert data to a Data URL. Only "html" media type is currently supported.
+
+See:
+https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
+
+
### GetGlobalClientCallback
| Parameter | Type |
diff --git a/examples/README-examples.md b/examples/README-examples.md
index c4e98961..0f0ef9a4 100644
--- a/examples/README-examples.md
+++ b/examples/README-examples.md
@@ -67,7 +67,17 @@ workarounds.
### Snippets
See small code snippets that test various features in the
-[examples/snippets/](snippets/) directory.
+[examples/snippets/](snippets/) directory:
+
+- [javascript_bindings.py](snippets/javascript_bindings.py) - Communicate
+ between Python and Javascript asynchronously using
+ inter-process messaging with the use of Javascript Bindings.
+- [mouse_clicks.py](snippets/mouse_clicks.py) - Perform mouse clicks
+ and mouse movements programmatically.
+- [network_cookies.py](snippets/network_cookies.py) - Implement
+ interfaces to block or allow cookies over network requests.
+- [onbeforeclose.py](snippets/onbeforeclose.py) - Implement interface
+ to execute custom code before browser window closes.
### Build executable with PyInstaller
diff --git a/examples/pyinstaller/hook-cefpython3.py b/examples/pyinstaller/hook-cefpython3.py
index e36c8ed3..d30a2308 100644
--- a/examples/pyinstaller/hook-cefpython3.py
+++ b/examples/pyinstaller/hook-cefpython3.py
@@ -162,6 +162,7 @@ def get_cefpython3_datas():
# TODO: Write a tool script that would find such imports in
# .pyx files automatically.
hiddenimports = [
+ "base64",
"codecs",
"copy",
"datetime",
diff --git a/examples/snippets/javascript_bindings.py b/examples/snippets/javascript_bindings.py
new file mode 100644
index 00000000..7c7dccc5
--- /dev/null
+++ b/examples/snippets/javascript_bindings.py
@@ -0,0 +1,70 @@
+"""
+Communicate between Python and Javascript asynchronously using
+inter-process messaging with the use of Javascript Bindings.
+"""
+
+from cefpython3 import cefpython as cef
+
+g_htmlcode = """
+
+
+
+
+
+
+
+ Javascript Bindings
+
+
+
+"""
+
+
+def main():
+ cef.Initialize()
+ browser = cef.CreateBrowserSync(url=cef.GetDataUrl(g_htmlcode),
+ window_title="OnBeforeClose")
+ browser.SetClientHandler(LifespanHandler())
+ bindings = cef.JavascriptBindings()
+ bindings.SetFunction("py_function", py_function)
+ bindings.SetFunction("py_callback", py_callback)
+ browser.SetJavascriptBindings(bindings)
+ cef.MessageLoop()
+ del browser
+ cef.Shutdown()
+
+
+def py_function(value, js_callback):
+ print("Value sent from Javascript: "+value)
+ js_callback.Call("I am a Python string #2", py_callback)
+
+
+def py_callback(value):
+ print("Value sent from Javascript: "+value)
+
+
+class LifespanHandler(object):
+ def OnLoadEnd(self, browser, **_):
+ browser.ExecuteFunction("js_function", "I am a Python string #1")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/snippets/mouse_clicks.py b/examples/snippets/mouse_clicks.py
index fcdd5280..e8ace36a 100644
--- a/examples/snippets/mouse_clicks.py
+++ b/examples/snippets/mouse_clicks.py
@@ -43,7 +43,7 @@ class LifespanHandler(object):
def OnLoadEnd(self, browser, **_):
# Execute function with a delay of 1 second after page
# has completed loading.
- print("Page completed loading")
+ print("Page loading is complete")
cef.PostDelayedTask(cef.TID_UI, 1000, click_after_1_second, browser)
diff --git a/examples/snippets/network_cookies.py b/examples/snippets/network_cookies.py
index 55a71ec0..c3631cff 100644
--- a/examples/snippets/network_cookies.py
+++ b/examples/snippets/network_cookies.py
@@ -30,7 +30,7 @@ def CanGetCookies(self, frame, request, **_):
print("-- CanGetCookies #"+str(self.getcount))
print("url="+request.GetUrl()[0:80])
print("")
- # Return True to allow reading cookies and False to block
+ # Return True to allow reading cookies or False to block
return True
def CanSetCookie(self, frame, request, cookie, **_):
@@ -43,7 +43,7 @@ def CanSetCookie(self, frame, request, cookie, **_):
print("Name="+cookie.GetName())
print("Value="+cookie.GetValue())
print("")
- # Return True to allow setting cookie and False to block
+ # Return True to allow setting cookie or False to block
return True
diff --git a/src/cef_v59..v66_changes.txt b/src/cef_v59..v66_changes.txt
index fdf96cb2..f78800d3 100644
--- a/src/cef_v59..v66_changes.txt
+++ b/src/cef_v59..v66_changes.txt
@@ -82,6 +82,8 @@ NEW FEATURES
+ onbeforeclose.py
+ network_cookies.py
+ mouse_clicks.py
+ + javascript_bindings.py
++ cef.GetDataUrl
internal/cef_types.h
+ cef_log_severity_t: new key LOGSEVERITY_DEBUG (no need to expose,
diff --git a/src/cefpython.pyx b/src/cefpython.pyx
index 5a526bcd..1631636f 100644
--- a/src/cefpython.pyx
+++ b/src/cefpython.pyx
@@ -134,6 +134,8 @@ import datetime
import random
# noinspection PyUnresolvedReferences
import struct
+# noinspection PyUnresolvedReferences
+import base64
# Must use compile-time condition instead of checking sys.version_info.major
# otherwise results in "ImportError: cannot import name urlencode" strange
@@ -1023,3 +1025,9 @@ cpdef dict GetVersion():
cpdef LoadCrlSetsFile(py_string path):
CefLoadCRLSetsFile(PyToCefStringValue(path))
+
+cpdef GetDataUrl(data, mediatype="html"):
+ html = data.encode("utf-8", "replace")
+ b64 = base64.b64encode(html).decode("utf-8", "replace")
+ ret = "data:text/html;base64,{data}".format(data=b64)
+ return ret
diff --git a/unittests/_common.py b/unittests/_common.py
index a0dc1fd3..684a27b1 100644
--- a/unittests/_common.py
+++ b/unittests/_common.py
@@ -41,13 +41,6 @@ def show_test_summary(pyfile):
+ os.path.basename(pyfile))
-def html_to_data_uri(html):
- html = html.encode("utf-8", "replace")
- b64 = base64.b64encode(html).decode("utf-8", "replace")
- ret = "data:text/html;base64,{data}".format(data=b64)
- return ret
-
-
def run_message_loop():
# Run message loop for some time.
# noinspection PyTypeChecker
diff --git a/unittests/main_test.py b/unittests/main_test.py
index 25405ce0..b0c2200c 100644
--- a/unittests/main_test.py
+++ b/unittests/main_test.py
@@ -112,7 +112,7 @@