From 36c0560230b2896138e94726c143c242d1f6e251 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 10 Mar 2017 14:42:09 +0100 Subject: [PATCH 001/398] Support Python executables with spaces in path (PR #318)... Python 3.5/3.6 installs to C:\Program Files (x86)\ when "install into PATH for all users" is checked. --- tools/automate.py | 15 +++++++-------- tools/build.py | 14 +++++++------- tools/make_installer.py | 2 +- tools/run_examples.py | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/tools/automate.py b/tools/automate.py index e7559af33..584d09a68 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -851,19 +851,13 @@ def run_command(command, working_dir, env=None): shell=(platform.system() == "Windows")) -def run_python(command_line, working_dir): - """Run python script using depot_tools.""" - python = "python" - return run_command("%s %s" % (python, command_line), working_dir) - - def run_git(command_line, working_dir): """Run git command using depot_tools.""" return run_command("git %s" % command_line, working_dir) def run_automate_git(): - """Run CEF automate-git.py.""" + """Run CEF automate-git.py using Python 2.7.""" script = os.path.join(Options.cefpython_dir, "tools", "automate-git.py") """ Example automate-git.py command: @@ -898,8 +892,13 @@ def run_automate_git(): # later in cef_binary/ with cmake/ninja do works fine. args.append("--build-target=cefsimple") + # On Windows automate-git.py must be run using Python 2.7 + # from depot_tools. depot_tools should already be added to PATH. + python = "python" # *do not* replace with sys.executable! args = " ".join(args) - return run_python(script+" "+args, Options.cef_build_dir) + command = script + " " + args + working_dir = Options.cef_build_dir + return run_command("%s %s" % (python, command), working_dir) def rmdir(path): diff --git a/tools/build.py b/tools/build.py index 15fc1da5f..5afa215fd 100644 --- a/tools/build.py +++ b/tools/build.py @@ -690,13 +690,13 @@ def build_cefpython_module(): os.chdir(BUILD_CEFPYTHON) if FAST_FLAG: - ret = subprocess.call("{python} {tools_dir}/cython_setup.py" + ret = subprocess.call("\"{python}\" {tools_dir}/cython_setup.py" " build_ext --fast" .format(python=sys.executable, tools_dir=TOOLS_DIR), shell=True) else: - ret = subprocess.call("{python} {tools_dir}/cython_setup.py" + ret = subprocess.call("\"{python}\" {tools_dir}/cython_setup.py" " build_ext" .format(python=sys.executable, tools_dir=TOOLS_DIR), @@ -725,7 +725,7 @@ def build_cefpython_module(): " being generated yet. Will re-run the build.py script" " programmatically now.") args = list() - args.append(sys.executable) + args.append("\"{python}\"".format(python=sys.executable)) args.append(os.path.join(TOOLS_DIR, os.path.basename(__file__))) assert __file__ in sys.argv[0] args.extend(sys.argv[1:]) @@ -794,7 +794,7 @@ def install_and_run(): # Make setup installer print("[build.py] Make setup installer") make_tool = os.path.join(TOOLS_DIR, "make_installer.py") - ret = os.system("{python} {make_tool} --version {version}" + ret = os.system("\"{python}\" {make_tool} --version {version}" .format(python=sys.executable, make_tool=make_tool, version=VERSION)) @@ -805,7 +805,7 @@ def install_and_run(): # Install print("[build.py] Install the cefpython package") os.chdir(setup_installer_dir) - ret = os.system("{sudo} {python} setup.py install" + ret = os.system("{sudo} \"{python}\" setup.py install" .format(sudo=get_sudo(), python=sys.executable)) if ret != 0: print("[build.py] ERROR while installing package") @@ -818,7 +818,7 @@ def install_and_run(): # Run unittests print("[build.py] Run unittests") test_runner = os.path.join(UNITTESTS_DIR, "_test_runner.py") - ret = os.system("{python} {test_runner}" + ret = os.system("\"{python}\" {test_runner}" .format(python=sys.executable, test_runner=test_runner)) if ret != 0: print("[build.py] ERROR while running unit tests") @@ -829,7 +829,7 @@ def install_and_run(): os.chdir(EXAMPLES_DIR) kivy_flag = "--kivy" if KIVY_FLAG else "" run_examples = os.path.join(TOOLS_DIR, "run_examples.py") - ret = os.system("{python} {run_examples} {kivy_flag}" + ret = os.system("\"{python}\" {run_examples} {kivy_flag}" .format(python=sys.executable, run_examples=run_examples, kivy_flag=kivy_flag)) diff --git a/tools/make_installer.py b/tools/make_installer.py index 6dd1eb717..d13b7155d 100644 --- a/tools/make_installer.py +++ b/tools/make_installer.py @@ -103,7 +103,7 @@ def main(): print("[make_installer.py] ERROR: you must specify flags" " eg. --python-tag cp27 or --universal") sys.exit(1) - command = ("{python} setup.py bdist_wheel {wheel_args}" + command = ("\"{python}\" setup.py bdist_wheel {wheel_args}" .format(python=sys.executable, wheel_args=" ".join(WHEEL_ARGS))) print("[make_installer.py] Run command: '{0}' in setup directory" diff --git a/tools/run_examples.py b/tools/run_examples.py index b2ae112d8..9f2583c65 100644 --- a/tools/run_examples.py +++ b/tools/run_examples.py @@ -96,7 +96,7 @@ def main(): for example in examples: print("[run_examples.py] Running '{example}'..." .format(example=example)) - ret = os.system("{python} {example}" + ret = os.system("\"{python}\" {example}" .format(python=sys.executable, example=example)) if ret == 0: succeeded.append(example) From e9db0e8e88f1572bf141e6330bd7c38cb4e3d2b4 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 11 Mar 2017 16:56:24 +0100 Subject: [PATCH 002/398] Create build_distrib.py tool (#317)... Add --no-run-examples flag to build.py. --- docs/Build-instructions.md | 4 +- tools/build.py | 66 +++--- tools/build_cpp_projects.py | 3 +- tools/build_distrib.py | 436 ++++++++++++++++++++++++++++++++++++ tools/common.py | 119 +++++++--- tools/make_installer.py | 9 +- 6 files changed, 571 insertions(+), 66 deletions(-) create mode 100644 tools/build_distrib.py diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index db67f0908..821adab6e 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -47,8 +47,8 @@ Before you can build CEF Python or CEF you must satisfy ## Quick build instructions for Windows -Complete steps for building CEF Python v50+ using prebuilt binaries -and libraries from GitHub Releases: +Complete steps for building CEF Python v50+ with Python 2.7 using +prebuilt binaries and libraries from GitHub Releases: 1) Tested and works fine on Windows 7 64-bit diff --git a/tools/build.py b/tools/build.py index 5afa215fd..0319a2ea5 100644 --- a/tools/build.py +++ b/tools/build.py @@ -21,11 +21,12 @@ build.py VERSION [--rebuild-cpp] [--fast] [--clean] [--kivy] Options: - VERSION Version number eg. 50.0 - --rebuild-cpp Force rebuild of C++ projects - --fast Fast mode - --clean Clean C++ projects build files (.o .a etc) - --kivy Run only Kivy example + VERSION Version number eg. 50.0 + --no-run-examples Do not run examples after build, only unit tests + --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED) + --fast Fast mode + --clean Clean C++ projects build files on Linux/Mac + --kivy Run only Kivy example """ # How to debug on Linux: @@ -59,12 +60,13 @@ pass # Command line args variables +VERSION = "" +NO_RUN_EXAMPLES = False DEBUG_FLAG = False FAST_FLAG = False CLEAN_FLAG = False KIVY_FLAG = False REBUILD_CPP = False -VERSION = "" # First run FIRST_RUN = False @@ -102,39 +104,44 @@ def main(): def command_line_args(): global DEBUG_FLAG, FAST_FLAG, CLEAN_FLAG, KIVY_FLAG,\ - REBUILD_CPP, VERSION + REBUILD_CPP, VERSION, NO_RUN_EXAMPLES - VERSION = get_version_from_command_line_args() + VERSION = get_version_from_command_line_args(__file__) if not VERSION: print(__doc__) sys.exit(1) print("[build.py] Parse command line arguments") - # -- debug flag - if len(sys.argv) > 1 and "--debug" in sys.argv: + # --no-run-examples + if "--no-run-examples" in sys.argv: + NO_RUN_EXAMPLES = True + print("[build.py] Running examples disabled (--no-run-examples)") + + # -- debug + if "--debug" in sys.argv: DEBUG_FLAG = True print("[build.py] DEBUG mode On") - # --fast flag - if len(sys.argv) > 1 and "--fast" in sys.argv: + # --fast + if "--fast" in sys.argv: # Fast mode doesn't delete C++ .o .a files. # Fast mode also disables optimization flags in setup/setup.py . FAST_FLAG = True print("[build.py] FAST mode On") - # --clean flag - if len(sys.argv) > 1 and "--clean" in sys.argv: + # --clean + if "--clean" in sys.argv: CLEAN_FLAG = True - # --kivy flag - if len(sys.argv) > 1 and "--kivy" in sys.argv: + # --kivy + if "--kivy" in sys.argv: KIVY_FLAG = True print("[build.py] KIVY mode enabled") - # --rebuild-cpp flag + # --rebuild-cpp # Rebuild c++ projects - if len(sys.argv) > 1 and "--rebuild-cpp" in sys.argv: + if "--rebuild-cpp" in sys.argv: REBUILD_CPP = True print("[build.py] REBUILD_CPP mode enabled") @@ -825,17 +832,18 @@ def install_and_run(): sys.exit(ret) # Run examples - print("[build.py] Run examples") - os.chdir(EXAMPLES_DIR) - kivy_flag = "--kivy" if KIVY_FLAG else "" - run_examples = os.path.join(TOOLS_DIR, "run_examples.py") - ret = os.system("\"{python}\" {run_examples} {kivy_flag}" - .format(python=sys.executable, - run_examples=run_examples, - kivy_flag=kivy_flag)) - if ret != 0: - print("[build.py] ERROR while running examples") - sys.exit(1) + if not NO_RUN_EXAMPLES: + print("[build.py] Run examples") + os.chdir(EXAMPLES_DIR) + kivy_flag = "--kivy" if KIVY_FLAG else "" + run_examples = os.path.join(TOOLS_DIR, "run_examples.py") + ret = os.system("\"{python}\" {run_examples} {kivy_flag}" + .format(python=sys.executable, + run_examples=run_examples, + kivy_flag=kivy_flag)) + if ret != 0: + print("[build.py] ERROR while running examples") + sys.exit(1) print("[build.py] Everything OK") diff --git a/tools/build_cpp_projects.py b/tools/build_cpp_projects.py index aa39e59b5..f7eae2a84 100644 --- a/tools/build_cpp_projects.py +++ b/tools/build_cpp_projects.py @@ -221,7 +221,8 @@ def smart_compile(compiler, macros, extra_args, sources, output_dir): obj_time = os.path.getmtime(obj_file) source_time = os.path.getmtime(source_file) header_time = os.path.getmtime(header_file) if header_file else 0 - cefpython_h_fixed_time = os.path.getmtime(CEFPYTHON_API_HFILE_FIXED) + cefpython_h_fixed_time = os.path.getmtime( + CEFPYTHON_API_HFILE_FIXED) common_files_time = get_directory_mtime(os.path.join(SRC_DIR, "common")) changed = ((source_time > obj_time) diff --git a/tools/build_distrib.py b/tools/build_distrib.py new file mode 100644 index 000000000..84c661d89 --- /dev/null +++ b/tools/build_distrib.py @@ -0,0 +1,436 @@ +# Copyright (c) 2017 CEF Python, see the Authors file. +# All rights reserved. Licensed under BSD 3-clause license. +# Project website: https://github.com/cztomczak/cefpython + +""" +Build distribution packages for all architectures and all supported +python versions. + +TODO: Linux/Mac support. Currently runs only on Windows. + +Usage: + build_distrib.py VERSION [--no-run-examples] + +Options: + VERSION Version number eg. 50.0 + --no-run-examples Do not run examples while building cefpython modules. + Only unit tests will be run in such case. + +This script does the following: +1. Expects that all supported python versions are installed + a) On Windows search for Pythons in the multiple default install + locations + b) On Mac use system Python 2.7 and Python 3 from ~/.pyenv/versions/ + directory + c) On Linux use only Pythons from ~/.pyenv/versions/ directory +2. Expects that all python compilers for supported python versions + are installed. See docs/Build-instructions.md > Requirements. +3. Expects cef_binary*/ directories from Spotify Automated Builds + to be in the build/ directory +4. Install and/or upgrade tools/requirements.txt and uninstall + cefpython3 packages for all python versions +5. Run automate.py --prebuilt-cef using both Python 32-bit and Python 64-bit +6. Pack the prebuilt biaries using zip on Win/Mac and .tar.gz on Linux + and move to build/distrib/ +7. Build cefpython modules for all supported Python versions on both + 32-bit and 64-bit +8. Make setup installers and pack them to zip (Win/Mac) or .tar.gz (Linux) +9. Make wheel packages +10. Move setup and wheel packages to the build/distrib/ directory +""" + +from common import * +import glob +import os +import pprint +import re +import shutil +import subprocess +import zipfile + +# Command line args +VERSION = "" +NO_RUN_EXAMPLES = False + +# Pythons +SUPPORTED_PYTHON_VERSIONS = [(2, 7), (3, 4), (3, 5), (3, 6)] +PYTHON_SEARCH_PATHS_WINDOWS = [ + "C:\\Python*\\", + "%LocalAppData%\\Programs\\Python\\Python*\\", + "C:\\Program Files\\Python*\\", + "C:\\Program Files (x86)\\Python*\\", +] + + +def main(): + command_line_args() + print("[build_distrib.py] Supported python versions:") + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(SUPPORTED_PYTHON_VERSIONS) + clean_build_directories() + pythons_32bit = list() + pythons_64bit = list() + if WINDOWS: + pythons_32bit = search_for_pythons("32bit") + pythons_64bit = search_for_pythons("64bit") + check_pythons(pythons_32bit, pythons_64bit) + install_upgrade_requirements(pythons_32bit + pythons_64bit) + uninstall_cefpython3_packages(pythons_32bit + pythons_64bit) + if not os.path.exists(DISTRIB_DIR): + os.makedirs(DISTRIB_DIR) + if pythons_32bit: + run_automate_prebuilt_cef(pythons_32bit[0]) + pack_prebuilt_cef(pythons_32bit[0]["arch"]) + if pythons_64bit is not None: + run_automate_prebuilt_cef(pythons_64bit[0]) + pack_prebuilt_cef(pythons_64bit[0]["arch"]) + build_cefpython_modules(pythons_32bit + pythons_64bit) + if pythons_32bit: + make_packages(pythons_32bit[0], "32bit") + if pythons_64bit: + make_packages(pythons_64bit[0], "64bit") + show_summary(pythons_32bit, pythons_64bit) + + +def command_line_args(): + global VERSION, NO_RUN_EXAMPLES + version = get_version_from_command_line_args(__file__) + if not version: + print(__doc__) + sys.exit(1) + VERSION = version + if "--no-run-examples" in sys.argv: + NO_RUN_EXAMPLES = True + + +def clean_build_directories(): + print("[build_distrib.py] Clean build directories") + + # Distrib dir + if os.path.exists(DISTRIB_DIR): + print("[build_distrib.py] Delete directory: {distrib_dir}/" + .format(distrib_dir=os.path.basename(DISTRIB_DIR))) + shutil.rmtree(DISTRIB_DIR) + + # build_cefpython/ dir + if os.path.exists(BUILD_CEFPYTHON): + print("[build_distirb.py] Delete directory: {dir}/" + .format(dir=os.path.basename(BUILD_CEFPYTHON))) + shutil.rmtree(BUILD_CEFPYTHON) + + # cefpython_binary_*/ dirs + delete_cefpython_binary_dir("32bit") + delete_cefpython_binary_dir("64bit") + + # cef binaries and libraries dirs + delete_cef_binaries_libraries_dir("32bit") + delete_cef_binaries_libraries_dir("64bit") + + +def delete_cefpython_binary_dir(arch): + cefpython_binary = get_cefpython_binary_basename( + postfix2=get_postfix2_for_arch(arch)) + assert cefpython_binary, cefpython_binary + cefpython_binary = os.path.join(BUILD_DIR, cefpython_binary) + if os.path.exists(cefpython_binary): + print("[build_distrib.py] Delete directory: {dir}/" + .format(dir=os.path.basename(cefpython_binary))) + shutil.rmtree(cefpython_binary) + + +def delete_cef_binaries_libraries_dir(arch): + cef_binlib = get_cef_binaries_libraries_basename( + postfix2=get_postfix2_for_arch(arch)) + assert cef_binlib, cef_binlib + cef_binlib = os.path.join(BUILD_DIR, cef_binlib) + if os.path.exists(cef_binlib): + print("[build_distrib.py] Delete directory: {dir}/" + .format(dir=os.path.basename(cef_binlib))) + shutil.rmtree(cef_binlib) + + +def check_pythons(pythons_32bit, pythons_64bit): + pp = pprint.PrettyPrinter(indent=4) + if pythons_32bit: + print("[build_distrib.py] Pythons 32-bit found:") + pp.pprint(pythons_32bit) + if WINDOWS and len(pythons_32bit) != len(SUPPORTED_PYTHON_VERSIONS): + print("[build_distrib.py] ERROR: Couldn't find all supported" + " python 32-bit installations. Found: {found}." + .format(found=len(pythons_32bit))) + sys.exit(1) + if pythons_64bit: + print("[build_distrib.py] Pythons 64-bit found:") + pp.pprint(pythons_64bit) + if len(pythons_64bit) != len(SUPPORTED_PYTHON_VERSIONS): + print("[build_distrib.py] ERROR: Couldn't find all supported" + " python 64-bit installations. Found: {found}." + .format(found=len(pythons_64bit))) + sys.exit(1) + + +def search_for_pythons(search_arch): + print("[build_distrib.py] Search for Pythons...") + if WINDOWS: + return search_for_pythons_windows(search_arch) + raise Exception("Only Windows platform supported currently") + + +def search_for_pythons_windows(search_arch): + """Returns pythons ordered by version from lowest to highest.""" + pythons_found = list() + for pattern in PYTHON_SEARCH_PATHS_WINDOWS: + pattern = pattern.replace("%LocalAppData%", + os.environ["LOCALAPPDATA"]) + results = glob.glob(pattern) + for path in results: + if os.path.isdir(path): + python = os.path.join(path, "python.exe") + version_code = ("import sys;" + "print(str(sys.version_info[:3]));") + version_str = subprocess.check_output([python, "-c", + version_code]) + version_str = version_str.strip() + match = re.search("^\((\d+), (\d+), (\d+)\)$", version_str) + assert match, version_str + major = match.group(1) + minor = match.group(2) + micro = match.group(3) + version_tuple2 = (int(major), int(minor)) + version_tuple3 = (int(major), int(minor), int(micro)) + arch_code = ("import platform;" + "print(str(platform.architecture()[0]));") + arch = subprocess.check_output([python, "-c", arch_code]) + arch = arch.strip() + if version_tuple2 in SUPPORTED_PYTHON_VERSIONS \ + and arch == search_arch: + name = ("Python {major}.{minor}.{micro} {arch}" + .format(major=major, minor=minor, micro=micro, + arch=arch)) + pythons_found.append(dict( + version2=version_tuple2, + version3=version_tuple3, + arch=arch, + executable=python, + name=name)) + ret_pythons = list() + for version_tuple in SUPPORTED_PYTHON_VERSIONS: + supported_python = None + for python in pythons_found: + if python["version2"] == version_tuple: + # Always go through the whole loop and save the last + # python executable for the given version (eg. 2.7), + # so that the latest version is used (eg. 2.7.12). + # This is assuming that glob.glob sorted directories. + supported_python = python + if supported_python: + ret_pythons.append(supported_python) + return ret_pythons + + +def install_upgrade_requirements(pythons): + for python in pythons: + print("[build_distrib.py] pip install/upgrade requirements.txt" + " for: {name}".format(name=python["name"])) + + # Upgrade pip + command = "\"{python}\" -m pip install --upgrade pip" + command = command.format(python=python["executable"]) + if python["executable"].startswith("/usr/"): + command = "sudo {command}".format(command=command) + pcode = subprocess.call(command, shell=True) + if pcode != 0: + print("[build_distrib.py] ERROR while upgrading pip") + sys.exit(1) + + # Install/upgrade requirements.txt + requirements = os.path.join(TOOLS_DIR, "requirements.txt") + command = "\"{python}\" -m pip install --upgrade -r {requirements}" + command = command.format(python=python["executable"], + requirements=requirements) + if python["executable"].startswith("/usr/"): + command = "sudo {command}".format(command=command) + pcode = subprocess.call(command, shell=True) + if pcode != 0: + print("[build_distrib.py] ERROR while running pip install/upgrade") + sys.exit(1) + + +def uninstall_cefpython3_packages(pythons): + for python in pythons: + print("[build_distrib.py] pip uninstall cefpython3 package" + " for: {name}".format(name=python["name"])) + + # Check if package is installed + command = ("\"{python}\" -m pip show cefpython3" + .format(python=python["executable"])) + try: + output = subprocess.check_output(command) + except subprocess.CalledProcessError, exc: + # pip show returns error code when package is not installed + output = exc.output + if not len(output.strip()): + # Package is not installed - info is an empty string + print("[build_distrib.py] Not installed") + continue + + # Uninstall package. Only uninstall if package is installed, + # otherwise error code is returned. + command = ("\"{python}\" -m pip uninstall -y cefpython3" + .format(python=python["executable"])) + if python["executable"].startswith("/usr/"): + command = "sudo {command}".format(command=command) + pcode = subprocess.call(command, shell=True) + if pcode != 0: + print("[build_distrib.py] ERROR while uninstall cefpython3" + " package using pip") + sys.exit(1) + + +def run_automate_prebuilt_cef(python): + print("[build_distrib.py] Run automate.py --prebuilt-cef for {arch}" + .format(arch=python["arch"])) + automate = os.path.join(TOOLS_DIR, "automate.py") + command = ("\"{python}\" {automate} --prebuilt-cef" + .format(python=python["executable"], automate=automate)) + code = subprocess.call(command) + if code != 0: + print("[build_distrib.py] ERROR while running automate.py") + sys.exit(1) + + +def pack_prebuilt_cef(arch): + prebuilt_basename = get_cef_binaries_libraries_basename( + get_postfix2_for_arch(arch)) + print("[build_distrib.py] Pack directory: {dir}/ ..." + .format(dir=prebuilt_basename)) + prebuilt_dir = os.path.join(BUILD_DIR, prebuilt_basename) + assert os.path.exists(prebuilt_dir), prebuilt_dir + archive = pack_directory(prebuilt_dir, base_path=BUILD_DIR) + shutil.move(archive, DISTRIB_DIR) + print("[build_distrib.py] Created archive in distrib dir: {archive}" + .format(archive=os.path.basename(archive))) + + +def pack_directory(path, base_path): + if path.endswith(os.path.sep): + path = path[:-1] + ext = ".zip" if WINDOWS or MAC else ".tar.gz" + archive = path + ext + if os.path.exists(archive): + os.remove(archive) + if WINDOWS or MAC: + zip_directory(path, base_path=base_path, archive=archive) + else: + # LINUX + raise Exception("pack_directory(): Linux not yet supported") # TODO + assert os.path.isfile(archive), archive + return archive + + +def zip_directory(path, base_path, archive): + original_dir = os.getcwd() + os.chdir(base_path) + path = path.replace(base_path, "") + if path[0] == os.path.sep: + path = path[1:] + zipf = zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) + for root, dirs, files in os.walk(path): + for file_ in files: + zipf.write(os.path.join(root, file_)) + zipf.close() + os.chdir(original_dir) + + +def build_cefpython_modules(pythons): + for python in pythons: + print("[build_distrib.py] Build cefpython module for {python_name}" + .format(python_name=python["name"])) + flags = "" + if NO_RUN_EXAMPLES: + flags += " --no-run-examples" + command = ("\"{python}\" {build_py} {version} {flags}" + .format(python=python["executable"], + build_py=os.path.join(TOOLS_DIR, "build.py"), + version=VERSION, + flags=flags)) + # build.py may require sudo if system python, so shell=True + pcode = subprocess.call(command, shell=True) + if pcode != 0: + print("[build_distrib.py] ERROR: failed to build cefpython" + " module for {python_name}" + .format(python_name=python["name"])) + sys.exit(1) + print("[build_distrib.py] Built successfully cefpython module for" + " {python_name}".format(python_name=python["name"])) + print("[build_distrib.py] Successfully built cefpython modules for" + " all Python versions") + + +def make_packages(python, arch): + # Make setup package + print("[build_distrib.py] Make setup package for {arch}..." + .format(arch=arch)) + make_installer_py = os.path.join(TOOLS_DIR, "make_installer.py") + installer_command = ("\"{python}\" {make_installer_py} {version}" + .format(python=python["executable"], + make_installer_py=make_installer_py, + version=VERSION)) + pcode = subprocess.call(installer_command, cwd=BUILD_DIR) + if pcode != 0: + print("[build_distrib.py] ERROR: failed to make setup package for" + " {arch}".format(arch=arch)) + sys.exit(1) + + # Pack setup package and move to distrib dir + print("[build_distrib.py] Pack setup package for {arch}..." + .format(arch=arch)) + setup_basename = get_setup_installer_basename( + VERSION, get_postfix2_for_arch(arch)) + setup_dir = os.path.join(BUILD_DIR, setup_basename) + archive = pack_directory(setup_dir, BUILD_DIR) + shutil.move(archive, DISTRIB_DIR) + + # Make wheel package + print("[build_distrib.py] Make wheel package for {arch}..." + .format(arch=arch)) + wheel_args = "bdist_wheel --universal" + wheel_command = ("\"{python}\" setup.py {wheel_args}" + .format(python=python["executable"], + wheel_args=wheel_args)) + pcode = subprocess.call(wheel_command, cwd=setup_dir) + if pcode != 0: + print("[build_distrib.py] ERROR: failed to make wheel package for" + " {arch}".format(arch=arch)) + sys.exit(1) + + # Move wheel package + files = glob.glob(os.path.join(setup_dir, "dist", "*.whl")) + assert len(files) == 1, ".whl file not found" + shutil.move(files[0], DISTRIB_DIR) + + # Delete setup directory + print("[build_distrib.py] Delete setup directory: {setup_dir}/" + .format(setup_dir=os.path.basename(setup_dir))) + shutil.rmtree(setup_dir) + + +def show_summary(pythons_32bit, pythons_64bit): + print("[build_distrib.py] SUMMARY:") + print(" Pythons 32bit ({count})".format(count=len(pythons_32bit))) + for python in pythons_32bit: + print(" {python_name}".format(python_name=python["name"])) + print(" Pythons 64bit ({count})".format(count=len(pythons_64bit))) + for python in pythons_64bit: + print(" {python_name}".format(python_name=python["name"])) + files = glob.glob(os.path.join(DISTRIB_DIR, "*")) + print(" Files in the build/{distrib_basename}/ directory ({count})" + .format(distrib_basename=os.path.basename(DISTRIB_DIR), + count=len(files))) + for file_ in files: + print(" {filename}".format(filename=os.path.basename(file_))) + print("[build_distrib.py] Done. Distribution packages created.") + + +if __name__ == "__main__": + main() diff --git a/tools/common.py b/tools/common.py index 8f63bdd3f..576626f36 100644 --- a/tools/common.py +++ b/tools/common.py @@ -14,7 +14,13 @@ # Architecture and OS postfixes ARCH32 = (8 * struct.calcsize('P') == 32) ARCH64 = (8 * struct.calcsize('P') == 64) -ARCH_STR = "32-bit" if (8 * struct.calcsize('P') == 32) else "64-bit" +# Make sure platform.architecture()[0] shows correctly 32bit when +# running Python 32bit on Windows 64bit. +if ARCH32: + assert platform.architecture()[0] == "32bit" +if ARCH64: + assert platform.architecture()[0] == "64bit" +ARCH_STR = platform.architecture()[0] # OS_POSTFIX is for directories/files names in cefpython sources # OS_POSTFIX2 is for platform name in cefpython binaries # CEF_POSTFIX2 is for platform name in upstream CEF binaries @@ -34,9 +40,16 @@ CEF_POSTFIX2 = "linux32" if ARCH32 else "linux64" # Platforms -WINDOWS = (platform.system() == "Windows") -LINUX = (platform.system() == "Linux") -MAC = (platform.system() == "Darwin") +SYSTEM = platform.system() +WINDOWS = SYSTEM if SYSTEM == "Windows" else False +LINUX = SYSTEM if SYSTEM == "Linux" else False +MAC = SYSTEM if SYSTEM == "Darwin" else False + +OS_POSTFIX2_ARCH = dict( + Windows={"32bit": "win32", "64bit": "win64"}, + Linux={"32bit": "linux32", "64bit": "linux64"}, + Darwin={"32bit": "mac32", "64bit": "mac64"}, +) # Python version eg. 27 PYVERSION = str(sys.version_info[0])+str(sys.version_info[1]) @@ -89,6 +102,9 @@ # Will be overwritten through detect_cefpython_binary_dir() CEFPYTHON_BINARY = "CEFPYTHON_BINARY_NOTSET" +# Distrib directory +DISTRIB_DIR = os.path.join(BUILD_DIR, "DISTRIB_NOTSET") + # Build C++ projects directories BUILD_CEFPYTHON_APP = os.path.join(BUILD_CEFPYTHON, "cefpython_app_py{pyver}_{os}" @@ -165,58 +181,102 @@ os.environ["LOCALAPPDATA"]) -def detect_cef_binaries_libraries_dir(): +def get_postfix2_for_arch(arch): + return OS_POSTFIX2_ARCH[SYSTEM][arch] + + +def _detect_cef_binaries_libraries_dir(): """Detect cef binary directory created by automate.py eg. build/cef55_3.2883.1553.g80bd606_win32/ and set CEF_BINARIES_LIBRARIES to it, otherwise it will point to eg. build/cef_win32/ .""" global CEF_BINARIES_LIBRARIES if not os.path.exists(CEF_BINARIES_LIBRARIES): - version = get_cefpython_version() dirs = glob.glob(os.path.join( BUILD_DIR, - "cef{major}_{cef_version}_{os}{sep}" - .format(major=version["CHROME_VERSION_MAJOR"], - cef_version=version["CEF_VERSION"], - os=OS_POSTFIX2, - sep=os.sep))) + get_cef_binaries_libraries_basename(OS_POSTFIX2))) if len(dirs) == 1: CEF_BINARIES_LIBRARIES = os.path.normpath(dirs[0]) -def detect_cefpython_binary_dir(): - """Detect cefpython binary directory where cefpython modules - will be put. Eg. build/cefpython_56.0_win32/.""" - # Check cef version from header file and check cefpython version - # that was passed as command line argument to either build.py - # or make-installer.py. The CEFPYTHON_BINARY constant should - # only be used in those two scripts, so version number in sys.argv - # is expected. If not found then keep the default - # "CEFPYTHON_BINARY_NOTSET" value intact. +def get_cef_binaries_libraries_basename(postfix2): + version = get_cefpython_version() + return ("cef{major}_{cef_version}_{os}" + .format(major=version["CHROME_VERSION_MAJOR"], + cef_version=version["CEF_VERSION"], + os=postfix2)) + + +def get_cefpython_binary_basename(postfix2, ignore_error=False): cef_version = get_cefpython_version() - cmdline_version = get_version_from_command_line_args() + cmdline_version = get_version_from_command_line_args( + __file__, ignore_error=ignore_error) if not cmdline_version: + if not ignore_error: + raise Exception("Version arg not found in command line args") return # If cef_version is 56 then expect version from command line to # start with "56.". cef_major = cef_version["CHROME_VERSION_MAJOR"] if not cmdline_version.startswith("{major}.".format(major=cef_major)): + if not ignore_error: + raise Exception("cmd line arg major version != Chrome version") return - binary_dir = "cefpython_binary_{version}_{os}".format( + dirname = "cefpython_binary_{version}_{os}".format( version=cmdline_version, - os=OS_POSTFIX2) - binary_dir = os.path.join(BUILD_DIR, binary_dir) + os=postfix2) + return dirname + + +def get_setup_installer_basename(version, postfix2): + setup_basename = ("cefpython3-{version}-{os}-setup" + .format(version=version, os=postfix2)) + return setup_basename + + +def _detect_cefpython_binary_dir(): + """Detect cefpython binary directory where cefpython modules + will be put. Eg. build/cefpython_56.0_win32/.""" + # Check cef version from header file and check cefpython version + # that was passed as command line argument to either build.py + # or make-installer.py. The CEFPYTHON_BINARY constant should + # only be used in those two scripts, so version number in sys.argv + # is expected. If not found then keep the default + # "CEFPYTHON_BINARY_NOTSET" value intact. + dirname = get_cefpython_binary_basename(OS_POSTFIX2, ignore_error=True) + if not dirname: + return + binary_dir = os.path.join(BUILD_DIR, dirname) global CEFPYTHON_BINARY CEFPYTHON_BINARY = binary_dir -def get_version_from_command_line_args(): +def _detect_distrib_dir(): + global DISTRIB_DIR + version = get_version_from_command_line_args(__file__, ignore_error=True) + if version: + # Will only be set when called from scripts that had version + # number arg passed on command line: build.py, build_distrib.py, + # make_installer.py, etc. + dirname = "distrib_{version}".format(version=version) + DISTRIB_DIR = os.path.join(BUILD_DIR, dirname) + + +def get_version_from_command_line_args(caller_script, ignore_error=False): args = " ".join(sys.argv) - match = re.search(r"\b\d+\.\d+\b", args) + match = re.search(r"\b(\d+)\.\d+\b", args) if match: version = match.group(0) + major = match.group(1) + cef_version = get_cefpython_version() + if major != cef_version["CHROME_VERSION_MAJOR"]: + if ignore_error: + return "" + print("[{script}] ERROR: cmd arg major version != Chrome version" + .format(script=os.path.basename(caller_script))) + sys.exit(1) return version - return "" + return def get_cefpython_version(): @@ -252,5 +312,6 @@ def get_msvs_for_python(vs_prefix=False): sys.exit(1) -detect_cef_binaries_libraries_dir() -detect_cefpython_binary_dir() +_detect_cef_binaries_libraries_dir() +_detect_cefpython_binary_dir() +_detect_distrib_dir() diff --git a/tools/make_installer.py b/tools/make_installer.py index d13b7155d..518b62cc2 100644 --- a/tools/make_installer.py +++ b/tools/make_installer.py @@ -45,8 +45,7 @@ def main(): # Setup and package directories global SETUP_DIR, PKG_DIR - setup_dir_name = ("cefpython3-{version}-{os}-setup" - .format(version=VERSION, os=OS_POSTFIX2)) + setup_dir_name = get_setup_installer_basename(VERSION, OS_POSTFIX2) SETUP_DIR = os.path.join(BUILD_DIR, setup_dir_name) PKG_DIR = os.path.join(SETUP_DIR, "cefpython3") @@ -93,7 +92,7 @@ def main(): create_empty_log_file(os.path.join(PKG_DIR, "debug.log")) create_empty_log_file(os.path.join(PKG_DIR, "examples/debug.log")) - print("[make_installer.py] DONE. Installer package created: {setup_dir}" + print("[make_installer.py] Done. Installer package created: {setup_dir}" .format(setup_dir=SETUP_DIR)) # Optional generation of wheel package @@ -112,13 +111,13 @@ def main(): dist_dir = os.path.join(SETUP_DIR, "dist") files = glob.glob(os.path.join(dist_dir, "*.whl")) assert len(files) == 1 - print("[make_installer.py] DONE. Wheel package created: {0}" + print("[make_installer.py] Done. Wheel package created: {0}" .format(files[0])) def command_line_args(): global VERSION, WHEEL, WHEEL_ARGS - VERSION = get_version_from_command_line_args() + VERSION = get_version_from_command_line_args(__file__) if not VERSION: print(__doc__) sys.exit(1) From 4a9c639ee33f3e073d6ebd1f85fabf1f2bfe4f21 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 11 Mar 2017 17:41:34 +0100 Subject: [PATCH 003/398] Update README - Windows now supports Python 3 and 64-bit. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27067efc1..ad3afd0fe 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ bots. 1. Can be installed on all platforms using `pip install cefpython3` command 2. Downloads are available on [GitHub Releases](../../releases) pages -2. Windows support: 32-bit, Python 2.7 +2. Windows support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 (requirements: Windows 7+) 3. Linux support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 (requirements: Debian 7+ / Ubuntu 12.04+) From 8664386f67366e79be8c32aecf2bc2d02fb59ee3 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 11 Mar 2017 18:32:57 +0100 Subject: [PATCH 004/398] Fix platform tag in wheel package on Windows 64-bit --- tools/build_distrib.py | 7 +++++++ tools/common.py | 5 +++++ tools/installer/cefpython3.setup.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/build_distrib.py b/tools/build_distrib.py index 84c661d89..8368b63cd 100644 --- a/tools/build_distrib.py +++ b/tools/build_distrib.py @@ -6,6 +6,7 @@ Build distribution packages for all architectures and all supported python versions. +TODO: test_wheel_package_installation() TODO: Linux/Mac support. Currently runs only on Windows. Usage: @@ -89,6 +90,7 @@ def main(): make_packages(pythons_32bit[0], "32bit") if pythons_64bit: make_packages(pythons_64bit[0], "64bit") + test_wheel_package_installation() show_summary(pythons_32bit, pythons_64bit) @@ -415,6 +417,11 @@ def make_packages(python, arch): shutil.rmtree(setup_dir) +def test_wheel_package_installation(): + # PYPI_POSTFIX2_ARCH + pass # TODO + + def show_summary(pythons_32bit, pythons_64bit): print("[build_distrib.py] SUMMARY:") print(" Pythons 32bit ({count})".format(count=len(pythons_32bit))) diff --git a/tools/common.py b/tools/common.py index 576626f36..2b3d595fa 100644 --- a/tools/common.py +++ b/tools/common.py @@ -50,6 +50,11 @@ Linux={"32bit": "linux32", "64bit": "linux64"}, Darwin={"32bit": "mac32", "64bit": "mac64"}, ) +PYPI_POSTFIX2_ARCH = dict( + Windows={"32bit": "win32", "64bit": "win-amd64"}, + Linux={"32bit": "i686", "64bit": "x86_64"}, + Darwin={"64bit": "x86_64"}, +) # Python version eg. 27 PYVERSION = str(sys.version_info[0])+str(sys.version_info[1]) diff --git a/tools/installer/cefpython3.setup.py b/tools/installer/cefpython3.setup.py index 7cc55356e..3458e09a1 100644 --- a/tools/installer/cefpython3.setup.py +++ b/tools/installer/cefpython3.setup.py @@ -81,11 +81,11 @@ class custom_bdist_wheel(bdist_wheel): def get_tag(self): tag = bdist_wheel.get_tag(self) platform_tag = sysconfig.get_platform() + platform_tag = platform_tag.replace("-", "_") if platform.system() == "Linux": assert "linux" in platform_tag # "linux-x86_64" replace with "manylinux1_x86_64" platform_tag = platform_tag.replace("linux", "manylinux1") - platform_tag = platform_tag.replace("-", "_") elif platform.system() == "Darwin": # For explanation of Mac platform tags, see: # http://lepture.com/en/2014/python-on-a-hard-wheel From 26d64ba3d15b79e1a81937c7af493cb1dbabfeb1 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 11 Mar 2017 23:19:46 +0100 Subject: [PATCH 005/398] Update README, minor changes in description --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ad3afd0fe..9b1fe314d 100644 --- a/README.md +++ b/README.md @@ -12,20 +12,23 @@ Table of contents: ## Introduction -CEF Python is an open source project founded by [Czarek Tomczak] -(http://www.linkedin.com/in/czarektomczak) -in 2012 to provide python bindings for the [Chromium Embedded Framework] -(https://bitbucket.org/chromiumembedded/cef). -See the growing list of [applications using CEF] +CEF Python is a BSD-licensed open source project founded by [Czarek Tomczak] +(http://www.linkedin.com/in/czarektomczak) in 2012 and is based on +Google Chromium and the [CEF Framework] +(https://bitbucket.org/chromiumembedded/cef) projects. The Chromium +project focuses mainly on Google Chrome application development, while +CEF focuses on facilitating embedded browser use cases in third-party +applications. Lots of applications use CEF control, there are more than +[100 million CEF instances] (http://en.wikipedia.org/wiki/Chromium_Embedded_Framework#Applications_using_CEF) -on wikipedia. Examples of embedding CEF browser are available for many -popular GUI toolkits including: wxPython, PyGTK, PyQt, PySide, Kivy, -Panda3D, PyWin32 and PyGame/PyOpenGL. +installed around the world. Examples of embedding Chrome browser are +available for many popular GUI toolkits including: wxPython, PyGTK, +PyQt, PySide, Kivy, Panda3D and PyGame/PyOpenGL. There are many use cases for CEF. You can embed a web browser control based on Chromium with great HTML 5 support. You can use it to create a HTML 5 based GUI in an application, this can act as a replacement for -standard GUI toolkits like wxWidgets, Qt or GTK. You can render web +standard GUI toolkits such as wxWidgets, Qt or GTK. You can render web content off-screen in application that use custom drawing frameworks. You can use it for automated testing of existing applications. You can use it for web scraping or as a web crawler, or other kind of internet From 4d2b05ef74defd8efc18ae4b68aaa308964039f8 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 12 Mar 2017 09:50:56 +0100 Subject: [PATCH 006/398] Create Examples-README.md --- README.md | 6 ++--- docs/Examples.md | 4 ++++ examples/Examples-README.md | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 docs/Examples.md create mode 100644 examples/Examples-README.md diff --git a/README.md b/README.md index 9b1fe314d..7ebe356fb 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ CEF focuses on facilitating embedded browser use cases in third-party applications. Lots of applications use CEF control, there are more than [100 million CEF instances] (http://en.wikipedia.org/wiki/Chromium_Embedded_Framework#Applications_using_CEF) -installed around the world. Examples of embedding Chrome browser are -available for many popular GUI toolkits including: wxPython, PyGTK, -PyQt, PySide, Kivy, Panda3D and PyGame/PyOpenGL. +installed around the world. [Examples of embedding](examples/Examples-README.md) +Chrome browser are available for many popular GUI toolkits including: +wxPython, PyGTK, PyQt, PySide, Kivy, Panda3D and PyGame/PyOpenGL. There are many use cases for CEF. You can embed a web browser control based on Chromium with great HTML 5 support. You can use it to create diff --git a/docs/Examples.md b/docs/Examples.md new file mode 100644 index 000000000..aa2af612e --- /dev/null +++ b/docs/Examples.md @@ -0,0 +1,4 @@ +# Examples + +Examples are available in the examples/ root directory. See +[Examples-README.md](../examples/Examples-README.md). diff --git a/examples/Examples-README.md b/examples/Examples-README.md new file mode 100644 index 000000000..1257bd7cd --- /dev/null +++ b/examples/Examples-README.md @@ -0,0 +1,48 @@ +# Examples + +Table of contents: +* [Supported examples](#supported-examples) +* [More examples](#more-examples) + + +## Supported examples + +Examples provided in the examples/ root directory are actively +maintained: + +- [gtk2.py](gtk2.py): example for [PyGTK](http://www.pygtk.org/) + library (GTK 2) +- [gtk3.py](gtk3.py): example for [PyGObject/PyGI] + (https://wiki.gnome.org/Projects/PyGObject) library (GTK 3). + Currently broken on Linux/Mac, see top comments in sources. +- [hello_world.py](hello_world.py): doesn't require any third party + GUI framework +- [qt4.py](qt4.py): example for [PyQt4](https://wiki.python.org/moin/PyQt4) + and [PySide](https://wiki.qt.io/PySide) libraries (Qt 4) +- [tkinter_.py](tkinter_.py): example for [Tkinter] + (https://wiki.python.org/moin/TkInter). Currently broken on Mac. +- [wxpython.py](wxpython.py): example for wxPython + +If there are any issues in examples read top comments in sources +to see whether this is a known issue with available workarounds. + + +## More examples + +There are even more examples available, some of them are externally +maintained. + +- Kivy framework: + see [Kivy](https://github.com/cztomczak/cefpython/wiki/Kivy) wiki page. +- Panda3D game engine: + see [Panda3D](https://github.com/cztomczak/cefpython/wiki/Panda3D) wiki page. +- PyGame/PyOpenGL: + see [gist by AnishN](https://gist.github.com/AnishN/aa3bb27fc9d69319955ed9a8973cd40f) +- Screenshot example: + see [gist by stefanbacon](https://gist.github.com/stefanbacon/7b1571d57aee54aa9f8e9021b4848d06) +- Old PyWin32 example: + see [pywin32.py](https://github.com/cztomczak/cefpython/blob/cefpython31/cefpython/cef3/windows/binaries_32bit/pywin32.py) + in the cefpython31 branch + +There are ongoing efforts to add these examples to the official examples/ +directory, see issues in the tracker. From 8d53d65fbc721b9140af1030c3ce458b3e9e64df Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 12 Mar 2017 10:32:36 +0100 Subject: [PATCH 007/398] Update docs --- README.md | 6 ++++-- docs/API.md | 2 +- examples/Examples-README.md | 2 +- tools/build_cpp_projects.py | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7ebe356fb..af22e2053 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ wxPython, PyGTK, PyQt, PySide, Kivy, Panda3D and PyGame/PyOpenGL. There are many use cases for CEF. You can embed a web browser control based on Chromium with great HTML 5 support. You can use it to create a HTML 5 based GUI in an application, this can act as a replacement for -standard GUI toolkits such as wxWidgets, Qt or GTK. You can render web -content off-screen in application that use custom drawing frameworks. +standard GUI toolkits such as wxWidgets, Qt or GTK. In such case to +communicate between Python<>Javascript use javascript bindings or +embed an internal web server and talk using http requests. You can render +web content off-screen in application that use custom drawing frameworks. You can use it for automated testing of existing applications. You can use it for web scraping or as a web crawler, or other kind of internet bots. diff --git a/docs/API.md b/docs/API.md index 720df3f57..35ea01ea0 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,3 +1,3 @@ # API -API reference is available in the [api/](../api) root directory. +API reference is available in the [api/ root directory](../api). diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 1257bd7cd..b1b4109bd 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -1,4 +1,4 @@ -# Examples +# Examples README Table of contents: * [Supported examples](#supported-examples) diff --git a/tools/build_cpp_projects.py b/tools/build_cpp_projects.py index f7eae2a84..8bb6bf335 100644 --- a/tools/build_cpp_projects.py +++ b/tools/build_cpp_projects.py @@ -2,8 +2,10 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -"""Build C++ projects. This tool is executed by build.py on Windows -only currently. Output directories are in build/build_cefpython/. +"""Called by build.py internally. Builds C++ projects using +distutils/setuptools compilers. This tool is executed by build.py +on Windows only currently. Output directories are in +build/build_cefpython/. Usage: build_cpp_projects.py [--force] From cc227fdbfad8b04914a0cdba61d41188a556868a Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 12 Mar 2017 14:01:34 +0100 Subject: [PATCH 008/398] Update README with quick links to docs (#326)... Update apidocs.py tool to add API reference links also to README. --- .github/CONTRIBUTING.md | 11 +- .github/ISSUE_TEMPLATE.md | 12 +- README.md | 531 +++++++++++++++++++++++++++++++++++--- api/API-index.md | 1 + tools/apidocs.py | 90 +++++-- tools/common.py | 3 + 6 files changed, 574 insertions(+), 74 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f4f8ab2a5..dcb75da2e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,7 @@ -Please read this: -1. Do not create Issues to report problems and/or ask questions. Use the Forum for that. -2. Before creating an Issue it must first be discussed and confirmed on the Forum. -3. Always use the Forum: https://goo.gl/xz4cEF +Please do not create Issues. Issues must first be discussed +and confirmed on the Forum. Report issues on the Forum here: + +https://goo.gl/xz4cEF + +Please do not ask questions in existing issues. Ask questions +on the Forum, link above. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f4f8ab2a5..2bf4f13a8 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,8 @@ -Please read this: -1. Do not create Issues to report problems and/or ask questions. Use the Forum for that. -2. Before creating an Issue it must first be discussed and confirmed on the Forum. -3. Always use the Forum: https://goo.gl/xz4cEF +Please do not create Issues using this form. Issues must +first be discussed and confirmed on the Forum. Report issues +on the Forum here: + +https://goo.gl/xz4cEF + +Please do not ask questions in existing issues. Ask questions +on the Forum, link above. diff --git a/README.md b/README.md index af22e2053..44d7979ea 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,15 @@ Table of contents: * [Introduction](#introduction) -* [Versions](#versions) +* [Support](#support) +* [Releases](#releases) * [v50+ releases](#v50-releases) * [v31 release](#v31-release) -* [Support](#support) * [Support development](#support-development) * [Thanks](#thanks) +* [Quick links](#quick-links) + * [Docs](#docs) + * [API reference](#api-reference) ## Introduction @@ -29,53 +32,53 @@ There are many use cases for CEF. You can embed a web browser control based on Chromium with great HTML 5 support. You can use it to create a HTML 5 based GUI in an application, this can act as a replacement for standard GUI toolkits such as wxWidgets, Qt or GTK. In such case to -communicate between Python<>Javascript use javascript bindings or -embed an internal web server and talk using http requests. You can render -web content off-screen in application that use custom drawing frameworks. -You can use it for automated testing of existing applications. You can -use it for web scraping or as a web crawler, or other kind of internet -bots. +communicate between Python<>Javascript use [javascript bindings] +(api/JavascriptBindings.md) or embed an internal web server and talk +using http requests. You can render web content off-screen in +applications that use custom drawing frameworks. You can use it for +automated testing of existing applications. You can use it for web +scraping or as a web crawler, or other kind of internet bots. -## Versions - -### v50+ releases - -1. Can be installed on all platforms using `pip install cefpython3` command -2. Downloads are available on [GitHub Releases](../../releases) pages -2. Windows support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 - (requirements: Windows 7+) -3. Linux support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 - (requirements: Debian 7+ / Ubuntu 12.04+) -4. Mac support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 - (requirements: MacOS 10.9+) -5. Documentation is in the [docs/](docs) directory -6. API reference is in the [api/](api) directory -7. Additional documentation is in issues labelled [Knowledge Base] - (../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +## Support -### v31 release +- Ask questions, report problems and issues on the [Forum] + (https://groups.google.com/group/cefpython) +- Documentation is in the [docs/](docs) directory +- API reference is in the [api/](api) directory +- Additional documentation is in issues labelled [Knowledge Base] + (../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +- Wiki pages are deprecated and for v31 only -1. Downloads are available on [wiki pages](../../wiki#downloads) - and on GH Releases tagged [v31.2](../../releases/tag/v31.2) -2. Supports only Python 2.7 -3. Windows support: 32-bit and 64-bit (requirements: Windows XP+) -4. Linux support: 32-bit and 64-bit (requirements: Debian 7+ / Ubuntu 12.04+) -5. Mac support: 32-bit and 64-bit (requirements: MacOS 10.7+) -6. Documentation is on [wiki pages](../../wiki) -7. API reference is available in revision [169a1b2] - (../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) +## Releases -## Support +### v50+ releases -- Ask questions and report problems on the [Forum] - (https://groups.google.com/group/cefpython) +- Can be installed on all platforms using `pip install cefpython3` command +- Downloads are available on [GitHub Releases](../../releases) pages +- Windows support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 + (requirements: Windows 7+) +- Linux support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 + (requirements: Debian 7+ / Ubuntu 12.04+) +- Mac support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 + (requirements: MacOS 10.9+) - Documentation is in the [docs/](docs) directory - API reference is in the [api/](api) directory - Additional documentation is in issues labelled [Knowledge Base] (../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) -- Wiki pages are deprecated and for v31 only + +### v31 release + +- Downloads are available on [wiki pages](../../wiki#downloads) + and on GH Releases tagged [v31.2](../../releases/tag/v31.2) +- Supports only Python 2.7 +- Windows support: 32-bit and 64-bit (requirements: Windows XP+) +- Linux support: 32-bit and 64-bit (requirements: Debian 7+ / Ubuntu 12.04+) +- Mac support: 32-bit and 64-bit (requirements: MacOS 10.7+) +- Documentation is on [wiki pages](../../wiki) +- API reference is available in revision [169a1b2] + (../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) ## Support development @@ -114,3 +117,455 @@ directly. development of the off-screen rendering support * Thanks to Thomas Wusatiuk for sponsoring the development of the web response reading features + + +## Quick links + +### Docs + +- [Build instructions](docs/Build-instructions.md) +- [Knowledge Base](docs/Knowledge-Base.md) +- [Migration guide](docs/Migration-guide.md) +- [Tutorial](docs/Tutorial.md) + +### API reference + +* [Application settings](api/ApplicationSettings.md#application-settings) + * [accept_language_list](api/ApplicationSettings.md#accept_language_list) + * [auto_zooming](api/ApplicationSettings.md#auto_zooming) + * [background_color](api/ApplicationSettings.md#background_color) + * [browser_subprocess_path](api/ApplicationSettings.md#browser_subprocess_path) + * [cache_path](api/ApplicationSettings.md#cache_path) + * [command_line_args_disabled](api/ApplicationSettings.md#command_line_args_disabled) + * [context_menu](api/ApplicationSettings.md#context_menu) + * [downloads_enabled](api/ApplicationSettings.md#downloads_enabled) + * [external_message_pump](api/ApplicationSettings.md#external_message_pump) + * [framework_dir_path](api/ApplicationSettings.md#framework_dir_path) + * [ignore_certificate_errors](api/ApplicationSettings.md#ignore_certificate_errors) + * [javascript_flags](api/ApplicationSettings.md#javascript_flags) + * [locale](api/ApplicationSettings.md#locale) + * [locales_dir_path](api/ApplicationSettings.md#locales_dir_path) + * [debug](api/ApplicationSettings.md#debug) + * [log_file](api/ApplicationSettings.md#log_file) + * [log_severity](api/ApplicationSettings.md#log_severity) + * [multi_threaded_message_loop](api/ApplicationSettings.md#multi_threaded_message_loop) + * [net_security_expiration_enabled](api/ApplicationSettings.md#net_security_expiration_enabled) + * [pack_loading_disabled](api/ApplicationSettings.md#pack_loading_disabled) + * [persist_session_cookies](api/ApplicationSettings.md#persist_session_cookies) + * [persist_user_preferences](api/ApplicationSettings.md#persist_user_preferences) + * [product_version](api/ApplicationSettings.md#product_version) + * [remote_debugging_port](api/ApplicationSettings.md#remote_debugging_port) + * [resources_dir_path](api/ApplicationSettings.md#resources_dir_path) + * [single_process](api/ApplicationSettings.md#single_process) + * [string_encoding](api/ApplicationSettings.md#string_encoding) + * [uncaught_exception_stack_size](api/ApplicationSettings.md#uncaught_exception_stack_size) + * [unique_request_context_per_browser](api/ApplicationSettings.md#unique_request_context_per_browser) + * [user_agent](api/ApplicationSettings.md#user_agent) + * [user_data_path](api/ApplicationSettings.md#user_data_path) + * [windowless_rendering_enabled](api/ApplicationSettings.md#windowless_rendering_enabled) +* [Browser (object)](api/Browser.md#browser-object) + * [AddWordToDictionary](api/Browser.md#addwordtodictionary) + * [CanGoBack](api/Browser.md#cangoback) + * [CanGoForward](api/Browser.md#cangoforward) + * [CloseBrowser](api/Browser.md#closebrowser) + * [CloseDevTools](api/Browser.md#closedevtools) + * [DragTargetDragEnter](api/Browser.md#dragtargetdragenter) + * [DragTargetDragOver](api/Browser.md#dragtargetdragover) + * [DragTargetDragLeave](api/Browser.md#dragtargetdragleave) + * [DragTargetDrop](api/Browser.md#dragtargetdrop) + * [DragSourceEndedAt](api/Browser.md#dragsourceendedat) + * [DragSourceSystemDragEnded](api/Browser.md#dragsourcesystemdragended) + * [ExecuteFunction](api/Browser.md#executefunction) + * [ExecuteJavascript](api/Browser.md#executejavascript) + * [Find](api/Browser.md#find) + * [GetClientCallback](api/Browser.md#getclientcallback) + * [GetClientCallbacksDict](api/Browser.md#getclientcallbacksdict) + * [GetFocusedFrame](api/Browser.md#getfocusedframe) + * [GetFrame](api/Browser.md#getframe) + * [GetFrameByIdentifier](api/Browser.md#getframebyidentifier) + * [GetFrames](api/Browser.md#getframes) + * [GetFrameCount](api/Browser.md#getframecount) + * [GetFrameIdentifiers](api/Browser.md#getframeidentifiers) + * [GetFrameNames](api/Browser.md#getframenames) + * [GetJavascriptBindings](api/Browser.md#getjavascriptbindings) + * [GetMainFrame](api/Browser.md#getmainframe) + * [GetNSTextInputContext](api/Browser.md#getnstextinputcontext) + * [GetOpenerWindowHandle](api/Browser.md#getopenerwindowhandle) + * [GetOuterWindowHandle](api/Browser.md#getouterwindowhandle) + * [GetUrl](api/Browser.md#geturl) + * [GetUserData](api/Browser.md#getuserdata) + * [GetWindowHandle](api/Browser.md#getwindowhandle) + * [GetIdentifier](api/Browser.md#getidentifier) + * [GetZoomLevel](api/Browser.md#getzoomlevel) + * [GoBack](api/Browser.md#goback) + * [GoForward](api/Browser.md#goforward) + * [HandleKeyEventAfterTextInputClient](api/Browser.md#handlekeyeventaftertextinputclient) + * [HandleKeyEventBeforeTextInputClient](api/Browser.md#handlekeyeventbeforetextinputclient) + * [HasDocument](api/Browser.md#hasdocument) + * [IsFullscreen](api/Browser.md#isfullscreen) + * [IsLoading](api/Browser.md#isloading) + * [IsMouseCursorChangeDisabled](api/Browser.md#ismousecursorchangedisabled) + * [IsPopup](api/Browser.md#ispopup) + * [IsWindowRenderingDisabled](api/Browser.md#iswindowrenderingdisabled) + * [LoadUrl](api/Browser.md#loadurl) + * [Navigate](api/Browser.md#navigate) + * [NotifyMoveOrResizeStarted](api/Browser.md#notifymoveorresizestarted) + * [NotifyScreenInfoChanged](api/Browser.md#notifyscreeninfochanged) + * [ParentWindowWillClose](api/Browser.md#parentwindowwillclose) + * [Reload](api/Browser.md#reload) + * [ReloadIgnoreCache](api/Browser.md#reloadignorecache) + * [ReplaceMisspelling](api/Browser.md#replacemisspelling) + * [SetBounds](api/Browser.md#setbounds) + * [SendKeyEvent](api/Browser.md#sendkeyevent) + * [SendMouseClickEvent](api/Browser.md#sendmouseclickevent) + * [SendMouseMoveEvent](api/Browser.md#sendmousemoveevent) + * [SendMouseWheelEvent](api/Browser.md#sendmousewheelevent) + * [SendFocusEvent](api/Browser.md#sendfocusevent) + * [SendCaptureLostEvent](api/Browser.md#sendcapturelostevent) + * [SetClientCallback](api/Browser.md#setclientcallback) + * [SetClientHandler](api/Browser.md#setclienthandler) + * [SetFocus](api/Browser.md#setfocus) + * [SetMouseCursorChangeDisabled](api/Browser.md#setmousecursorchangedisabled) + * [SetJavascriptBindings](api/Browser.md#setjavascriptbindings) + * [SetUserData](api/Browser.md#setuserdata) + * [SetZoomLevel](api/Browser.md#setzoomlevel) + * [ShowDevTools](api/Browser.md#showdevtools) + * [StartDownload](api/Browser.md#startdownload) + * [StopLoad](api/Browser.md#stopload) + * [StopFinding](api/Browser.md#stopfinding) + * [ToggleFullscreen](api/Browser.md#togglefullscreen) + * [TryCloseBrowser](api/Browser.md#tryclosebrowser) + * [WasResized](api/Browser.md#wasresized) + * [WasHidden](api/Browser.md#washidden) +* [Browser settings](api/BrowserSettings.md#browser-settings) + * [Font settings](api/BrowserSettings.md#font-settings) + * [accept_language_list](api/BrowserSettings.md#accept_language_list) + * [application_cache_disabled](api/BrowserSettings.md#application_cache_disabled) + * [background_color](api/BrowserSettings.md#background_color) + * [databases_disabled](api/BrowserSettings.md#databases_disabled) + * [default_encoding](api/BrowserSettings.md#default_encoding) + * [dom_paste_disabled](api/BrowserSettings.md#dom_paste_disabled) + * [file_access_from_file_urls_allowed](api/BrowserSettings.md#file_access_from_file_urls_allowed) + * [image_load_disabled](api/BrowserSettings.md#image_load_disabled) + * [javascript_disabled](api/BrowserSettings.md#javascript_disabled) + * [javascript_open_windows_disallowed](api/BrowserSettings.md#javascript_open_windows_disallowed) + * [javascript_close_windows_disallowed](api/BrowserSettings.md#javascript_close_windows_disallowed) + * [javascript_access_clipboard_disallowed](api/BrowserSettings.md#javascript_access_clipboard_disallowed) + * [local_storage_disabled](api/BrowserSettings.md#local_storage_disabled) + * [plugins_disabled](api/BrowserSettings.md#plugins_disabled) + * [remote_fonts](api/BrowserSettings.md#remote_fonts) + * [shrink_standalone_images_to_fit](api/BrowserSettings.md#shrink_standalone_images_to_fit) + * [tab_to_links_disabled](api/BrowserSettings.md#tab_to_links_disabled) + * [text_area_resize_disabled](api/BrowserSettings.md#text_area_resize_disabled) + * [universal_access_from_file_urls_allowed](api/BrowserSettings.md#universal_access_from_file_urls_allowed) + * [user_style_sheet_location](api/BrowserSettings.md#user_style_sheet_location) + * [web_security_disabled](api/BrowserSettings.md#web_security_disabled) + * [webgl_disabled](api/BrowserSettings.md#webgl_disabled) + * [windowless_frame_rate](api/BrowserSettings.md#windowless_frame_rate) +* [Callback (object)](api/Callback.md#callback-object) + * [Continue](api/Callback.md#continue) + * [Cancel](api/Callback.md#cancel) +* [cefpython](api/cefpython.md#cefpython) + * [CreateBrowser](api/cefpython.md#createbrowser) + * [CreateBrowserSync](api/cefpython.md#createbrowsersync) + * [ExceptHook](api/cefpython.md#excepthook) + * [GetAppSetting](api/cefpython.md#getappsetting) + * [GetAppPath](api/cefpython.md#getapppath) + * [GetBrowserByWindowHandle](api/cefpython.md#getbrowserbywindowhandle) + * [GetCommandLineSwitch](api/cefpython.md#getcommandlineswitch) + * [GetGlobalClientCallback](api/cefpython.md#getglobalclientcallback) + * [GetModuleDirectory](api/cefpython.md#getmoduledirectory) + * [GetVersion](api/cefpython.md#getversion) + * [Initialize](api/cefpython.md#initialize) + * [IsThread](api/cefpython.md#isthread) + * [MessageLoop](api/cefpython.md#messageloop) + * [MessageLoopWork](api/cefpython.md#messageloopwork) + * [PostTask](api/cefpython.md#posttask) + * [QuitMessageLoop](api/cefpython.md#quitmessageloop) + * [SetGlobalClientCallback](api/cefpython.md#setglobalclientcallback) + * [SetOsModalLoop](api/cefpython.md#setosmodalloop) + * [Shutdown](api/cefpython.md#shutdown) +* [Command line switches](api/CommandLineSwitches.md#command-line-switches) + * [enable-media-stream](api/CommandLineSwitches.md#enable-media-stream) + * [proxy-server](api/CommandLineSwitches.md#proxy-server) + * [no-proxy-server](api/CommandLineSwitches.md#no-proxy-server) + * [disable-gpu](api/CommandLineSwitches.md#disable-gpu) +* [Cookie (class)](api/Cookie.md#cookie-class) + * [Set](api/Cookie.md#set) + * [Get](api/Cookie.md#get) + * [SetName](api/Cookie.md#setname) + * [GetName](api/Cookie.md#getname) + * [SetValue](api/Cookie.md#setvalue) + * [GetValue](api/Cookie.md#getvalue) + * [SetDomain](api/Cookie.md#setdomain) + * [GetDomain](api/Cookie.md#getdomain) + * [SetPath](api/Cookie.md#setpath) + * [GetPath](api/Cookie.md#getpath) + * [SetSecure](api/Cookie.md#setsecure) + * [GetSecure](api/Cookie.md#getsecure) + * [SetHttpOnly](api/Cookie.md#sethttponly) + * [GetHttpOnly](api/Cookie.md#gethttponly) + * [SetCreation](api/Cookie.md#setcreation) + * [GetCreation](api/Cookie.md#getcreation) + * [SetLastAccess](api/Cookie.md#setlastaccess) + * [GetLastAccess](api/Cookie.md#getlastaccess) + * [SetHasExpires](api/Cookie.md#sethasexpires) + * [GetHasExpires](api/Cookie.md#gethasexpires) + * [SetExpires](api/Cookie.md#setexpires) + * [GetExpires](api/Cookie.md#getexpires) +* [CookieManager (class)](api/CookieManager.md#cookiemanager-class) + * [GetGlobalManager](api/CookieManager.md#getglobalmanager) + * [CreateManager](api/CookieManager.md#createmanager) + * [SetSupportedSchemes](api/CookieManager.md#setsupportedschemes) + * [VisitAllCookies](api/CookieManager.md#visitallcookies) + * [VisitUrlCookies](api/CookieManager.md#visiturlcookies) + * [SetCookie](api/CookieManager.md#setcookie) + * [DeleteCookies](api/CookieManager.md#deletecookies) + * [SetStoragePath](api/CookieManager.md#setstoragepath) + * [FlushStore](api/CookieManager.md#flushstore) +* [CookieVisitor (interface)](api/CookieVisitor.md#cookievisitor-interface) + * [Visit](api/CookieVisitor.md#visit) +* [DisplayHandler (interface)](api/DisplayHandler.md#displayhandler-interface) + * [OnAddressChange](api/DisplayHandler.md#onaddresschange) + * [OnTitleChange](api/DisplayHandler.md#ontitlechange) + * [OnTooltip](api/DisplayHandler.md#ontooltip) + * [OnStatusMessage](api/DisplayHandler.md#onstatusmessage) + * [OnConsoleMessage](api/DisplayHandler.md#onconsolemessage) +* [DownloadHandler](api/DownloadHandler.md#downloadhandler) +* [DpiAware (class)](api/DpiAware.md#dpiaware-class) + * [CalculateWindowSize](api/DpiAware.md#calculatewindowsize) + * [GetSystemDpi](api/DpiAware.md#getsystemdpi) + * [IsProcessDpiAware](api/DpiAware.md#isprocessdpiaware) + * [SetProcessDpiAware](api/DpiAware.md#setprocessdpiaware) +* [DragData (object)](api/DragData.md#dragdata-object) + * [IsLink](api/DragData.md#islink) + * [IsFragment](api/DragData.md#isfragment) + * [GetLinkUrl](api/DragData.md#getlinkurl) + * [GetLinkTitle](api/DragData.md#getlinktitle) + * [GetFragmentText](api/DragData.md#getfragmenttext) + * [GetFragmentHtml](api/DragData.md#getfragmenthtml) + * [GetImage](api/DragData.md#getimage) + * [HasImage](api/DragData.md#hasimage) +* [FocusHandler (interface)](api/FocusHandler.md#focushandler-interface) + * [OnTakeFocus](api/FocusHandler.md#ontakefocus) + * [OnSetFocus](api/FocusHandler.md#onsetfocus) + * [OnGotFocus](api/FocusHandler.md#ongotfocus) +* [Frame (object)](api/Frame.md#frame-object) + * [Copy](api/Frame.md#copy) + * [Cut](api/Frame.md#cut) + * [Delete](api/Frame.md#delete) + * [ExecuteFunction](api/Frame.md#executefunction) + * [ExecuteJavascript](api/Frame.md#executejavascript) + * [GetBrowser](api/Frame.md#getbrowser) + * [GetParent](api/Frame.md#getparent) + * [GetIdentifier](api/Frame.md#getidentifier) + * [GetBrowserIdentifier](api/Frame.md#getbrowseridentifier) + * [GetName](api/Frame.md#getname) + * [GetParent](api/Frame.md#getparent) + * [GetProperty](api/Frame.md#getproperty) + * [GetSource](api/Frame.md#getsource) + * [GetText](api/Frame.md#gettext) + * [GetUrl](api/Frame.md#geturl) + * [IsFocused](api/Frame.md#isfocused) + * [IsMain](api/Frame.md#ismain) + * [IsValid](api/Frame.md#isvalid) + * [LoadString](api/Frame.md#loadstring) + * [LoadUrl](api/Frame.md#loadurl) + * [Paste](api/Frame.md#paste) + * [Redo](api/Frame.md#redo) + * [SelectAll](api/Frame.md#selectall) + * [SetProperty](api/Frame.md#setproperty) + * [Undo](api/Frame.md#undo) + * [ViewSource](api/Frame.md#viewsource) +* [Image (object)](api/Image.md#image-object) + * [GetAsBitmap](api/Image.md#getasbitmap) + * [GetAsPng](api/Image.md#getaspng) + * [GetHeight](api/Image.md#getheight) + * [GetWidth](api/Image.md#getwidth) +* [JavascriptBindings (class)](api/JavascriptBindings.md#javascriptbindings-class) + * [\_\_init\_\_](api/JavascriptBindings.md#__init__) + * [IsValueAllowed](api/JavascriptBindings.md#isvalueallowed) + * [Rebind](api/JavascriptBindings.md#rebind) + * [SetFunction](api/JavascriptBindings.md#setfunction) + * [SetObject](api/JavascriptBindings.md#setobject) + * [SetProperty](api/JavascriptBindings.md#setproperty) +* [JavascriptCallback (object)](api/JavascriptCallback.md#javascriptcallback-object) + * [Call](api/JavascriptCallback.md#call) + * [GetName](api/JavascriptCallback.md#getname) +* [JavascriptDialogHandler (interface)](api/JavascriptDialogHandler.md#javascriptdialoghandler-interface) + * [Continue](api/JavascriptDialogHandler.md#continue) + * [OnJavascriptDialog](api/JavascriptDialogHandler.md#onjavascriptdialog) + * [OnBeforeUnloadJavascriptDialog](api/JavascriptDialogHandler.md#onbeforeunloadjavascriptdialog) + * [OnResetJavascriptDialogState](api/JavascriptDialogHandler.md#onresetjavascriptdialogstate) + * [OnJavascriptDialogClosed](api/JavascriptDialogHandler.md#onjavascriptdialogclosed) +* [KeyboardHandler (interface)](api/KeyboardHandler.md#keyboardhandler-interface) + * [OnPreKeyEvent](api/KeyboardHandler.md#onprekeyevent) + * [OnKeyEvent](api/KeyboardHandler.md#onkeyevent) +* [LifespanHandler (interface)](api/LifespanHandler.md#lifespanhandler-interface) + * [OnBeforePopup](api/LifespanHandler.md#onbeforepopup) + * [_OnAfterCreated](api/LifespanHandler.md#_onaftercreated) + * [DoClose](api/LifespanHandler.md#doclose) + * [OnBeforeClose](api/LifespanHandler.md#onbeforeclose) +* [LoadHandler (interface)](api/LoadHandler.md#loadhandler-interface) + * [OnLoadingStateChange](api/LoadHandler.md#onloadingstatechange) + * [OnLoadStart](api/LoadHandler.md#onloadstart) + * [OnDomReady](api/LoadHandler.md#ondomready) + * [OnLoadEnd](api/LoadHandler.md#onloadend) + * [OnLoadError](api/LoadHandler.md#onloaderror) +* [Network error](api/NetworkError.md#network-error) + * [ERR_NONE](api/NetworkError.md#err_none) + * [ERR_ABORTED](api/NetworkError.md#err_aborted) + * [ERR_ACCESS_DENIED](api/NetworkError.md#err_access_denied) + * [ERR_ADDRESS_INVALID](api/NetworkError.md#err_address_invalid) + * [ERR_ADDRESS_UNREACHABLE](api/NetworkError.md#err_address_unreachable) + * [ERR_CACHE_MISS](api/NetworkError.md#err_cache_miss) + * [ERR_CERT_AUTHORITY_INVALID](api/NetworkError.md#err_cert_authority_invalid) + * [ERR_CERT_COMMON_NAME_INVALID](api/NetworkError.md#err_cert_common_name_invalid) + * [ERR_CERT_CONTAINS_ERRORS](api/NetworkError.md#err_cert_contains_errors) + * [ERR_CERT_DATE_INVALID](api/NetworkError.md#err_cert_date_invalid) + * [ERR_CERT_END](api/NetworkError.md#err_cert_end) + * [ERR_CERT_INVALID](api/NetworkError.md#err_cert_invalid) + * [ERR_CERT_NO_REVOCATION_MECHANISM](api/NetworkError.md#err_cert_no_revocation_mechanism) + * [ERR_CERT_REVOKED](api/NetworkError.md#err_cert_revoked) + * [ERR_CERT_UNABLE_TO_CHECK_REVOCATION](api/NetworkError.md#err_cert_unable_to_check_revocation) + * [ERR_CONNECTION_ABORTED](api/NetworkError.md#err_connection_aborted) + * [ERR_CONNECTION_CLOSED](api/NetworkError.md#err_connection_closed) + * [ERR_CONNECTION_FAILED](api/NetworkError.md#err_connection_failed) + * [ERR_CONNECTION_REFUSED](api/NetworkError.md#err_connection_refused) + * [ERR_CONNECTION_RESET](api/NetworkError.md#err_connection_reset) + * [ERR_DISALLOWED_URL_SCHEME](api/NetworkError.md#err_disallowed_url_scheme) + * [ERR_EMPTY_RESPONSE](api/NetworkError.md#err_empty_response) + * [ERR_FAILED](api/NetworkError.md#err_failed) + * [ERR_FILE_NOT_FOUND](api/NetworkError.md#err_file_not_found) + * [ERR_FILE_TOO_BIG](api/NetworkError.md#err_file_too_big) + * [ERR_INSECURE_RESPONSE](api/NetworkError.md#err_insecure_response) + * [ERR_INTERNET_DISCONNECTED](api/NetworkError.md#err_internet_disconnected) + * [ERR_INVALID_ARGUMENT](api/NetworkError.md#err_invalid_argument) + * [ERR_INVALID_CHUNKED_ENCODING](api/NetworkError.md#err_invalid_chunked_encoding) + * [ERR_INVALID_HANDLE](api/NetworkError.md#err_invalid_handle) + * [ERR_INVALID_RESPONSE](api/NetworkError.md#err_invalid_response) + * [ERR_INVALID_URL](api/NetworkError.md#err_invalid_url) + * [ERR_METHOD_NOT_SUPPORTED](api/NetworkError.md#err_method_not_supported) + * [ERR_NAME_NOT_RESOLVED](api/NetworkError.md#err_name_not_resolved) + * [ERR_NO_SSL_VERSIONS_ENABLED](api/NetworkError.md#err_no_ssl_versions_enabled) + * [ERR_NOT_IMPLEMENTED](api/NetworkError.md#err_not_implemented) + * [ERR_RESPONSE_HEADERS_TOO_BIG](api/NetworkError.md#err_response_headers_too_big) + * [ERR_SSL_CLIENT_AUTH_CERT_NEEDED](api/NetworkError.md#err_ssl_client_auth_cert_needed) + * [ERR_SSL_PROTOCOL_ERROR](api/NetworkError.md#err_ssl_protocol_error) + * [ERR_SSL_RENEGOTIATION_REQUESTED](api/NetworkError.md#err_ssl_renegotiation_requested) + * [ERR_SSL_VERSION_OR_CIPHER_MISMATCH](api/NetworkError.md#err_ssl_version_or_cipher_mismatch) + * [ERR_TIMED_OUT](api/NetworkError.md#err_timed_out) + * [ERR_TOO_MANY_REDIRECTS](api/NetworkError.md#err_too_many_redirects) + * [ERR_TUNNEL_CONNECTION_FAILED](api/NetworkError.md#err_tunnel_connection_failed) + * [ERR_UNEXPECTED](api/NetworkError.md#err_unexpected) + * [ERR_UNEXPECTED_PROXY_AUTH](api/NetworkError.md#err_unexpected_proxy_auth) + * [ERR_UNKNOWN_URL_SCHEME](api/NetworkError.md#err_unknown_url_scheme) + * [ERR_UNSAFE_PORT](api/NetworkError.md#err_unsafe_port) + * [ERR_UNSAFE_REDIRECT](api/NetworkError.md#err_unsafe_redirect) +* [PaintBuffer (object)](api/PaintBuffer.md#paintbuffer-object) + * [GetIntPointer](api/PaintBuffer.md#getintpointer) + * [GetString](api/PaintBuffer.md#getstring) +* [RenderHandler (interface)](api/RenderHandler.md#renderhandler-interface) + * [GetRootScreenRect](api/RenderHandler.md#getrootscreenrect) + * [GetViewRect](api/RenderHandler.md#getviewrect) + * [GetScreenRect](api/RenderHandler.md#getscreenrect) + * [GetScreenPoint](api/RenderHandler.md#getscreenpoint) + * [OnPopupShow](api/RenderHandler.md#onpopupshow) + * [OnPopupSize](api/RenderHandler.md#onpopupsize) + * [OnPaint](api/RenderHandler.md#onpaint) + * [OnCursorChange](api/RenderHandler.md#oncursorchange) + * [OnScrollOffsetChanged](api/RenderHandler.md#onscrolloffsetchanged) + * [StartDragging](api/RenderHandler.md#startdragging) + * [UpdateDragCursor](api/RenderHandler.md#updatedragcursor) +* [Request (class)](api/Request.md#request-class) + * [CreateRequest](api/Request.md#createrequest) + * [IsReadOnly](api/Request.md#isreadonly) + * [GetUrl](api/Request.md#geturl) + * [SetUrl](api/Request.md#seturl) + * [GetMethod](api/Request.md#getmethod) + * [SetMethod](api/Request.md#setmethod) + * [GetPostData](api/Request.md#getpostdata) + * [SetPostData](api/Request.md#setpostdata) + * [GetHeaderMap](api/Request.md#getheadermap) + * [GetHeaderMultimap](api/Request.md#getheadermultimap) + * [SetHeaderMap](api/Request.md#setheadermap) + * [SetHeaderMultimap](api/Request.md#setheadermultimap) + * [GetFlags](api/Request.md#getflags) + * [SetFlags](api/Request.md#setflags) + * [GetFirstPartyForCookies](api/Request.md#getfirstpartyforcookies) + * [SetFirstPartyForCookies](api/Request.md#setfirstpartyforcookies) + * [GetResourceType](api/Request.md#getresourcetype) + * [GetTransitionType](api/Request.md#gettransitiontype) +* [RequestHandler (interface)](api/RequestHandler.md#requesthandler-interface) + * [OnBeforeBrowse](api/RequestHandler.md#onbeforebrowse) + * [OnBeforeResourceLoad](api/RequestHandler.md#onbeforeresourceload) + * [GetResourceHandler](api/RequestHandler.md#getresourcehandler) + * [OnResourceRedirect](api/RequestHandler.md#onresourceredirect) + * [OnResourceResponse](api/RequestHandler.md#onresourceresponse) + * [GetAuthCredentials](api/RequestHandler.md#getauthcredentials) + * [OnQuotaRequest](api/RequestHandler.md#onquotarequest) + * [GetCookieManager](api/RequestHandler.md#getcookiemanager) + * [OnProtocolExecution](api/RequestHandler.md#onprotocolexecution) + * [_OnBeforePluginLoad](api/RequestHandler.md#_onbeforepluginload) + * [_OnCertificateError](api/RequestHandler.md#_oncertificateerror) + * [OnRendererProcessTerminated](api/RequestHandler.md#onrendererprocessterminated) + * [OnPluginCrashed](api/RequestHandler.md#onplugincrashed) +* [ResourceHandler (interface)](api/ResourceHandler.md#resourcehandler-interface) + * [ProcessRequest](api/ResourceHandler.md#processrequest) + * [GetResponseHeaders](api/ResourceHandler.md#getresponseheaders) + * [ReadResponse](api/ResourceHandler.md#readresponse) + * [CanGetCookie](api/ResourceHandler.md#cangetcookie) + * [CanSetCookie](api/ResourceHandler.md#cansetcookie) + * [Cancel](api/ResourceHandler.md#cancel) +* [Response (object)](api/Response.md#response-object) + * [IsReadOnly](api/Response.md#isreadonly) + * [GetStatus](api/Response.md#getstatus) + * [SetStatus](api/Response.md#setstatus) + * [GetStatusText](api/Response.md#getstatustext) + * [SetStatusText](api/Response.md#setstatustext) + * [GetMimeType](api/Response.md#getmimetype) + * [SetMimeType](api/Response.md#setmimetype) + * [GetHeader](api/Response.md#getheader) + * [GetHeaderMap](api/Response.md#getheadermap) + * [GetHeaderMultimap](api/Response.md#getheadermultimap) + * [SetHeaderMap](api/Response.md#setheadermap) + * [SetHeaderMultimap](api/Response.md#setheadermultimap) +* [StringVisitor (interface)](api/StringVisitor.md#stringvisitor-interface) + * [Visit](api/StringVisitor.md#visit) +* [V8ContextHandler (interface)](api/V8ContextHandler.md#v8contexthandler-interface) + * [OnContextCreated](api/V8ContextHandler.md#oncontextcreated) + * [OnContextReleased](api/V8ContextHandler.md#oncontextreleased) +* [Virtual Key codes](api/VirtualKey.md#virtual-key-codes) +* [WebPluginInfo (object)](api/WebPluginInfo.md#webplugininfo-object) + * [GetName](api/WebPluginInfo.md#getname) + * [GetPath](api/WebPluginInfo.md#getpath) + * [GetVersion](api/WebPluginInfo.md#getversion) + * [GetDescription](api/WebPluginInfo.md#getdescription) +* [WebRequest (class)](api/WebRequest.md#webrequest-class) + * [Create](api/WebRequest.md#create) + * [GetRequest](api/WebRequest.md#getrequest) + * [GetRequestStatus](api/WebRequest.md#getrequeststatus) + * [GetRequestError](api/WebRequest.md#getrequesterror) + * [GetResponse](api/WebRequest.md#getresponse) + * [Cancel](api/WebRequest.md#cancel) +* [WebRequestClient (interface)](api/WebRequestClient.md#webrequestclient-interface) + * [OnUploadProgress](api/WebRequestClient.md#onuploadprogress) + * [OnDownloadProgress](api/WebRequestClient.md#ondownloadprogress) + * [OnDownloadData](api/WebRequestClient.md#ondownloaddata) + * [OnRequestComplete](api/WebRequestClient.md#onrequestcomplete) +* [WindowInfo (class)](api/WindowInfo.md#windowinfo-class) + * [SetAsChild](api/WindowInfo.md#setaschild) + * [SetAsPopup](api/WindowInfo.md#setaspopup) + * [SetAsOffscreen](api/WindowInfo.md#setasoffscreen) + * [SetTransparentPainting](api/WindowInfo.md#settransparentpainting) +* [WindowUtils (class)](api/WindowUtils.md#windowutils-class) + * [OnSetFocus ](api/WindowUtils.md#onsetfocus-win) + * [OnSize ](api/WindowUtils.md#onsize-win) + * [OnEraseBackground ](api/WindowUtils.md#onerasebackground-win) + * [SetTitle ](api/WindowUtils.md#settitle-win) + * [SetIcon ](api/WindowUtils.md#seticon-win) + * [GetParentHandle](api/WindowUtils.md#getparenthandle) + * [IsWindowHandle](api/WindowUtils.md#iswindowhandle) + * [gtk_plug_new ](api/WindowUtils.md#gtk_plug_new-linux) + * [gtk_widget_show ](api/WindowUtils.md#gtk_widget_show-linux) + * [InstallX11ErrorHandlers ](api/WindowUtils.md#installx11errorhandlers-linux) diff --git a/api/API-index.md b/api/API-index.md index 3fe74ca2e..df2dc4bd2 100644 --- a/api/API-index.md +++ b/api/API-index.md @@ -147,6 +147,7 @@ * [GetCommandLineSwitch](cefpython.md#getcommandlineswitch) * [GetGlobalClientCallback](cefpython.md#getglobalclientcallback) * [GetModuleDirectory](cefpython.md#getmoduledirectory) + * [GetVersion](cefpython.md#getversion) * [Initialize](cefpython.md#initialize) * [IsThread](cefpython.md#isthread) * [MessageLoop](cefpython.md#messageloop) diff --git a/tools/apidocs.py b/tools/apidocs.py index 69e6ed9dd..0ba3aa5e1 100644 --- a/tools/apidocs.py +++ b/tools/apidocs.py @@ -2,7 +2,7 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -"""Generate API docs from sources. +"""Generate API reference index in api/ directory and in root/README.md. TODO: - generate api/ docs from Cython sources @@ -12,59 +12,93 @@ (eg. TerminationStatus, KeyEvent, KeyEventFlags) """ -import os +from common import * + import glob +import os import re -# Constants -API_DIR = os.path.join(os.path.dirname(__file__), "..", "api") - def main(): """Main entry point.""" - api_index() + api_links = generate_api_links() + update_api_index_file(api_links) + update_readme_file(api_links) -def api_index(): - """Generate API-index.md file with all modules/classes/funcs/methods.""" - files = glob.glob(os.path.join(API_DIR, "*.md")) +def update_api_index_file(api_links): + """Create or update API-index.md file.""" contents = ("[API categories](API-categories.md#api-categories) | " + "[API index](API-index.md#api-index)\n\n" + "# API index\n\n") + contents += api_links + index_file = os.path.join(API_DIR, "API-index.md") + with open(index_file, "rb") as fo: + current_contents = fo.read().decode("utf-8") + if contents == current_contents: + print("No changes: %s/%s" % (os.path.basename(API_DIR), + os.path.basename(index_file))) + return + with open(index_file, "wb") as fo: + fo.write(contents.encode("utf-8")) + print("Updated: %s/%s" % (os.path.basename(API_DIR), + os.path.basename(index_file))) + + +def update_readme_file(api_links): + """Update root/README.md with API reference links.""" + api_links = api_links.replace("](", "](api/") + readme_file = os.path.join(ROOT_DIR, "README.md") + with open(readme_file, "rb") as fo: + current_contents = fo.read().decode("utf-8") + contents = current_contents + contents = re.sub((r"### API reference\s+" + r"(\s*\*[ ]\[[^\r\n\[\]]+\]\([^\r\n()]+\)\s+)*"), + ("### API reference\r\n\r\n{api_links}" + .format(api_links=api_links)), + contents) + if contents == current_contents: + print("No changes: /%s" % (os.path.basename(readme_file))) + return + with open(readme_file, "wb") as fo: + fo.write(contents.encode("utf-8")) + print("Updated: /%s" % (os.path.basename(readme_file))) + + +def generate_api_links(): + """Generate API index with all modules / classes / functions.""" + contents = "" + files = glob.glob(os.path.join(API_DIR, "*.md")) files = sorted(files, key=lambda s: s.lower()) for file_ in files: + # Ignore API-index.md and API-categories.md files if "API-" in file_: continue with open(file_, "rb") as fo: - raw_mdcontents = fo.read().decode("utf-8") - - parsable_mdcontents = re.sub(r"```[\s\S]+?```", "", raw_mdcontents) - allmatches = re.findall(r"^(#|###)\s+(.*)", parsable_mdcontents, - re.MULTILINE) - for allmatch in allmatches: - hlevel = allmatch[0].strip() - title = allmatch[1].strip() + md_contents = fo.read().decode("utf-8") + md_contents = re.sub(r"```[\s\S]+?```", "", md_contents) + matches = re.findall(r"^(#|###)\s+(.*)", md_contents, + re.MULTILINE) + for match in matches: + heading_level = match[0].strip() + title = match[1].strip() title = title.strip() - if hlevel == "#": + if heading_level == "#": indent = "" - link = os.path.basename(file_) + "#" + headinghash(title) - elif hlevel == "###": + link = os.path.basename(file_) + "#" + get_heading_hash(title) + elif heading_level == "###": indent = " " - link = os.path.basename(file_) + "#" + headinghash(title) + link = os.path.basename(file_) + "#" + get_heading_hash(title) # hash generation needs complete title. Now we can strip some. title = re.sub(r"\(.*", r"", title) else: assert False, "Heading level unsupported" contents += (indent + "* " + "[%s](%s)\n" % (title, link)) - indexfile = os.path.join(API_DIR, "API-index.md") - with open(indexfile, "wb") as fo: - fo.write(contents.encode("utf-8")) - print("Created %s in %s" % (os.path.basename(indexfile), API_DIR)) - print("Done") + return contents -def headinghash(title): - """Get a link hash for a heading H1,H2,H3.""" +def get_heading_hash(title): + """Get a link hash for headings H1, H2, H3.""" hash_ = title.lower() hash_ = re.sub(r"[^a-z0-9_\- ]+", r"", hash_) hash_ = hash_.replace(" ", "-") diff --git a/tools/common.py b/tools/common.py index 2b3d595fa..360494172 100644 --- a/tools/common.py +++ b/tools/common.py @@ -97,6 +97,9 @@ assert __file__ ROOT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +# API reference +API_DIR = os.path.join(ROOT_DIR, "api") + # Build directories BUILD_DIR = os.path.join(ROOT_DIR, "build") BUILD_CEFPYTHON = os.path.join(BUILD_DIR, "build_cefpython") From 2ae6f08c73a661c335c527524312f0174c15842e Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 12 Mar 2017 14:44:34 +0100 Subject: [PATCH 009/398] Add API categories to README (#326)... Update apidocs.py. --- README.md | 63 ++++++++++++++++++++++++++++++++++++-- api/API-categories.md | 70 +++++++++++++++++++++---------------------- tools/apidocs.py | 40 ++++++++++++++++++++----- 3 files changed, 129 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 44d7979ea..3b391bf7c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ Table of contents: * [Thanks](#thanks) * [Quick links](#quick-links) * [Docs](#docs) - * [API reference](#api-reference) + * [API categories](#api-categories) + * [API index](#api-index) ## Introduction @@ -128,7 +129,65 @@ directly. - [Migration guide](docs/Migration-guide.md) - [Tutorial](docs/Tutorial.md) -### API reference + +### API categories + +#### Modules + + * [cefpython](cefpython.md#cefpython) module + + +#### Settings + + * [ApplicationSettings](ApplicationSettings.md#application-settings) dictionary + * [BrowserSettings](BrowserSettings.md#browser-settings) dictionary + * [CommandLineSwitches](CommandLineSwitches.md#command-line-switches) dictionary + + +#### Classes and objects + + * [Browser](Browser.md#browser-object) object + * [Callback](Callback.md#callback-object) object + * [Cookie](Cookie.md#cookie-class) class + * [CookieManager](CookieManager.md#cookiemanager-class) class + * [DpiAware](DpiAware.md#dpiaware-class) class (Win) + * [DragData](DragData.md#dragdata-object) object + * [Frame](Frame.md#frame-object) object + * [Image](Image.md#image-object) object + * [JavascriptBindings](JavascriptBindings.md#javascriptbindings-class) class + * [JavascriptCallback](JavascriptCallback.md#javascriptcallback-object) object + * [PaintBuffer](PaintBuffer.md#paintbuffer-object) object + * [Request](Request.md#request-class) class + * [Response](Response.md#response-object) object + * [WebPluginInfo](WebPluginInfo.md#webplugininfo-object) object + * [WebRequest](WebRequest.md#webrequest-class) class + * [WindowInfo](WindowInfo.md#windowinfo-class) class + * [WindowUtils](WindowUtils.md#windowutils-class) class + + +#### Handlers (interfaces) + + * [DisplayHandler](DisplayHandler.md#displayhandler-interface) + * [DownloadHandler](DownloadHandler.md#downloadhandler) + * [FocusHandler](FocusHandler.md#focushandler-interface) + * [JavascriptDialogHandler](JavascriptDialogHandler.md#javascriptdialoghandler-interface) + * [KeyboardHandler](KeyboardHandler.md#keyboardhandler-interface) + * [LifespanHandler](LifespanHandler.md#lifespanhandler-interface) + * [LoadHandler](LoadHandler.md#loadhandler-interface) + * [RenderHandler](RenderHandler.md#renderhandler-interface) + * [RequestHandler](RequestHandler.md#requesthandler-interface) + * [ResourceHandler](ResourceHandler.md#resourcehandler-interface) + * [V8ContextHandler](V8ContextHandler.md#v8contexthandler-interface) + + +#### Other interfaces + + * [CookieVisitor](CookieVisitor.md#cookievisitor-interface) interface + * [StringVisitor](StringVisitor.md#stringvisitor-interface) interface + * [WebRequestClient](WebRequestClient.md#webrequestclient-interface) interface + + +### API index * [Application settings](api/ApplicationSettings.md#application-settings) * [accept_language_list](api/ApplicationSettings.md#accept_language_list) diff --git a/api/API-categories.md b/api/API-categories.md index a338fc34c..cceb66fe7 100644 --- a/api/API-categories.md +++ b/api/API-categories.md @@ -5,55 +5,55 @@ ### Modules - * [cefpython](cefpython.md) module + * [cefpython](cefpython.md#cefpython) module ### Settings - * [ApplicationSettings](ApplicationSettings.md) dictionary - * [BrowserSettings](BrowserSettings.md) dictionary - * [CommandLineSwitches](CommandLineSwitches.md) dictionary + * [ApplicationSettings](ApplicationSettings.md#application-settings) dictionary + * [BrowserSettings](BrowserSettings.md#browser-settings) dictionary + * [CommandLineSwitches](CommandLineSwitches.md#command-line-switches) dictionary ### Classes and objects - * [Browser](Browser.md) object - * [Callback](Callback.md) object - * [Cookie](Cookie.md) class - * [CookieManager](CookieManager.md) class - * [DpiAware](DpiAware.md) class (Win) - * [DragData](DragData.md) object - * [Frame](Frame.md) object - * [Image](Image.md) object - * [JavascriptBindings](JavascriptBindings.md) class - * [JavascriptCallback](JavascriptCallback.md) object - * [PaintBuffer](PaintBuffer.md) object - * [Request](Request.md) class - * [Response](Response.md) object - * [WebPluginInfo](WebPluginInfo.md) object - * [WebRequest](WebRequest.md) class - * [WindowInfo](WindowInfo.md) class - * [WindowUtils](WindowUtils.md) class + * [Browser](Browser.md#browser-object) object + * [Callback](Callback.md#callback-object) object + * [Cookie](Cookie.md#cookie-class) class + * [CookieManager](CookieManager.md#cookiemanager-class) class + * [DpiAware](DpiAware.md#dpiaware-class) class (Win) + * [DragData](DragData.md#dragdata-object) object + * [Frame](Frame.md#frame-object) object + * [Image](Image.md#image-object) object + * [JavascriptBindings](JavascriptBindings.md#javascriptbindings-class) class + * [JavascriptCallback](JavascriptCallback.md#javascriptcallback-object) object + * [PaintBuffer](PaintBuffer.md#paintbuffer-object) object + * [Request](Request.md#request-class) class + * [Response](Response.md#response-object) object + * [WebPluginInfo](WebPluginInfo.md#webplugininfo-object) object + * [WebRequest](WebRequest.md#webrequest-class) class + * [WindowInfo](WindowInfo.md#windowinfo-class) class + * [WindowUtils](WindowUtils.md#windowutils-class) class ### Handlers (interfaces) - * [DisplayHandler](DisplayHandler.md) - * [DownloadHandler](DownloadHandler.md) - * [FocusHandler](FocusHandler.md) - * [JavascriptDialogHandler](JavascriptDialogHandler.md) - * [KeyboardHandler](KeyboardHandler.md) - * [LifespanHandler](LifespanHandler.md) - * [LoadHandler](LoadHandler.md) - * [RenderHandler](RenderHandler.md) - * [RequestHandler](RequestHandler.md) - * [ResourceHandler](ResourceHandler.md) - * [V8ContextHandler](V8ContextHandler.md) + * [DisplayHandler](DisplayHandler.md#displayhandler-interface) + * [DownloadHandler](DownloadHandler.md#downloadhandler) + * [FocusHandler](FocusHandler.md#focushandler-interface) + * [JavascriptDialogHandler](JavascriptDialogHandler.md#javascriptdialoghandler-interface) + * [KeyboardHandler](KeyboardHandler.md#keyboardhandler-interface) + * [LifespanHandler](LifespanHandler.md#lifespanhandler-interface) + * [LoadHandler](LoadHandler.md#loadhandler-interface) + * [RenderHandler](RenderHandler.md#renderhandler-interface) + * [RequestHandler](RequestHandler.md#requesthandler-interface) + * [ResourceHandler](ResourceHandler.md#resourcehandler-interface) + * [V8ContextHandler](V8ContextHandler.md#v8contexthandler-interface) ### Other interfaces - * [CookieVisitor](CookieVisitor.md) interface - * [StringVisitor](StringVisitor.md) interface - * [WebRequestClient](WebRequestClient.md) interface + * [CookieVisitor](CookieVisitor.md#cookievisitor-interface) interface + * [StringVisitor](StringVisitor.md#stringvisitor-interface) interface + * [WebRequestClient](WebRequestClient.md#webrequestclient-interface) interface diff --git a/tools/apidocs.py b/tools/apidocs.py index 0ba3aa5e1..badb067ec 100644 --- a/tools/apidocs.py +++ b/tools/apidocs.py @@ -46,18 +46,44 @@ def update_api_index_file(api_links): def update_readme_file(api_links): - """Update root/README.md with API reference links.""" + """Update root/README.md with API categories and index links. + API categories are copied from API-categories.md which is + generated manually. """ api_links = api_links.replace("](", "](api/") readme_file = os.path.join(ROOT_DIR, "README.md") with open(readme_file, "rb") as fo: - current_contents = fo.read().decode("utf-8") - contents = current_contents - contents = re.sub((r"### API reference\s+" - r"(\s*\*[ ]\[[^\r\n\[\]]+\]\([^\r\n()]+\)\s+)*"), - ("### API reference\r\n\r\n{api_links}" + readme_contents = fo.read().decode("utf-8") + contents = readme_contents + + # Update API categories + categories_file = os.path.join(API_DIR, "API-categories.md") + with open(categories_file, "rb") as fo: + categories_contents = fo.read().decode("utf-8") + match = re.search(r"# API categories\s+(###[\s\S]+)", + categories_contents) + assert match and match.group(1), "Failed to parse API categories" + categories_contents = match.group(1) + categories_contents = categories_contents.replace("###", "####") + re_find = r"### API categories[\s\S]+### API index" + assert re.search(re_find, readme_contents), ("API categories not found" + " in README") + contents = re.sub(re_find, + ("### API categories\r\n\r\n{categories_contents}" + "\r\n### API index" + .format(categories_contents=categories_contents)), + contents) + + # Update API index + re_find = (r"### API index\s+" + r"(\s*\*[ ]\[[^\r\n\[\]]+\]\([^\r\n()]+\)\s+)*") + assert re.search(re_find, readme_contents), ("API index not found" + " in README") + contents = re.sub(re_find, + ("### API index\r\n\r\n{api_links}" .format(api_links=api_links)), contents) - if contents == current_contents: + + if contents == readme_contents: print("No changes: /%s" % (os.path.basename(readme_file))) return with open(readme_file, "wb") as fo: From 94f2c05eb99b0dcefe68cdad2b2d428e8a61232d Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 12 Mar 2017 14:46:56 +0100 Subject: [PATCH 010/398] Fix API links in README (#326) --- README.md | 70 ++++++++++++++++++++++++------------------------ tools/apidocs.py | 1 + 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 3b391bf7c..feb5768c5 100644 --- a/README.md +++ b/README.md @@ -134,57 +134,57 @@ directly. #### Modules - * [cefpython](cefpython.md#cefpython) module + * [cefpython](api/cefpython.md#cefpython) module #### Settings - * [ApplicationSettings](ApplicationSettings.md#application-settings) dictionary - * [BrowserSettings](BrowserSettings.md#browser-settings) dictionary - * [CommandLineSwitches](CommandLineSwitches.md#command-line-switches) dictionary + * [ApplicationSettings](api/ApplicationSettings.md#application-settings) dictionary + * [BrowserSettings](api/BrowserSettings.md#browser-settings) dictionary + * [CommandLineSwitches](api/CommandLineSwitches.md#command-line-switches) dictionary #### Classes and objects - * [Browser](Browser.md#browser-object) object - * [Callback](Callback.md#callback-object) object - * [Cookie](Cookie.md#cookie-class) class - * [CookieManager](CookieManager.md#cookiemanager-class) class - * [DpiAware](DpiAware.md#dpiaware-class) class (Win) - * [DragData](DragData.md#dragdata-object) object - * [Frame](Frame.md#frame-object) object - * [Image](Image.md#image-object) object - * [JavascriptBindings](JavascriptBindings.md#javascriptbindings-class) class - * [JavascriptCallback](JavascriptCallback.md#javascriptcallback-object) object - * [PaintBuffer](PaintBuffer.md#paintbuffer-object) object - * [Request](Request.md#request-class) class - * [Response](Response.md#response-object) object - * [WebPluginInfo](WebPluginInfo.md#webplugininfo-object) object - * [WebRequest](WebRequest.md#webrequest-class) class - * [WindowInfo](WindowInfo.md#windowinfo-class) class - * [WindowUtils](WindowUtils.md#windowutils-class) class + * [Browser](api/Browser.md#browser-object) object + * [Callback](api/Callback.md#callback-object) object + * [Cookie](api/Cookie.md#cookie-class) class + * [CookieManager](api/CookieManager.md#cookiemanager-class) class + * [DpiAware](api/DpiAware.md#dpiaware-class) class (Win) + * [DragData](api/DragData.md#dragdata-object) object + * [Frame](api/Frame.md#frame-object) object + * [Image](api/Image.md#image-object) object + * [JavascriptBindings](api/JavascriptBindings.md#javascriptbindings-class) class + * [JavascriptCallback](api/JavascriptCallback.md#javascriptcallback-object) object + * [PaintBuffer](api/PaintBuffer.md#paintbuffer-object) object + * [Request](api/Request.md#request-class) class + * [Response](api/Response.md#response-object) object + * [WebPluginInfo](api/WebPluginInfo.md#webplugininfo-object) object + * [WebRequest](api/WebRequest.md#webrequest-class) class + * [WindowInfo](api/WindowInfo.md#windowinfo-class) class + * [WindowUtils](api/WindowUtils.md#windowutils-class) class #### Handlers (interfaces) - * [DisplayHandler](DisplayHandler.md#displayhandler-interface) - * [DownloadHandler](DownloadHandler.md#downloadhandler) - * [FocusHandler](FocusHandler.md#focushandler-interface) - * [JavascriptDialogHandler](JavascriptDialogHandler.md#javascriptdialoghandler-interface) - * [KeyboardHandler](KeyboardHandler.md#keyboardhandler-interface) - * [LifespanHandler](LifespanHandler.md#lifespanhandler-interface) - * [LoadHandler](LoadHandler.md#loadhandler-interface) - * [RenderHandler](RenderHandler.md#renderhandler-interface) - * [RequestHandler](RequestHandler.md#requesthandler-interface) - * [ResourceHandler](ResourceHandler.md#resourcehandler-interface) - * [V8ContextHandler](V8ContextHandler.md#v8contexthandler-interface) + * [DisplayHandler](api/DisplayHandler.md#displayhandler-interface) + * [DownloadHandler](api/DownloadHandler.md#downloadhandler) + * [FocusHandler](api/FocusHandler.md#focushandler-interface) + * [JavascriptDialogHandler](api/JavascriptDialogHandler.md#javascriptdialoghandler-interface) + * [KeyboardHandler](api/KeyboardHandler.md#keyboardhandler-interface) + * [LifespanHandler](api/LifespanHandler.md#lifespanhandler-interface) + * [LoadHandler](api/LoadHandler.md#loadhandler-interface) + * [RenderHandler](api/RenderHandler.md#renderhandler-interface) + * [RequestHandler](api/RequestHandler.md#requesthandler-interface) + * [ResourceHandler](api/ResourceHandler.md#resourcehandler-interface) + * [V8ContextHandler](api/V8ContextHandler.md#v8contexthandler-interface) #### Other interfaces - * [CookieVisitor](CookieVisitor.md#cookievisitor-interface) interface - * [StringVisitor](StringVisitor.md#stringvisitor-interface) interface - * [WebRequestClient](WebRequestClient.md#webrequestclient-interface) interface + * [CookieVisitor](api/CookieVisitor.md#cookievisitor-interface) interface + * [StringVisitor](api/StringVisitor.md#stringvisitor-interface) interface + * [WebRequestClient](api/WebRequestClient.md#webrequestclient-interface) interface ### API index diff --git a/tools/apidocs.py b/tools/apidocs.py index badb067ec..8bd8da318 100644 --- a/tools/apidocs.py +++ b/tools/apidocs.py @@ -64,6 +64,7 @@ def update_readme_file(api_links): assert match and match.group(1), "Failed to parse API categories" categories_contents = match.group(1) categories_contents = categories_contents.replace("###", "####") + categories_contents = categories_contents.replace("](", "](api/") re_find = r"### API categories[\s\S]+### API index" assert re.search(re_find, readme_contents), ("API categories not found" " in README") From 3681c59aebcda67f5460c000be12dcf4d3bf4db2 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 13 Mar 2017 10:41:49 +0100 Subject: [PATCH 011/398] Update to Chromium v56 on Linux (#276)... Update build instructions for Linux. Requirements changed, install-build-deps command changed, there is one error during build and a solution for that problem was added. Update patches. Minor fixes to automate.py. --- docs/Build-instructions.md | 33 ++++++++++++++++++++++++++------- patches/issue125.patch | 4 ++-- patches/issue231.patch | 8 ++++---- patches/issue251.patch | 36 ++++++++++++++++++------------------ tools/automate.py | 37 +++++++++++++++++++++++++++++++------ 5 files changed, 81 insertions(+), 37 deletions(-) diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index 821adab6e..832e0f968 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -12,6 +12,7 @@ Table of contents: * [Build using prebuilt CEF binaries and libraries](#build-using-prebuilt-cef-binaries-and-libraries) * [Build using CEF binaries from Spotify Automated Builds](#build-using-cef-binaries-from-spotify-automated-builds) * [Build upstream CEF from sources](#build-upstream-cef-from-sources) + * [Possible errors](#possible-errors) * [Build CEF manually](#build-cef-manually) * [CEF Automated Builds (Spotify and Adobe)](#cef-automated-builds-spotify-and-adobe) * [Notes](#notes) @@ -191,8 +192,9 @@ requirements common for all platforms. * Official binaries are built on Ubuntu 14.04 (cmake 2.8.12, g++ 4.8.4) * Download [ninja](http://martine.github.io/ninja/) 1.7.1 or later and copy it to /usr/bin and chmod 755. - * Install required packages using one of the three methods below: - 1. Type command: `sudo apt-get install bison build-essential cdbs curl devscripts dpkg-dev elfutils fakeroot flex g++ git-core git-svn gperf libapache2-mod-php5 libasound2-dev libav-tools libbrlapi-dev libbz2-dev libcairo2-dev libcap-dev libcups2-dev libcurl4-gnutls-dev libdrm-dev libelf-dev libexif-dev libffi-dev libgconf2-dev libgl1-mesa-dev libglib2.0-dev libglu1-mesa-dev libgnome-keyring-dev libgtk2.0-dev libkrb5-dev libnspr4-dev libnss3-dev libpam0g-dev libpci-dev libpulse-dev libsctp-dev libspeechd-dev libsqlite3-dev libssl-dev libudev-dev libwww-perl libxslt1-dev libxss-dev libxt-dev libxtst-dev mesa-common-dev openbox patch perl php5-cgi pkg-config python python-cherrypy3 python-crypto python-dev python-psutil python-numpy python-opencv python-openssl python-yaml rpm ruby subversion ttf-dejavu-core ttf-indic-fonts ttf-kochi-gothic ttf-kochi-mincho fonts-thai-tlwg wdiff zip` + * Install/upgrade required packages using one of the three methods below + (these packages should be upgraded each time you update to newer CEF): + 1. Type command: `sudo apt-get install bison build-essential cdbs curl devscripts dpkg-dev elfutils fakeroot flex g++ git-core git-svn gperf libapache2-mod-php5 libasound2-dev libav-tools libbrlapi-dev libbz2-dev libcairo2-dev libcap-dev libcups2-dev libcurl4-gnutls-dev libdrm-dev libelf-dev libexif-dev libffi-dev libgconf2-dev libgl1-mesa-dev libglib2.0-dev libglu1-mesa-dev libgnome-keyring-dev libgtk2.0-dev libkrb5-dev libnspr4-dev libnss3-dev libpam0g-dev libpci-dev libpulse-dev libsctp-dev libspeechd-dev libsqlite3-dev libssl-dev libudev-dev libwww-perl libxslt1-dev libxss-dev libxt-dev libxtst-dev mesa-common-dev openbox patch perl php5-cgi pkg-config python python-cherrypy3 python-crypto python-dev python-psutil python-numpy python-opencv python-openssl python-yaml rpm ruby subversion ttf-dejavu-core ttf-indic-fonts ttf-kochi-gothic ttf-kochi-mincho fonts-thai-tlwg wdiff wget zip` 2. See the list of packages on the [cef/AutomatedBuildSetup.md](https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md#markdown-header-linux-configuration) wiki page. @@ -298,13 +300,16 @@ a custom CEF branch then use the --cef-branch flag, but note that this is only for advanced users as this will require updating cefpython's C++/Cython code. -If building on Linux and there are errors, see the -"MISSING PACKAGES (Linux)" note futher down. - You should be fine by running automate.py with the default options, but if you need to customize the build then use the --help flag to see more options. +Remember to always upgrade packages listed in Requirements section each +time you update to newer CEF. + +On Linux if there are errors about missing packages or others, +then see solutions in the [Possible errors](#possible-errors) section. + The commands below will build CEF from sources with custom CEF Python patches applied and then build the CEF Python package (xx.x is version number): @@ -323,6 +328,15 @@ module, make installer package, install the package and run unit tests and examples. See the notes for commands for creating package installer and/or wheel package for distribution. +### Possible errors + +__Debug_GN_arm/ configuration error (Linux)__: Even though building +on Linux for Linux, Chromium still runs ARM configuration files. If +there is an error showing that pkg-config fails with GTK 3 library +then see solution in the third post in this topic on CEF Forum: +[Debug_GN_arm error when building on Linux, *not* arm] +(https://magpcss.org/ceforum/viewtopic.php?f=6&t=14976). + __MISSING PACKAGES (Linux)__: After the chromium sources are downloaded, it will try to build cef projects and if it fails due to missing packages make sure you've installed all the required packages listed in the @@ -334,7 +348,7 @@ graphical installer pops up don't install it - deny EULA. ``` cd build/chromium/src/build/ chmod 755 install-build-deps.sh -sudo ./install-build-deps.sh --no-lib32 --no-arm --no-chromeos-fonts --no-nacl +sudo ./install-build-deps.sh --no-chromeos-fonts --no-nacl ``` After dependencies are satisifed re-run automate.py. @@ -442,7 +456,12 @@ cd chromium/src/cef/ git diff --no-prefix --relative > issue251.patch ``` -Apply a patch in current directory: +Apply a patch in current directory and ignore git index: +``` +patch -p0 < issue251.patch +``` + +Apply a patch in current directory and do not ignore git index: ``` cd chromium/src/cef/ git apply -v -p0 issue251.patch diff --git a/patches/issue125.patch b/patches/issue125.patch index 0f3323672..1cde324ae 100644 --- a/patches/issue125.patch +++ b/patches/issue125.patch @@ -1,8 +1,8 @@ diff --git http_cache_transaction.cc http_cache_transaction.cc -index 45f3db6aed3c..373b1e3c5b45 100644 +index 717522eb6951..e92ba22a9ca9 100644 --- http_cache_transaction.cc +++ http_cache_transaction.cc -@@ -2552,7 +2552,8 @@ int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) { +@@ -2554,7 +2554,8 @@ int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) { // blocking page is shown. An alternative would be to reverse-map the cert // status to a net error and replay the net error. if ((response_.headers->HasHeaderValue("cache-control", "no-store")) || diff --git a/patches/issue231.patch b/patches/issue231.patch index 80175a2fb..c265759bc 100644 --- a/patches/issue231.patch +++ b/patches/issue231.patch @@ -83,10 +83,10 @@ index 6a759309..ad620d7f 100644 + return PathService::Override(pref_key, file_path); +} diff --git libcef_dll/libcef_dll.cc libcef_dll/libcef_dll.cc -index cc1aafe5..bde00c09 100644 +index 1f037f92..b0274ad8 100644 --- libcef_dll/libcef_dll.cc +++ libcef_dll/libcef_dll.cc -@@ -930,6 +930,23 @@ CEF_EXPORT int cef_get_path(cef_path_key_t key, cef_string_t* path) { +@@ -927,6 +927,23 @@ CEF_EXPORT int cef_get_path(cef_path_key_t key, cef_string_t* path) { return _retval; } @@ -111,10 +111,10 @@ index cc1aafe5..bde00c09 100644 // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING diff --git libcef_dll/wrapper/libcef_dll_wrapper.cc libcef_dll/wrapper/libcef_dll_wrapper.cc -index 08ddbd83..42e7dca1 100644 +index c4a1b559..a0b7765b 100644 --- libcef_dll/wrapper/libcef_dll_wrapper.cc +++ libcef_dll/wrapper/libcef_dll_wrapper.cc -@@ -851,6 +851,23 @@ CEF_GLOBAL bool CefGetPath(PathKey key, CefString& path) { +@@ -848,6 +848,23 @@ CEF_GLOBAL bool CefGetPath(PathKey key, CefString& path) { return _retval?true:false; } diff --git a/patches/issue251.patch b/patches/issue251.patch index 0c2ddc7dc..e1e9bc4ea 100644 --- a/patches/issue251.patch +++ b/patches/issue251.patch @@ -1,5 +1,5 @@ diff --git include/capi/cef_drag_data_capi.h include/capi/cef_drag_data_capi.h -index 5f86225..1684de2 100644 +index 2e84df8f..5b17e212 100644 --- include/capi/cef_drag_data_capi.h +++ include/capi/cef_drag_data_capi.h @@ -39,6 +39,7 @@ @@ -34,7 +34,7 @@ index 5f86225..1684de2 100644 diff --git include/cef_drag_data.h include/cef_drag_data.h -index 8f8094b..8b4602b 100644 +index 8f8094b4..8b4602b1 100644 --- include/cef_drag_data.h +++ include/cef_drag_data.h @@ -39,6 +39,7 @@ @@ -70,21 +70,21 @@ index 8f8094b..8b4602b 100644 }; #endif // CEF_INCLUDE_CEF_DRAG_DATA_H_ -diff --git libcef/browser/osr/web_contents_view_osr.cc libcef/browser/osr/web_contents_view_osr.cc -index c39a29c..8b3c30a 100644 ---- libcef/browser/osr/web_contents_view_osr.cc -+++ libcef/browser/osr/web_contents_view_osr.cc -@@ -6,6 +6,7 @@ - #include "libcef/browser/osr/web_contents_view_osr.h" +diff --git libcef/browser/osr/browser_platform_delegate_osr.cc libcef/browser/osr/browser_platform_delegate_osr.cc +index 2cf44f6d..2e6b8c32 100644 +--- libcef/browser/osr/browser_platform_delegate_osr.cc ++++ libcef/browser/osr/browser_platform_delegate_osr.cc +@@ -7,6 +7,7 @@ + #include #include "libcef/browser/browser_host_impl.h" +#include "libcef/browser/image_impl.h" #include "libcef/browser/osr/render_widget_host_view_osr.h" + #include "libcef/browser/osr/web_contents_view_osr.h" #include "libcef/common/drag_data_impl.h" - -@@ -230,7 +231,9 @@ void CefWebContentsViewOSR::StartDragging( - if (browser.get()) - handler = browser->GetClient()->GetRenderHandler(); +@@ -393,7 +394,9 @@ void CefBrowserPlatformDelegateOsr::StartDragging( + CefRefPtr handler = + browser_->GetClient()->GetRenderHandler(); if (handler.get()) { - CefRefPtr drag_data(new CefDragDataImpl(drop_data)); + CefRefPtr cef_image(new CefImageImpl(image)); @@ -94,7 +94,7 @@ index c39a29c..8b3c30a 100644 base::MessageLoop::ScopedNestableTaskAllower allow( base::MessageLoop::current()); diff --git libcef/common/drag_data_impl.cc libcef/common/drag_data_impl.cc -index 6b632ab..2f12b92 100644 +index 6b632ab2..2f12b928 100644 --- libcef/common/drag_data_impl.cc +++ libcef/common/drag_data_impl.cc @@ -20,6 +20,13 @@ CefDragDataImpl::CefDragDataImpl(const content::DropData& data) @@ -142,7 +142,7 @@ index 6b632ab..2f12b92 100644 + else return false; +} diff --git libcef/common/drag_data_impl.h libcef/common/drag_data_impl.h -index 64f29ed..9870726 100644 +index 64f29ed3..98707262 100644 --- libcef/common/drag_data_impl.h +++ libcef/common/drag_data_impl.h @@ -7,6 +7,7 @@ @@ -181,7 +181,7 @@ index 64f29ed..9870726 100644 // True if this object is read-only. bool read_only_; diff --git libcef_dll/cpptoc/drag_data_cpptoc.cc libcef_dll/cpptoc/drag_data_cpptoc.cc -index c36069e..ddb5729 100644 +index ffad1755..70a6c5c2 100644 --- libcef_dll/cpptoc/drag_data_cpptoc.cc +++ libcef_dll/cpptoc/drag_data_cpptoc.cc @@ -11,6 +11,7 @@ @@ -256,7 +256,7 @@ index c36069e..ddb5729 100644 template<> CefRefPtr CefCppToC Date: Mon, 13 Mar 2017 20:51:47 +0100 Subject: [PATCH 012/398] Update to Chromium v56 on Linux Part 2 (#276)... Everything builds and runs fine now. tools/build.py works fine on Linux (#299). --- docs/Build-instructions.md | 2 +- src/compile_time_constants.pxi | 4 ++-- src/subprocess/Makefile | 8 +++----- src/version/cef_version_linux.h | 20 +++++++++---------- tools/build.py | 35 +++++++++++++++++++-------------- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index 832e0f968..1b0cc0e2e 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -348,7 +348,7 @@ graphical installer pops up don't install it - deny EULA. ``` cd build/chromium/src/build/ chmod 755 install-build-deps.sh -sudo ./install-build-deps.sh --no-chromeos-fonts --no-nacl +sudo ./install-build-deps.sh --no-chromeos-fonts --no-nacl --no-arm ``` After dependencies are satisifed re-run automate.py. diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 632aab59c..35f850027 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Windows" -DEF PY_MAJOR_VERSION = 3 +DEF UNAME_SYSNAME = "Linux" +DEF PY_MAJOR_VERSION = 2 diff --git a/src/subprocess/Makefile b/src/subprocess/Makefile index 1ed3b15c1..580cb784f 100644 --- a/src/subprocess/Makefile +++ b/src/subprocess/Makefile @@ -27,13 +27,11 @@ INC = -I./../ -I./../common/ -I/usr/include/python2.7 \ -I/usr/lib/glib-2.0/include ifeq ($(UNAME_S), Linux) - LIB_DIRS = -L./../../build/cef_linux64/bin \ - -L./../../build/cef_linux32/bin \ - -L./../../build/cef_linux64/lib \ - -L./../../build/cef_linux32/lib + LIB_DIRS = -L$(CEF_BIN) \ + -L$(CEF_LIB) else ifeq ($(UNAME_S), Darwin) LIB_DIRS = -F$(CEF_BIN) \ - -L$(CEF_LIB) + -L$(CEF_LIB) endif ifeq ($(UNAME_S), Linux) diff --git a/src/version/cef_version_linux.h b/src/version/cef_version_linux.h index 39126c00a..5e718118b 100644 --- a/src/version/cef_version_linux.h +++ b/src/version/cef_version_linux.h @@ -35,16 +35,16 @@ #ifndef CEF_INCLUDE_CEF_VERSION_H_ #define CEF_INCLUDE_CEF_VERSION_H_ -#define CEF_VERSION "3.2883.1554.gf984155" +#define CEF_VERSION "3.2924.1575.g97389a9" #define CEF_VERSION_MAJOR 3 -#define CEF_COMMIT_NUMBER 1554 -#define CEF_COMMIT_HASH "f984155b3f0ad80833742d17cdab2065f3ec75ce" +#define CEF_COMMIT_NUMBER 1575 +#define CEF_COMMIT_HASH "97389a92ee2309ded830338d6afd61ba109d31d8" #define COPYRIGHT_YEAR 2017 -#define CHROME_VERSION_MAJOR 55 +#define CHROME_VERSION_MAJOR 56 #define CHROME_VERSION_MINOR 0 -#define CHROME_VERSION_BUILD 2883 -#define CHROME_VERSION_PATCH 87 +#define CHROME_VERSION_BUILD 2924 +#define CHROME_VERSION_PATCH 76 #define DO_MAKE_STRING(p) #p #define MAKE_STRING(p) DO_MAKE_STRING(p) @@ -63,13 +63,13 @@ extern "C" { // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected. -#define CEF_API_HASH_UNIVERSAL "87b7eefcb86c87b28f86bfd7919f7d7a6cffc0d8" +#define CEF_API_HASH_UNIVERSAL "66de193ba22e1d92a99bb29d60f3107709aeefda" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "00823905486d7b7222da5654fe35d2d15f65543a" +#define CEF_API_HASH_PLATFORM "8055740cd08db66cefe838a826dc90806fadfb33" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "f0180f006643782254250f34e858b98110a40e6e" +#define CEF_API_HASH_PLATFORM "12d8ab423df369b68d37c3667123a1812bc0d345" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "14b19454a4231fa10a77b8955954dc95f073af6b" +#define CEF_API_HASH_PLATFORM "86ab23c0d7dafbdff7f66764cf8dac5ec1712af4" #endif // Returns CEF version information for the libcef library. The |entry| diff --git a/tools/build.py b/tools/build.py index 0319a2ea5..df0355b43 100644 --- a/tools/build.py +++ b/tools/build.py @@ -23,12 +23,13 @@ Options: VERSION Version number eg. 50.0 --no-run-examples Do not run examples after build, only unit tests - --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED) --fast Fast mode --clean Clean C++ projects build files on Linux/Mac --kivy Run only Kivy example """ +# --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED) + # How to debug on Linux: # 1. Install "python-dbg" package # 2. Install "python-wxgtk2.8-dbg" package @@ -236,7 +237,7 @@ def setup_environ(): pass # Mac env variables for makefiles - if MAC: + if MAC or LINUX: os.environ["CEF_BIN"] = os.path.join(CEF_BINARIES_LIBRARIES, "bin") os.environ["CEF_LIB"] = os.path.join(CEF_BINARIES_LIBRARIES, "lib") @@ -306,29 +307,33 @@ def fix_cefpython_api_header_file(): print("[build.py] cefpython API header file was not yet generated") return + # Original contents with open(CEFPYTHON_API_HFILE, "rb") as fo: contents = fo.read().decode("utf-8") - already_fixed = False - pragma = "#pragma warning(disable:4190)" - if pragma in contents: - already_fixed = True - print("[build.py] cefpython API header file is already fixed") - else: - if not MAC: + # Pragma fix on Windows + if WINDOWS: + already_fixed = False + pragma = "#pragma warning(disable:4190)" + if pragma in contents: + already_fixed = True + print("[build.py] cefpython API header file is already fixed") + else: contents = ("%s\n\n" % pragma) + contents + with open(CEFPYTHON_API_HFILE, "wb") as fo: + fo.write(contents.encode("utf-8")) + print("[build.py] Save {filename}" + .format(filename=CEFPYTHON_API_HFILE)) - if not already_fixed: - with open(CEFPYTHON_API_HFILE, "wb") as fo: - fo.write(contents.encode("utf-8")) - print("[build.py] Save {filename}" - .format(filename=CEFPYTHON_API_HFILE)) - + # Make a copy with a "_fixed" postfix if os.path.exists(CEFPYTHON_API_HFILE_FIXED): with open(CEFPYTHON_API_HFILE_FIXED, "rb") as fo: contents_fixed = fo.read().decode("utf-8") else: contents_fixed = "" + + # Resave fixed copy only if contents changed. Other scripts + # depend on "modified time" of the "_fixed" file. if contents != contents_fixed: print("[build.py] Save cefpython_fixed.h") with open(CEFPYTHON_API_HFILE_FIXED, "wb") as fo: From 0c8a3b02b95b6bac4420752e07e256049736634d Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 14 Mar 2017 10:22:06 +0100 Subject: [PATCH 013/398] Reduce packages size with build_distrib.py tool (#321, #262)... Test wheel package installation in build_distrib.py. Add --no-rebuild flag to build_distrib.py. --- src/compile_time_constants.pxi | 4 +- tools/automate.py | 8 +- tools/build_distrib.py | 145 ++++++++++++++++++++++++++------- tools/common.py | 13 ++- 4 files changed, 132 insertions(+), 38 deletions(-) diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..632aab59c 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 2 +DEF UNAME_SYSNAME = "Windows" +DEF PY_MAJOR_VERSION = 3 diff --git a/tools/automate.py b/tools/automate.py index 97d7968c4..7fed6b255 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -348,7 +348,7 @@ def build_cef_projects(): "build_cefclient") cefclient_exe = os.path.join(build_cefclient_dir, "tests", "cefclient", Options.build_type, - "cefclient" + EXECUTABLE_EXT) + "cefclient" + APP_EXT) # Check whether already built already_built = False @@ -691,7 +691,7 @@ def create_prebuilt_binaries(): src, "build_cefclient", "tests", "cefclient", Options.build_type, - "cefclient" + EXECUTABLE_EXT) + "cefclient" + APP_EXT) if LINUX and os.path.exists(cefclient): # On Windows resources/*.html files are embedded inside exe cefclient_files = os.path.join( @@ -706,14 +706,14 @@ def create_prebuilt_binaries(): src, "build_cefclient", "tests", "cefsimple", Options.build_type, - "cefsimple" + EXECUTABLE_EXT) + "cefsimple" + APP_EXT) # ceftests ceftests = os.path.join( src, "build_cefclient", "tests", "ceftests", Options.build_type, - "ceftests" + EXECUTABLE_EXT) + "ceftests" + APP_EXT) if LINUX and os.path.exists(ceftests): # On Windows resources/*.html files are embedded inside exe ceftests_files = os.path.join( diff --git a/tools/build_distrib.py b/tools/build_distrib.py index 8368b63cd..c40035c73 100644 --- a/tools/build_distrib.py +++ b/tools/build_distrib.py @@ -6,16 +6,18 @@ Build distribution packages for all architectures and all supported python versions. -TODO: test_wheel_package_installation() TODO: Linux/Mac support. Currently runs only on Windows. Usage: - build_distrib.py VERSION [--no-run-examples] + build_distrib.py VERSION [--no-run-examples] [--no-rebuild] Options: VERSION Version number eg. 50.0 --no-run-examples Do not run examples while building cefpython modules. Only unit tests will be run in such case. + --no-rebuild Do not rebuild cefpython modules. For internal use + so that changes to packaging can be quickly tested. + This script does the following: 1. Expects that all supported python versions are installed @@ -27,17 +29,24 @@ 2. Expects that all python compilers for supported python versions are installed. See docs/Build-instructions.md > Requirements. 3. Expects cef_binary*/ directories from Spotify Automated Builds - to be in the build/ directory + to be in the build/ directory. It does not rebuild cefclient + nor libcef_dll_wrapper libraries in these directories. If you + would like to rebuild everything from scratch then delete subdirs + manually (build_cefclient/, build_wrapper*/). 4. Install and/or upgrade tools/requirements.txt and uninstall cefpython3 packages for all python versions 5. Run automate.py --prebuilt-cef using both Python 32-bit and Python 64-bit 6. Pack the prebuilt biaries using zip on Win/Mac and .tar.gz on Linux and move to build/distrib/ -7. Build cefpython modules for all supported Python versions on both +7. Reduce packages size (Issue #321). After packing prebuilt binaries, + reduce its size so that packages will use the reduced prebuilt binaries. +8. Build cefpython modules for all supported Python versions on both 32-bit and 64-bit -8. Make setup installers and pack them to zip (Win/Mac) or .tar.gz (Linux) -9. Make wheel packages -10. Move setup and wheel packages to the build/distrib/ directory +9. Make setup installers and pack them to zip (Win/Mac) or .tar.gz (Linux) +10. Make wheel packages +11. Move setup and wheel packages to the build/distrib/ directory +12. Test installation of wheel packages +13. Show summary """ from common import * @@ -52,6 +61,7 @@ # Command line args VERSION = "" NO_RUN_EXAMPLES = False +NO_REBUILD = False # Pythons SUPPORTED_PYTHON_VERSIONS = [(2, 7), (3, 4), (3, 5), (3, 6)] @@ -81,50 +91,67 @@ def main(): os.makedirs(DISTRIB_DIR) if pythons_32bit: run_automate_prebuilt_cef(pythons_32bit[0]) - pack_prebuilt_cef(pythons_32bit[0]["arch"]) + pack_prebuilt_cef("32bit") + if LINUX: + reduce_package_size_issue_262("32bit") + reduce_package_size_issue_321("32bit") if pythons_64bit is not None: run_automate_prebuilt_cef(pythons_64bit[0]) - pack_prebuilt_cef(pythons_64bit[0]["arch"]) - build_cefpython_modules(pythons_32bit + pythons_64bit) + pack_prebuilt_cef("64bit") + if LINUX: + reduce_package_size_issue_262("64bit") + reduce_package_size_issue_321("64bit") + if not NO_REBUILD: + build_cefpython_modules(pythons_32bit + pythons_64bit) if pythons_32bit: make_packages(pythons_32bit[0], "32bit") if pythons_64bit: make_packages(pythons_64bit[0], "64bit") - test_wheel_package_installation() + test_wheel_package_installation(pythons_32bit + pythons_64bit) show_summary(pythons_32bit, pythons_64bit) def command_line_args(): - global VERSION, NO_RUN_EXAMPLES + global VERSION, NO_RUN_EXAMPLES, NO_REBUILD version = get_version_from_command_line_args(__file__) - if not version: + if not version or "--help" in sys.argv: print(__doc__) sys.exit(1) VERSION = version if "--no-run-examples" in sys.argv: NO_RUN_EXAMPLES = True + sys.argv.remove("--no-run-examples") + if "--no-rebuild" in sys.argv: + NO_REBUILD = True + sys.argv.remove("--no-rebuild") + args = sys.argv[1:] + for arg in args: + if arg == version: + continue + print("[build_distrib.py] Invalid argument: {arg}".format(arg=arg)) + sys.exit(1) def clean_build_directories(): print("[build_distrib.py] Clean build directories") - # Distrib dir + # Delete distrib dir if os.path.exists(DISTRIB_DIR): print("[build_distrib.py] Delete directory: {distrib_dir}/" .format(distrib_dir=os.path.basename(DISTRIB_DIR))) shutil.rmtree(DISTRIB_DIR) - # build_cefpython/ dir - if os.path.exists(BUILD_CEFPYTHON): - print("[build_distirb.py] Delete directory: {dir}/" - .format(dir=os.path.basename(BUILD_CEFPYTHON))) - shutil.rmtree(BUILD_CEFPYTHON) - - # cefpython_binary_*/ dirs - delete_cefpython_binary_dir("32bit") - delete_cefpython_binary_dir("64bit") - - # cef binaries and libraries dirs + if not NO_REBUILD: + # Delete build_cefpython/ dir + if os.path.exists(BUILD_CEFPYTHON): + print("[build_distirb.py] Delete directory: {dir}/" + .format(dir=os.path.basename(BUILD_CEFPYTHON))) + shutil.rmtree(BUILD_CEFPYTHON) + # Delete cefpython_binary_*/ dirs + delete_cefpython_binary_dir("32bit") + delete_cefpython_binary_dir("64bit") + + # Delete cef binaries and libraries dirs delete_cef_binaries_libraries_dir("32bit") delete_cef_binaries_libraries_dir("64bit") @@ -344,6 +371,47 @@ def zip_directory(path, base_path, archive): os.chdir(original_dir) +def reduce_package_size_issue_262(arch): + """Linux only: libcef.so is huge (500 MB) in Chrome v54+. Issue #262.""" + print("[build_distrib.py] Reduce package size for {arch} (Issue #262)" + .format(arch=arch)) + prebuilt_basename = get_cef_binaries_libraries_basename( + get_postfix2_for_arch(arch)) + bin_dir = os.path.join(prebuilt_basename, "bin") + + # Run `strip` command on `libcef.so` + libcef_so = os.path.join(bin_dir, "libcef.so") + print("[build_distrib.py] Strip {libcef_so}" + .format(libcef_so=os.path.basename(libcef_so))) + command = "strip {libcef_so}".format(libcef_so=libcef_so) + pcode = subprocess.call(command) + if pcode != 0: + print("[build_distrib.py] ") + sys.exit(1) + + +def reduce_package_size_issue_321(arch): + """PyPI has file size limit and must reduce package size. Issue #321.""" + print("[build_distrib.py] Reduce package size for {arch} (Issue #321)" + .format(arch=arch)) + prebuilt_basename = get_cef_binaries_libraries_basename( + get_postfix2_for_arch(arch)) + bin_dir = os.path.join(prebuilt_basename, "bin") + + # Delete sample applications to reduce package size + sample_apps = ["cefclient", "cefsimple", "ceftests"] + for sample_app in sample_apps: + sample_app = os.path.join(bin_dir, sample_app + APP_EXT) + # Not on all platforms sample apps may be available + if os.path.exists(sample_app): + print("[build_distrib.py] Delete {sample_app}" + .format(sample_app=os.path.basename(sample_app))) + if os.path.isdir(sample_app): + shutil.rmtree(sample_app) + else: + os.remove(sample_app) + + def build_cefpython_modules(pythons): for python in pythons: print("[build_distrib.py] Build cefpython module for {python_name}" @@ -417,9 +485,28 @@ def make_packages(python, arch): shutil.rmtree(setup_dir) -def test_wheel_package_installation(): - # PYPI_POSTFIX2_ARCH - pass # TODO +def test_wheel_package_installation(pythons): + uninstall_cefpython3_packages(pythons) + for python in pythons: + print("[build_distrib.py] Test wheel package installation for" + " {python_name}".format(python_name=python["name"])) + platform_tag = get_pypi_postfix2_for_arch(python["arch"]) + whl_pattern = (r"*-{platform_tag}.whl" + .format(platform_tag=platform_tag)) + wheels = glob.glob(os.path.join(DISTRIB_DIR, whl_pattern)) + assert len(wheels) == 1, ("No wheels found in distrib dir for %s" + % python["arch"]) + # Install wheel + command = ("\"{python}\" -m pip install {wheel}" + .format(python=python["executable"], + wheel=os.path.basename(wheels[0]))) + if python["executable"].startswith("/usr/"): + command = "sudo {command}".format(command=command) + pcode = subprocess.call(command, cwd=DISTRIB_DIR) + if pcode != 0: + print("[build_distrib.py] Wheel package installation failed for" + " {python_name}".format(python_name=python["name"])) + sys.exit(1) def show_summary(pythons_32bit, pythons_64bit): @@ -436,7 +523,7 @@ def show_summary(pythons_32bit, pythons_64bit): count=len(files))) for file_ in files: print(" {filename}".format(filename=os.path.basename(file_))) - print("[build_distrib.py] Done. Distribution packages created.") + print("[build_distrib.py] Everything OK. Distribution packages created.") if __name__ == "__main__": diff --git a/tools/common.py b/tools/common.py index 360494172..3c868c2af 100644 --- a/tools/common.py +++ b/tools/common.py @@ -51,7 +51,7 @@ Darwin={"32bit": "mac32", "64bit": "mac64"}, ) PYPI_POSTFIX2_ARCH = dict( - Windows={"32bit": "win32", "64bit": "win-amd64"}, + Windows={"32bit": "win32", "64bit": "win_amd64"}, Linux={"32bit": "i686", "64bit": "x86_64"}, Darwin={"64bit": "x86_64"}, ) @@ -71,12 +71,15 @@ MODULE_NAME = MODULE_NAME_TEMPLATE.format(pyversion=PYVERSION, ext=MODULE_EXT) MODULE_NAME_NOEXT = MODULE_NAME_TEMPLATE_NOEXT.format(pyversion=PYVERSION) -# Executable extension +# App and Executable extensions if WINDOWS: + APP_EXT = ".exe" EXECUTABLE_EXT = ".exe" elif MAC: - EXECUTABLE_EXT = ".app" + APP_EXT = ".app" # cefclient, cefsimple, ceftests + EXECUTABLE_EXT = "" # subprocess else: + APP_EXT = "" EXECUTABLE_EXT = "" # Library extension @@ -193,6 +196,10 @@ def get_postfix2_for_arch(arch): return OS_POSTFIX2_ARCH[SYSTEM][arch] +def get_pypi_postfix2_for_arch(arch): + return PYPI_POSTFIX2_ARCH[SYSTEM][arch] + + def _detect_cef_binaries_libraries_dir(): """Detect cef binary directory created by automate.py eg. build/cef55_3.2883.1553.g80bd606_win32/ From d0844b1c5a44a5356f8477467511175095e3d675 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 14 Mar 2017 20:56:25 +0100 Subject: [PATCH 014/398] Fix and enhance tools for Linux (#327)... Add 32-bit support to automate.py. Fix build.py and build_distrib.py on Linux. Run unit tests in build_distrib.py after wheel package was installed. --- docs/Build-instructions.md | 9 +- src/compile_time_constants.pxi | 2 +- tools/automate.py | 34 +++-- tools/build.py | 64 ++++------ tools/build_distrib.py | 222 ++++++++++++++++++++------------- tools/common.py | 53 ++++++-- tools/run_examples.py | 5 +- 7 files changed, 237 insertions(+), 152 deletions(-) diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index 1b0cc0e2e..f52645a84 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -204,6 +204,8 @@ requirements common for all platforms. [cef/BuildingOnDebian7.md](https://bitbucket.org/chromiumembedded/cef/wiki/BuildingOnDebian7.md) and [cef/#1575](https://bitbucket.org/chromiumembedded/cef/issues/1575), and [cef/#1697](https://bitbucket.org/chromiumembedded/cef/issues/1697) +* If building CEF from sources, 32-bit on 64-bit machine: + * Follow the configuration [here](https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md#markdown-header-linux-configuration) * To perform a 32-bit Linux build on a 64-bit Linux system see Linux configuration in upstream cef/AutomatedBuildSetup.md. See also [cef/#1804](https://bitbucket.org/chromiumembedded/cef/issues/1804). @@ -311,14 +313,15 @@ On Linux if there are errors about missing packages or others, then see solutions in the [Possible errors](#possible-errors) section. The commands below will build CEF from sources with custom CEF Python -patches applied and then build the CEF Python package (xx.x is version -number): +patches applied and then build the CEF Python package. "xx.x" is version +number and "ninja-jobs 4" means to run 4 parallel jobs for compiling, +increase it if you have more CPU cores and want things to build faster: ``` git clone https://github.com/cztomczak/cefpython.git cd cefpython/ mkdir build/ cd build/ -python ../tools/automate.py --build-cef --ninja-jobs 6 +python ../tools/automate.py --build-cef --ninja-jobs 4 python ../tools/build.py xx.x ``` diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 632aab59c..47dedaecd 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Windows" +DEF UNAME_SYSNAME = "Linux" DEF PY_MAJOR_VERSION = 3 diff --git a/tools/automate.py b/tools/automate.py index 7fed6b255..716f9c579 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -6,7 +6,10 @@ Prepares CEF binaries and libraries for work with the build.py tool. Option 1 is to build CEF from sources with the CEF Python patches applied -using the --build-cef flag. +using the --build-cef flag. Building CEF from sources is supported only +on 64-bit systems. 32-bit is also built on 64-bit using cross-compiling. +Note that building CEF from sources was last tested with v56 on Linux +and with v50 on Windows, so if there are issues report them on the Forum. Option 2 is to use CEF binaries from Spotify Automated Builds using the --prebuilt-cef flag. In such case check the cefpython/src/version/ @@ -22,6 +25,7 @@ Usage: automate.py (--prebuilt-cef | --build-cef) + [--x86 X86] [--fast-build FAST_BUILD] [--force-chromium-update FORCE_CHROMIUM_UPDATE] [--no-cef-update NO_CEF_UPDATE] @@ -37,6 +41,7 @@ binaries for Linux are built on Ubuntu. --build-cef Whether to build CEF from sources with the cefpython patches applied. + --x86 Build 32-bit CEF on 64-bit system --fast-build Fast build with is_official_build=False --force-chromium-update Force Chromium update (gclient sync etc). --no-cef-update Do not update CEF sources (by default both cef/ @@ -82,6 +87,7 @@ class Options(object): # From command-line prebuilt_cef = False build_cef = False + x86 = False fast_build = False force_chromium_update = False no_cef_update = False @@ -223,17 +229,20 @@ def prebuilt_cef(): # eg. tag 'upstream-cef47'. # Find cef_binary directory in the build directory + postfix2 = CEF_POSTFIX2 + if Options.x86: + postfix2 = get_cef_postfix2_for_arch("32bit") if Options.cef_version: cef_binary = os.path.join(Options.build_dir, "cef_binary_{cef_version}_{os}{sep}" .format(cef_version=Options.cef_version, - os=CEF_POSTFIX2, + os=postfix2, sep=os.sep)) else: cef_binary = os.path.join(Options.build_dir, "cef_binary_3.{cef_branch}.*_{os}{sep}" .format(cef_branch=Options.cef_branch, - os=CEF_POSTFIX2, + os=postfix2, sep=os.sep)) dirs = glob.glob(cef_binary) if len(dirs) == 1: @@ -336,8 +345,11 @@ def build_cef_projects(): cef_binary = symbols.replace("_debug_symbols", "") assert "symbols" not in os.path.basename(cef_binary) else: + postfix2 = CEF_POSTFIX2 + if Options.x86: + postfix2 = get_cef_postfix2_for_arch("32bit") files = glob.glob(os.path.join(Options.binary_distrib, - "cef_binary_*_"+OS_POSTFIX2)) + "cef_binary_*_"+postfix2)) assert len(files) == 1, "Error finding binary distrib" cef_binary = files[0] assert os.path.exists(cef_binary) @@ -832,7 +844,6 @@ def getenv(): # GN configuration env["CEF_USE_GN"] = "1" # Issue #73 patch applied here with "use_allocator=none" - # TODO: 32-bit GN defines: host_arch=x86_64 target_arch=ia32 env["GN_DEFINES"] = "use_sysroot=true use_allocator=none symbol_level=1" # env["GN_DEFINES"] += " use_gtk3=false" # To perform an official build set GYP_DEFINES=buildtype=Official. @@ -845,7 +856,8 @@ def getenv(): # upstream Linux configuration on AutomatedBuildSetup wiki page, # so setting it here as well. env["GYP_DEFINES"] = "disable_nacl=1 use_sysroot=1 use_allocator=none" - # Note: 32-bit GYP defines: host_arch=x86_64 target_arch=ia32 + if Options.x86: + env["GYP_DEFINES"] += " host_arch=x86_64 target_arch=ia32" if Options.release_build and not Options.fast_build: env["GYP_DEFINES"] += " buildtype=Official" @@ -872,8 +884,7 @@ def run_command(command, working_dir, env=None): args = command if not env: env = getenv() - return subprocess.check_call(args, cwd=working_dir, env=env, - shell=(platform.system() == "Windows")) + return subprocess.check_call(args, cwd=working_dir, env=env, shell=True) def run_git(command_line, working_dir): @@ -893,7 +904,7 @@ def run_automate_git(): ninja -v -j2 -Cout\Release cefclient """ args = [] - if ARCH64: + if ARCH64 and not Options.x86: args.append("--x64-build") args.append("--download-dir=" + Options.cef_build_dir) args.append("--branch=" + Options.cef_branch) @@ -972,10 +983,13 @@ def get_prebuilt_name(header_file=""): version = get_version_from_file(header_file) else: version = get_cefpython_version() + postfix2 = OS_POSTFIX2 + if Options.x86: + postfix2 = get_os_postfix2_for_arch("32bit") name = "cef%s_%s_%s" % ( version["CHROME_VERSION_MAJOR"], version["CEF_VERSION"], - OS_POSTFIX2, + postfix2 ) return name diff --git a/tools/build.py b/tools/build.py index df0355b43..375d64973 100644 --- a/tools/build.py +++ b/tools/build.py @@ -313,10 +313,8 @@ def fix_cefpython_api_header_file(): # Pragma fix on Windows if WINDOWS: - already_fixed = False pragma = "#pragma warning(disable:4190)" if pragma in contents: - already_fixed = True print("[build.py] cefpython API header file is already fixed") else: contents = ("%s\n\n" % pragma) + contents @@ -701,18 +699,11 @@ def build_cefpython_module(): os.chdir(BUILD_CEFPYTHON) + command = ("\"{python}\" {tools_dir}/cython_setup.py build_ext" + .format(python=sys.executable, tools_dir=TOOLS_DIR)) if FAST_FLAG: - ret = subprocess.call("\"{python}\" {tools_dir}/cython_setup.py" - " build_ext --fast" - .format(python=sys.executable, - tools_dir=TOOLS_DIR), - shell=True) - else: - ret = subprocess.call("\"{python}\" {tools_dir}/cython_setup.py" - " build_ext" - .format(python=sys.executable, - tools_dir=TOOLS_DIR), - shell=True) + command += " --fast" + ret = subprocess.call(command, shell=True) # if DEBUG_FLAG: # shutil.rmtree("./../binaries_%s/cython_debug/" % BITS, @@ -741,7 +732,8 @@ def build_cefpython_module(): args.append(os.path.join(TOOLS_DIR, os.path.basename(__file__))) assert __file__ in sys.argv[0] args.extend(sys.argv[1:]) - ret = subprocess.call(" ".join(args), shell=True) + command = " ".join(args) + ret = subprocess.call(command, shell=True) sys.exit(ret) else: print("[build.py] ERROR: failed to build the cefpython module") @@ -806,10 +798,11 @@ def install_and_run(): # Make setup installer print("[build.py] Make setup installer") make_tool = os.path.join(TOOLS_DIR, "make_installer.py") - ret = os.system("\"{python}\" {make_tool} --version {version}" - .format(python=sys.executable, - make_tool=make_tool, - version=VERSION)) + command = ("\"{python}\" {make_tool} --version {version}" + .format(python=sys.executable, + make_tool=make_tool, + version=VERSION)) + ret = os.system(command) if ret != 0: print("[build.py] ERROR while making installer package") sys.exit(ret) @@ -817,8 +810,10 @@ def install_and_run(): # Install print("[build.py] Install the cefpython package") os.chdir(setup_installer_dir) - ret = os.system("{sudo} \"{python}\" setup.py install" - .format(sudo=get_sudo(), python=sys.executable)) + command = ("\"{python}\" setup.py install" + .format(python=sys.executable)) + command = sudo_command(command, python=sys.executable) + ret = os.system(command) if ret != 0: print("[build.py] ERROR while installing package") sys.exit(ret) @@ -830,8 +825,10 @@ def install_and_run(): # Run unittests print("[build.py] Run unittests") test_runner = os.path.join(UNITTESTS_DIR, "_test_runner.py") - ret = os.system("\"{python}\" {test_runner}" - .format(python=sys.executable, test_runner=test_runner)) + command = ("\"{python}\" {test_runner}" + .format(python=sys.executable, + test_runner=test_runner)) + ret = os.system(command) if ret != 0: print("[build.py] ERROR while running unit tests") sys.exit(ret) @@ -842,10 +839,11 @@ def install_and_run(): os.chdir(EXAMPLES_DIR) kivy_flag = "--kivy" if KIVY_FLAG else "" run_examples = os.path.join(TOOLS_DIR, "run_examples.py") - ret = os.system("\"{python}\" {run_examples} {kivy_flag}" - .format(python=sys.executable, - run_examples=run_examples, - kivy_flag=kivy_flag)) + command = ("\"{python}\" {run_examples} {kivy_flag}" + .format(python=sys.executable, + run_examples=run_examples, + kivy_flag=kivy_flag)) + ret = os.system(command) if ret != 0: print("[build.py] ERROR while running examples") sys.exit(1) @@ -853,15 +851,6 @@ def install_and_run(): print("[build.py] Everything OK") -def get_sudo(): - # System Python requires sudo when installing package - if sys.executable.startswith("/usr/"): - sudo = "sudo" - else: - sudo = "" - return sudo - - def delete_directory_reliably(adir): assert len(adir) > 2 assert os.path.isdir(adir) @@ -873,8 +862,9 @@ def delete_directory_reliably(adir): # On Linux sudo might be required to delete directory, as this # might be a setup installer directory with package installed # using sudo and in such case files were created with sudo. - os.system("{sudo} rm -rf {dir}" - .format(sudo=get_sudo(), dir=adir)) + command = "rm -rf {dir}".format(dir=adir) + command = sudo_command(command, python=sys.executable) + os.system(command) if __name__ == "__main__": diff --git a/tools/build_distrib.py b/tools/build_distrib.py index c40035c73..93f3cf340 100644 --- a/tools/build_distrib.py +++ b/tools/build_distrib.py @@ -6,7 +6,7 @@ Build distribution packages for all architectures and all supported python versions. -TODO: Linux/Mac support. Currently runs only on Windows. +TODO: Mac support. Currently runs only on Windows/Linux. Usage: build_distrib.py VERSION [--no-run-examples] [--no-rebuild] @@ -14,7 +14,8 @@ Options: VERSION Version number eg. 50.0 --no-run-examples Do not run examples while building cefpython modules. - Only unit tests will be run in such case. + Examples require interaction, closing window before + proceeding. Only unit tests will be run in such case. --no-rebuild Do not rebuild cefpython modules. For internal use so that changes to packaging can be quickly tested. @@ -33,6 +34,8 @@ nor libcef_dll_wrapper libraries in these directories. If you would like to rebuild everything from scratch then delete subdirs manually (build_cefclient/, build_wrapper*/). + When building CEF from sources copy build/chromium/src/cef/binary_distrib + /cef_binary_*/ to the build/ directory. 4. Install and/or upgrade tools/requirements.txt and uninstall cefpython3 packages for all python versions 5. Run automate.py --prebuilt-cef using both Python 32-bit and Python 64-bit @@ -45,7 +48,8 @@ 9. Make setup installers and pack them to zip (Win/Mac) or .tar.gz (Linux) 10. Make wheel packages 11. Move setup and wheel packages to the build/distrib/ directory -12. Test installation of wheel packages +12. Test wheel packages installation and run unit tests using the + installed wheel package. 13. Show summary """ @@ -56,6 +60,7 @@ import re import shutil import subprocess +import tarfile import zipfile # Command line args @@ -63,27 +68,45 @@ NO_RUN_EXAMPLES = False NO_REBUILD = False -# Pythons +# Python versions SUPPORTED_PYTHON_VERSIONS = [(2, 7), (3, 4), (3, 5), (3, 6)] -PYTHON_SEARCH_PATHS_WINDOWS = [ - "C:\\Python*\\", - "%LocalAppData%\\Programs\\Python\\Python*\\", - "C:\\Program Files\\Python*\\", - "C:\\Program Files (x86)\\Python*\\", -] + +# Python search paths. It will use first Python found for specific version. +# Supports replacement of one environment variable in path eg.: %ENV_KEY%. +PYTHON_SEARCH_PATHS = dict( + WINDOWS=[ + "C:\\Python*\\", + "%LOCALAPPDATA%\\Programs\\Python\\Python*\\", + "C:\\Program Files\\Python*\\", + "C:\\Program Files (x86)\\Python*\\", + ], + LINUX=[ + "%PYENV_ROOT%/versions/*/bin", + ], +) def main(): command_line_args() - print("[build_distrib.py] Supported python versions:") - pp = pprint.PrettyPrinter(indent=4) - pp.pprint(SUPPORTED_PYTHON_VERSIONS) + supported = list() + for version in SUPPORTED_PYTHON_VERSIONS: + supported.append("{major}.{minor}".format(major=version[0], + minor=version[1])) + print("[build_distrib.py] Supported python versions: {supported}" + .format(supported=" / ".join(supported))) clean_build_directories() - pythons_32bit = list() - pythons_64bit = list() if WINDOWS: pythons_32bit = search_for_pythons("32bit") pythons_64bit = search_for_pythons("64bit") + elif LINUX: + pythons_32bit = search_for_pythons("32bit") if ARCH32 else list() + pythons_64bit = search_for_pythons("64bit") if ARCH64 else list() + elif MAC: + pythons_32bit = list() + pythons_64bit = search_for_pythons("64bit") + else: + print("ERROR: Unsupported OS") + sys.exit(1) check_pythons(pythons_32bit, pythons_64bit) install_upgrade_requirements(pythons_32bit + pythons_64bit) uninstall_cefpython3_packages(pythons_32bit + pythons_64bit) @@ -107,7 +130,7 @@ def main(): make_packages(pythons_32bit[0], "32bit") if pythons_64bit: make_packages(pythons_64bit[0], "64bit") - test_wheel_package_installation(pythons_32bit + pythons_64bit) + test_wheel_packages(pythons_32bit + pythons_64bit) show_summary(pythons_32bit, pythons_64bit) @@ -158,7 +181,7 @@ def clean_build_directories(): def delete_cefpython_binary_dir(arch): cefpython_binary = get_cefpython_binary_basename( - postfix2=get_postfix2_for_arch(arch)) + postfix2=get_os_postfix2_for_arch(arch)) assert cefpython_binary, cefpython_binary cefpython_binary = os.path.join(BUILD_DIR, cefpython_binary) if os.path.exists(cefpython_binary): @@ -169,7 +192,7 @@ def delete_cefpython_binary_dir(arch): def delete_cef_binaries_libraries_dir(arch): cef_binlib = get_cef_binaries_libraries_basename( - postfix2=get_postfix2_for_arch(arch)) + postfix2=get_os_postfix2_for_arch(arch)) assert cef_binlib, cef_binlib cef_binlib = os.path.join(BUILD_DIR, cef_binlib) if os.path.exists(cef_binlib): @@ -178,45 +201,31 @@ def delete_cef_binaries_libraries_dir(arch): shutil.rmtree(cef_binlib) -def check_pythons(pythons_32bit, pythons_64bit): - pp = pprint.PrettyPrinter(indent=4) - if pythons_32bit: - print("[build_distrib.py] Pythons 32-bit found:") - pp.pprint(pythons_32bit) - if WINDOWS and len(pythons_32bit) != len(SUPPORTED_PYTHON_VERSIONS): - print("[build_distrib.py] ERROR: Couldn't find all supported" - " python 32-bit installations. Found: {found}." - .format(found=len(pythons_32bit))) - sys.exit(1) - if pythons_64bit: - print("[build_distrib.py] Pythons 64-bit found:") - pp.pprint(pythons_64bit) - if len(pythons_64bit) != len(SUPPORTED_PYTHON_VERSIONS): - print("[build_distrib.py] ERROR: Couldn't find all supported" - " python 64-bit installations. Found: {found}." - .format(found=len(pythons_64bit))) - sys.exit(1) - - def search_for_pythons(search_arch): - print("[build_distrib.py] Search for Pythons...") - if WINDOWS: - return search_for_pythons_windows(search_arch) - raise Exception("Only Windows platform supported currently") - - -def search_for_pythons_windows(search_arch): """Returns pythons ordered by version from lowest to highest.""" pythons_found = list() - for pattern in PYTHON_SEARCH_PATHS_WINDOWS: - pattern = pattern.replace("%LocalAppData%", - os.environ["LOCALAPPDATA"]) + for pattern in PYTHON_SEARCH_PATHS[SYSTEM]: + # Replace env variable in path + match = re.search(r"%(\w+)%", pattern) + if match: + env_key = match.group(1) + if env_key in os.environ: + pattern = pattern.replace(match.group(0), os.environ[env_key]) + else: + print("ERROR: Env variable not found: {env_key}" + .format(env_key=env_key)) + sys.exit(1) results = glob.glob(pattern) for path in results: if os.path.isdir(path): - python = os.path.join(path, "python.exe") + python = os.path.join(path, + "python{ext}".format(ext=EXECUTABLE_EXT)) version_code = ("import sys;" "print(str(sys.version_info[:3]));") + if not os.path.isfile(python): + print("ERROR: Python executable not found: {executable}" + .format(executable=python)) + sys.exit(1) version_str = subprocess.check_output([python, "-c", version_code]) version_str = version_str.strip() @@ -247,26 +256,42 @@ def search_for_pythons_windows(search_arch): supported_python = None for python in pythons_found: if python["version2"] == version_tuple: - # Always go through the whole loop and save the last - # python executable for the given version (eg. 2.7), - # so that the latest version is used (eg. 2.7.12). - # This is assuming that glob.glob sorted directories. supported_python = python + break if supported_python: ret_pythons.append(supported_python) return ret_pythons +def check_pythons(pythons_32bit, pythons_64bit): + pp = pprint.PrettyPrinter(indent=4) + if pythons_32bit: + print("[build_distrib.py] Pythons 32-bit found:") + pp.pprint(pythons_32bit) + if WINDOWS and len(pythons_32bit) != len(SUPPORTED_PYTHON_VERSIONS): + print("[build_distrib.py] ERROR: Couldn't find all supported" + " python 32-bit installations. Found: {found}." + .format(found=len(pythons_32bit))) + sys.exit(1) + if pythons_64bit: + print("[build_distrib.py] Pythons 64-bit found:") + pp.pprint(pythons_64bit) + if len(pythons_64bit) != len(SUPPORTED_PYTHON_VERSIONS): + print("[build_distrib.py] ERROR: Couldn't find all supported" + " python 64-bit installations. Found: {found}." + .format(found=len(pythons_64bit))) + sys.exit(1) + + def install_upgrade_requirements(pythons): for python in pythons: print("[build_distrib.py] pip install/upgrade requirements.txt" " for: {name}".format(name=python["name"])) # Upgrade pip - command = "\"{python}\" -m pip install --upgrade pip" - command = command.format(python=python["executable"]) - if python["executable"].startswith("/usr/"): - command = "sudo {command}".format(command=command) + command = ("\"{python}\" -m pip install --upgrade pip" + .format(python=python["executable"])) + command = sudo_command(command, python=python["executable"]) pcode = subprocess.call(command, shell=True) if pcode != 0: print("[build_distrib.py] ERROR while upgrading pip") @@ -274,11 +299,10 @@ def install_upgrade_requirements(pythons): # Install/upgrade requirements.txt requirements = os.path.join(TOOLS_DIR, "requirements.txt") - command = "\"{python}\" -m pip install --upgrade -r {requirements}" - command = command.format(python=python["executable"], - requirements=requirements) - if python["executable"].startswith("/usr/"): - command = "sudo {command}".format(command=command) + command = ("\"{python}\" -m pip install --upgrade -r {requirements}" + .format(python=python["executable"], + requirements=requirements)) + command = sudo_command(command, python=python["executable"]) pcode = subprocess.call(command, shell=True) if pcode != 0: print("[build_distrib.py] ERROR while running pip install/upgrade") @@ -287,14 +311,14 @@ def install_upgrade_requirements(pythons): def uninstall_cefpython3_packages(pythons): for python in pythons: - print("[build_distrib.py] pip uninstall cefpython3 package" + print("[build_distrib.py] Uninstall cefpython3 package" " for: {name}".format(name=python["name"])) # Check if package is installed command = ("\"{python}\" -m pip show cefpython3" .format(python=python["executable"])) try: - output = subprocess.check_output(command) + output = subprocess.check_output(command, shell=True) except subprocess.CalledProcessError, exc: # pip show returns error code when package is not installed output = exc.output @@ -307,8 +331,7 @@ def uninstall_cefpython3_packages(pythons): # otherwise error code is returned. command = ("\"{python}\" -m pip uninstall -y cefpython3" .format(python=python["executable"])) - if python["executable"].startswith("/usr/"): - command = "sudo {command}".format(command=command) + command = sudo_command(command, python=python["executable"]) pcode = subprocess.call(command, shell=True) if pcode != 0: print("[build_distrib.py] ERROR while uninstall cefpython3" @@ -322,7 +345,7 @@ def run_automate_prebuilt_cef(python): automate = os.path.join(TOOLS_DIR, "automate.py") command = ("\"{python}\" {automate} --prebuilt-cef" .format(python=python["executable"], automate=automate)) - code = subprocess.call(command) + code = subprocess.call(command, shell=True) if code != 0: print("[build_distrib.py] ERROR while running automate.py") sys.exit(1) @@ -330,7 +353,7 @@ def run_automate_prebuilt_cef(python): def pack_prebuilt_cef(arch): prebuilt_basename = get_cef_binaries_libraries_basename( - get_postfix2_for_arch(arch)) + get_os_postfix2_for_arch(arch)) print("[build_distrib.py] Pack directory: {dir}/ ..." .format(dir=prebuilt_basename)) prebuilt_dir = os.path.join(BUILD_DIR, prebuilt_basename) @@ -351,8 +374,8 @@ def pack_directory(path, base_path): if WINDOWS or MAC: zip_directory(path, base_path=base_path, archive=archive) else: - # LINUX - raise Exception("pack_directory(): Linux not yet supported") # TODO + with tarfile.open(archive, "w:gz") as tar: + tar.add(path, arcname=os.path.basename(path)) assert os.path.isfile(archive), archive return archive @@ -376,7 +399,7 @@ def reduce_package_size_issue_262(arch): print("[build_distrib.py] Reduce package size for {arch} (Issue #262)" .format(arch=arch)) prebuilt_basename = get_cef_binaries_libraries_basename( - get_postfix2_for_arch(arch)) + get_os_postfix2_for_arch(arch)) bin_dir = os.path.join(prebuilt_basename, "bin") # Run `strip` command on `libcef.so` @@ -384,10 +407,8 @@ def reduce_package_size_issue_262(arch): print("[build_distrib.py] Strip {libcef_so}" .format(libcef_so=os.path.basename(libcef_so))) command = "strip {libcef_so}".format(libcef_so=libcef_so) - pcode = subprocess.call(command) - if pcode != 0: - print("[build_distrib.py] ") - sys.exit(1) + pcode = subprocess.call(command, shell=True) + assert pcode, "strip command failed" def reduce_package_size_issue_321(arch): @@ -395,13 +416,13 @@ def reduce_package_size_issue_321(arch): print("[build_distrib.py] Reduce package size for {arch} (Issue #321)" .format(arch=arch)) prebuilt_basename = get_cef_binaries_libraries_basename( - get_postfix2_for_arch(arch)) + get_os_postfix2_for_arch(arch)) bin_dir = os.path.join(prebuilt_basename, "bin") # Delete sample applications to reduce package size sample_apps = ["cefclient", "cefsimple", "ceftests"] - for sample_app in sample_apps: - sample_app = os.path.join(bin_dir, sample_app + APP_EXT) + for sample_app_name in sample_apps: + sample_app = os.path.join(bin_dir, sample_app_name + APP_EXT) # Not on all platforms sample apps may be available if os.path.exists(sample_app): print("[build_distrib.py] Delete {sample_app}" @@ -410,6 +431,20 @@ def reduce_package_size_issue_321(arch): shutil.rmtree(sample_app) else: os.remove(sample_app) + # Also delete subdirs eg. cefclient_files/, ceftests_files/ + files_subdir = os.path.join(bin_dir, sample_app_name + "_files") + if os.path.isdir(files_subdir): + print("[build_distrib.py] Delete directory: {dir}/" + .format(dir=os.path.basename(files_subdir))) + shutil.rmtree(files_subdir) + + # Strip symbols from cefpython .so modules to reduce size + modules = glob.glob(os.path.join(CEFPYTHON_BINARY, "*.so")) + for module in modules: + print("[build_distrib.py] strip {module}" + .format(module=os.path.basename(module))) + command = "strip {module}".format(module=module) + assert os.system(command) == 0, "strip command failed" def build_cefpython_modules(pythons): @@ -419,7 +454,8 @@ def build_cefpython_modules(pythons): flags = "" if NO_RUN_EXAMPLES: flags += " --no-run-examples" - command = ("\"{python}\" {build_py} {version} {flags}" + # On Linux/Mac Makefiles are used and must pass --clean flag + command = ("\"{python}\" {build_py} {version} --clean {flags}" .format(python=python["executable"], build_py=os.path.join(TOOLS_DIR, "build.py"), version=VERSION, @@ -446,7 +482,7 @@ def make_packages(python, arch): .format(python=python["executable"], make_installer_py=make_installer_py, version=VERSION)) - pcode = subprocess.call(installer_command, cwd=BUILD_DIR) + pcode = subprocess.call(installer_command, cwd=BUILD_DIR, shell=True) if pcode != 0: print("[build_distrib.py] ERROR: failed to make setup package for" " {arch}".format(arch=arch)) @@ -456,7 +492,7 @@ def make_packages(python, arch): print("[build_distrib.py] Pack setup package for {arch}..." .format(arch=arch)) setup_basename = get_setup_installer_basename( - VERSION, get_postfix2_for_arch(arch)) + VERSION, get_os_postfix2_for_arch(arch)) setup_dir = os.path.join(BUILD_DIR, setup_basename) archive = pack_directory(setup_dir, BUILD_DIR) shutil.move(archive, DISTRIB_DIR) @@ -468,7 +504,7 @@ def make_packages(python, arch): wheel_command = ("\"{python}\" setup.py {wheel_args}" .format(python=python["executable"], wheel_args=wheel_args)) - pcode = subprocess.call(wheel_command, cwd=setup_dir) + pcode = subprocess.call(wheel_command, cwd=setup_dir, shell=True) if pcode != 0: print("[build_distrib.py] ERROR: failed to make wheel package for" " {arch}".format(arch=arch)) @@ -485,10 +521,11 @@ def make_packages(python, arch): shutil.rmtree(setup_dir) -def test_wheel_package_installation(pythons): +def test_wheel_packages(pythons): + """Test wheel packages installation and run unit tests.""" uninstall_cefpython3_packages(pythons) for python in pythons: - print("[build_distrib.py] Test wheel package installation for" + print("[build_distrib.py] Test wheel package (install, unittests) for" " {python_name}".format(python_name=python["name"])) platform_tag = get_pypi_postfix2_for_arch(python["arch"]) whl_pattern = (r"*-{platform_tag}.whl" @@ -496,18 +533,29 @@ def test_wheel_package_installation(pythons): wheels = glob.glob(os.path.join(DISTRIB_DIR, whl_pattern)) assert len(wheels) == 1, ("No wheels found in distrib dir for %s" % python["arch"]) + # Install wheel command = ("\"{python}\" -m pip install {wheel}" .format(python=python["executable"], wheel=os.path.basename(wheels[0]))) - if python["executable"].startswith("/usr/"): - command = "sudo {command}".format(command=command) - pcode = subprocess.call(command, cwd=DISTRIB_DIR) + command = sudo_command(command, python=python["executable"]) + pcode = subprocess.call(command, cwd=DISTRIB_DIR, shell=True) if pcode != 0: print("[build_distrib.py] Wheel package installation failed for" " {python_name}".format(python_name=python["name"])) sys.exit(1) + # Run unittests using the installed wheel package + command = ("\"{python}\" {unittests}" + .format(python=python["executable"], + unittests=os.path.join(UNITTESTS_DIR, + "main_test.py"))) + pcode = subprocess.call(command, cwd=DISTRIB_DIR, shell=True) + if pcode != 0: + print("[build_distrib.py] ERROR: Unit tests failed for" + " {python_name}".format(python_name=python["name"])) + sys.exit(1) + def show_summary(pythons_32bit, pythons_64bit): print("[build_distrib.py] SUMMARY:") diff --git a/tools/common.py b/tools/common.py index 3c868c2af..b84476d81 100644 --- a/tools/common.py +++ b/tools/common.py @@ -21,6 +21,7 @@ if ARCH64: assert platform.architecture()[0] == "64bit" ARCH_STR = platform.architecture()[0] + # OS_POSTFIX is for directories/files names in cefpython sources # OS_POSTFIX2 is for platform name in cefpython binaries # CEF_POSTFIX2 is for platform name in upstream CEF binaries @@ -29,6 +30,7 @@ "mac" if platform.system() == "Darwin" else "unknown") OS_POSTFIX2 = "unknown" CEF_POSTFIX2 = "unknown" # Upstream CEF binaries postfix + if OS_POSTFIX == "win": OS_POSTFIX2 = "win32" if ARCH32 else "win64" CEF_POSTFIX2 = "windows32" if ARCH32 else "windows64" @@ -40,20 +42,25 @@ CEF_POSTFIX2 = "linux32" if ARCH32 else "linux64" # Platforms -SYSTEM = platform.system() -WINDOWS = SYSTEM if SYSTEM == "Windows" else False -LINUX = SYSTEM if SYSTEM == "Linux" else False -MAC = SYSTEM if SYSTEM == "Darwin" else False +SYSTEM = platform.system().upper() +WINDOWS = SYSTEM if SYSTEM == "WINDOWS" else False +LINUX = SYSTEM if SYSTEM == "LINUX" else False +MAC = SYSTEM if SYSTEM == "DARWIN" else False OS_POSTFIX2_ARCH = dict( - Windows={"32bit": "win32", "64bit": "win64"}, - Linux={"32bit": "linux32", "64bit": "linux64"}, - Darwin={"32bit": "mac32", "64bit": "mac64"}, + WINDOWS={"32bit": "win32", "64bit": "win64"}, + LINUX={"32bit": "linux32", "64bit": "linux64"}, + DARWIN={"32bit": "mac32", "64bit": "mac64"}, +) +CEF_POSTFIX2_ARCH = dict( + WINDOWS={"32bit": "windows32", "64bit": "windows64"}, + LINUX={"32bit": "linux32", "64bit": "linux64"}, + DARWIN={"64bit": "macosx64"}, ) PYPI_POSTFIX2_ARCH = dict( - Windows={"32bit": "win32", "64bit": "win_amd64"}, - Linux={"32bit": "i686", "64bit": "x86_64"}, - Darwin={"64bit": "x86_64"}, + WINDOWS={"32bit": "win32", "64bit": "win_amd64"}, + LINUX={"32bit": "manylinux1_i686", "64bit": "manylinux1_x86_64"}, + DARWIN={"64bit": "x86_64"}, ) # Python version eg. 27 @@ -192,14 +199,26 @@ os.environ["LOCALAPPDATA"]) -def get_postfix2_for_arch(arch): +def get_os_postfix2_for_arch(arch): return OS_POSTFIX2_ARCH[SYSTEM][arch] +def get_cef_postfix2_for_arch(arch): + return CEF_POSTFIX2_ARCH[SYSTEM][arch] + + def get_pypi_postfix2_for_arch(arch): return PYPI_POSTFIX2_ARCH[SYSTEM][arch] +def sudo_command(command, python): + """Prepends command with sudo when installing python packages + requires sudo.""" + if python.startswith("/usr/"): + command = "sudo " + command + return command + + def _detect_cef_binaries_libraries_dir(): """Detect cef binary directory created by automate.py eg. build/cef55_3.2883.1553.g80bd606_win32/ @@ -273,7 +292,17 @@ def _detect_distrib_dir(): # Will only be set when called from scripts that had version # number arg passed on command line: build.py, build_distrib.py, # make_installer.py, etc. - dirname = "distrib_{version}".format(version=version) + if LINUX: + # On Linux buildig 32bit and 64bit separately, so don't + # delete eg. 64bit distrib when building 32bit distrib. + # Keep them in different directories. + dirname = ("distrib_{version}_{postfix2}" + .format(version=version, postfix2=OS_POSTFIX2)) + else: + # On Windows both 32bit and 64bit distribs are built at + # the same time. + # On Mac only 64bit is supported. + dirname = "distrib_{version}".format(version=version) DISTRIB_DIR = os.path.join(BUILD_DIR, dirname) diff --git a/tools/run_examples.py b/tools/run_examples.py index 9f2583c65..6562af669 100644 --- a/tools/run_examples.py +++ b/tools/run_examples.py @@ -96,8 +96,9 @@ def main(): for example in examples: print("[run_examples.py] Running '{example}'..." .format(example=example)) - ret = os.system("\"{python}\" {example}" - .format(python=sys.executable, example=example)) + command = "\"{python}\" {example}".format(python=sys.executable, + example=example) + ret = os.system(command) if ret == 0: succeeded.append(example) else: From c472898bc06aa6134d1b4cf385dc6a904ec29fde Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 16 Mar 2017 22:54:17 +0100 Subject: [PATCH 015/398] Fix tools on Linux. Add 32-bit support on Linux (#327). --- README.md | 2 +- docs/Build-instructions.md | 7 ++- examples/Examples-README.md | 2 +- src/client_handler/Makefile | 2 +- src/subprocess/Makefile | 2 +- src/subprocess/Makefile-libcefpythonapp | 2 +- tools/automate.py | 25 ++++++++-- tools/build.py | 40 +++++++-------- tools/build_cpp_projects.py | 4 +- tools/build_distrib.py | 66 ++++++++++--------------- tools/common.py | 65 ++++++++++++++++++++++-- tools/installer/cefpython3.setup.py | 4 +- tools/make_installer.py | 9 +++- 13 files changed, 147 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index feb5768c5..ebf04ddad 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ scraping or as a web crawler, or other kind of internet bots. - Downloads are available on [GitHub Releases](../../releases) pages - Windows support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 (requirements: Windows 7+) -- Linux support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 +- Linux support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 (requirements: Debian 7+ / Ubuntu 12.04+) - Mac support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 (requirements: MacOS 10.9+) diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index f52645a84..910e9d5f1 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -184,10 +184,7 @@ requirements common for all platforms. ### Linux -* Install packages: `sudo apt-get install python-dev cmake g++ libgtk2.0-dev` -* If using prebuilt binaries from Spotify automated builds and want to - build cefclient/cefsimple you need to install these packages: - `sudo apt-get install libgtkglext1-dev` +* Install packages: `sudo apt-get install python-dev cmake g++ libgtk2.0-dev libgtkglext1-dev` * If building CEF from sources: * Official binaries are built on Ubuntu 14.04 (cmake 2.8.12, g++ 4.8.4) * Download [ninja](http://martine.github.io/ninja/) 1.7.1 or later @@ -209,6 +206,8 @@ requirements common for all platforms. * To perform a 32-bit Linux build on a 64-bit Linux system see Linux configuration in upstream cef/AutomatedBuildSetup.md. See also [cef/#1804](https://bitbucket.org/chromiumembedded/cef/issues/1804). +* Sometimes it is also required to install these packages (eg. chroot): + `sudo apt-get install libnss3 libnspr4 libxss1 libgconf-2-4` ### Mac diff --git a/examples/Examples-README.md b/examples/Examples-README.md index b1b4109bd..337004d79 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -21,7 +21,7 @@ maintained: and [PySide](https://wiki.qt.io/PySide) libraries (Qt 4) - [tkinter_.py](tkinter_.py): example for [Tkinter] (https://wiki.python.org/moin/TkInter). Currently broken on Mac. -- [wxpython.py](wxpython.py): example for wxPython +- [wxpython.py](wxpython.py): example for [wxPython](https://wxpython.org/) If there are any issues in examples read top comments in sources to see whether this is a known issue with available workarounds. diff --git a/src/client_handler/Makefile b/src/client_handler/Makefile index f0b9a5a4d..4465d28b5 100644 --- a/src/client_handler/Makefile +++ b/src/client_handler/Makefile @@ -30,7 +30,7 @@ OBJ = $(filter %.o, $(SRC:.cpp=.o) $(SRC:.mm=.o)) OUT = libclient_handler.a -INC = -I./../ -I./../common/ -I/usr/include/python2.7 \ +INC = -I./../ -I./../common/ -I$(PYTHON_INCLUDE) \ -I/usr/include/gtk-2.0 \ -I/usr/include/gtk-unix-print-2.0 \ -I/usr/include/glib-2.0 \ diff --git a/src/subprocess/Makefile b/src/subprocess/Makefile index 580cb784f..b6f003be4 100644 --- a/src/subprocess/Makefile +++ b/src/subprocess/Makefile @@ -5,7 +5,7 @@ # -Werror - treat warnings as errors UNAME_S = $(shell uname -s) -INC = -I./../ -I./../common/ -I/usr/include/python2.7 \ +INC = -I./../ -I./../common/ -I$(PYTHON_INCLUDE) \ -I/usr/include/gtk-2.0 \ -I/usr/include/gtk-unix-print-2.0 \ -I/usr/include/glib-2.0 \ diff --git a/src/subprocess/Makefile-libcefpythonapp b/src/subprocess/Makefile-libcefpythonapp index bd8186da7..e811a32fa 100644 --- a/src/subprocess/Makefile-libcefpythonapp +++ b/src/subprocess/Makefile-libcefpythonapp @@ -30,7 +30,7 @@ OBJ = $(filter %.o, $(SRC:.cpp=.o) $(SRC:.mm=.o)) OUT = libcefpythonapp.a -INC = -I./../ -I./../common/ -I/usr/include/python2.7 \ +INC = -I./../ -I./../common/ -I$(PYTHON_INCLUDE) \ -I/usr/include/gtk-2.0 \ -I/usr/include/gtk-unix-print-2.0 \ -I/usr/include/glib-2.0 \ diff --git a/tools/automate.py b/tools/automate.py index 716f9c579..1a12398e5 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -126,9 +126,6 @@ def main(): " version of python.") sys.exit(1) build_cef() - # Build cefclient, cefsimple, ceftests, libcef_dll_wrapper - build_cef_projects() - create_prebuilt_binaries() elif Options.prebuilt_cef: prebuilt_cef() @@ -209,6 +206,11 @@ def setup_options(docopt_args): def build_cef(): """Build CEF from sources.""" + if ARCH32: + print("[automate.py] INFO: building CEF 32-bit from sources is" + " supported only with cross-compiling on 64-bit OS.") + sys.exit(1) + # cef/ repo create_cef_directories() @@ -221,6 +223,18 @@ def build_cef(): print("[automate.py] Binary distrib created in %s" % Options.binary_distrib) + if Options.x86: + print("[automate.py] INFO: to build CEF projects and create prebuilt" + " binaries you have to use 32-bit chroot. Copy the binary" + " distrib's cef_binary_*/ directory (path displayed above) to" + " cefpython's build/ directory. Then run automate.py" + " --prebuilt-cef using 32-bit chroot.") + sys.exit(0) + else: + # Build cefclient, cefsimple, ceftests, libcef_dll_wrapper + build_cef_projects() + create_prebuilt_binaries() + def prebuilt_cef(): """Use prebuilt binaries.""" @@ -884,7 +898,10 @@ def run_command(command, working_dir, env=None): args = command if not env: env = getenv() - return subprocess.check_call(args, cwd=working_dir, env=env, shell=True) + # When passing list of args shell cannot be True on eg. Linux, read + # notes in build.py + shell=(platform.system() == "Windows") + return subprocess.check_call(args, cwd=working_dir, env=env, shell=shell) def run_git(command_line, working_dir): diff --git a/tools/build.py b/tools/build.py index 375d64973..fce02f349 100644 --- a/tools/build.py +++ b/tools/build.py @@ -30,7 +30,16 @@ # --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED) -# How to debug on Linux: +# NOTE: When passing string command to subprocess functions you must +# always use shell=True, otherwise on Linux error is thrown: +# "No such file or directory". Always pass string commands to +# subprocess functions with shell=True. If you pass a list of +# arguments instead, then on Linux a "Segmentation fault" error +# message is not shown. When passing a list of args to subprocess +# function then you can't pass shell=True on Linux. If you pass +# then it will execute args[0] and ignore others args. + +# How to debug on Linux (OLD unsupported). # 1. Install "python-dbg" package # 2. Install "python-wxgtk2.8-dbg" package # 3. Run "python compile.py debug" @@ -38,12 +47,6 @@ # 5. To display debug backtrace type "cy bt" # 6. More commands: http://docs.cython.org/src/userguide/debugging.html -# This will not show "Segmentation fault" error message: -# > subprocess.call(["python", "./wxpython.py"]) -# You need to call it with command as string and shell=True -# for this kind of error message to be shown: -# > subprocess.call("python wxpython.py", shell=True) - from common import * import sys import os @@ -210,8 +213,7 @@ def setup_environ(): if WINDOWS: if "INCLUDE" not in os.environ: os.environ["INCLUDE"] = "" - os.environ["INCLUDE"] += os.pathsep + os.path.join(get_python_path(), - "include") + os.environ["INCLUDE"] += os.pathsep + get_python_include_path() print("[build.py] environ INCLUDE: {include}" .format(include=os.environ["INCLUDE"])) @@ -223,7 +225,11 @@ def setup_environ(): .format(lib=os.environ["AdditionalLibraryDirectories"])) if LINUX or MAC: - # Used in makefiles + # Env variables for makefiles + os.environ["PYTHON_INCLUDE"] = get_python_include_path() + print("[build.py] PYTHON_INCLUDE: {python_include}" + .format(python_include=os.environ["PYTHON_INCLUDE"])) + os.environ["CEF_CCFLAGS"] = "-std=gnu++11 -DNDEBUG -Wall -Werror" if FAST_FLAG: os.environ["CEF_CCFLAGS"] += " -O0" @@ -231,16 +237,14 @@ def setup_environ(): os.environ["CEF_CCFLAGS"] += " -O3" os.environ["CEF_LINK_FLAGS"] = "" + os.environ["CEF_BIN"] = os.path.join(CEF_BINARIES_LIBRARIES, "bin") + os.environ["CEF_LIB"] = os.path.join(CEF_BINARIES_LIBRARIES, "lib") + if LINUX: # TODO: Set CEF_CCFLAGS and CEF_LINK_FLAGS according to what is # in upstream cefclient, see cef/cmake/cef_variables.cmake. pass - # Mac env variables for makefiles - if MAC or LINUX: - os.environ["CEF_BIN"] = os.path.join(CEF_BINARIES_LIBRARIES, "bin") - os.environ["CEF_LIB"] = os.path.join(CEF_BINARIES_LIBRARIES, "lib") - # Mac compiler options if MAC: os.environ["PATH"] = "/usr/local/bin:"+os.environ["PATH"] @@ -282,11 +286,6 @@ def setup_environ(): ) -def get_python_path(): - """Get Python path.""" - return os.path.dirname(sys.executable) - - def fix_cefpython_api_header_file(): """This function does two things: 1) Disable warnings in cefpython API header file and 2) Make a copy named cefpython_pyXX_fixed.h, @@ -449,6 +448,7 @@ def clean_cpp_projects_unix(): delete_files_by_pattern("{0}/*.o".format(SUBPROCESS_DIR)) delete_files_by_pattern("{0}/*.a".format(SUBPROCESS_DIR)) delete_files_by_pattern("{0}/subprocess".format(SUBPROCESS_DIR)) + delete_files_by_pattern("{0}/main_message_loop/*.o".format(SUBPROCESS_DIR)) delete_files_by_pattern("{0}/*.o".format(CPP_UTILS_DIR)) delete_files_by_pattern("{0}/*.a".format(CPP_UTILS_DIR)) diff --git a/tools/build_cpp_projects.py b/tools/build_cpp_projects.py index 8bb6bf335..f62f85c37 100644 --- a/tools/build_cpp_projects.py +++ b/tools/build_cpp_projects.py @@ -247,8 +247,6 @@ def smart_compile(compiler, macros, extra_args, sources, output_dir): # all put in the same output_directory. Otherwise distutils # will create lots of subdirs in output_directory. macros = macros_as_tuples(macros) - python_include = os.path.join(os.path.dirname(sys.executable), - "include") common_dir = os.path.join(SRC_DIR, "common") original_dir = os.getcwd() for source_file in sources: @@ -261,7 +259,7 @@ def smart_compile(compiler, macros, extra_args, sources, output_dir): # TODO include dirs for Linux/Mac include_dirs=[SRC_DIR, common_dir, - python_include], + get_python_include_path()], # TODO compiler flags for Linux/Mac extra_preargs=None, extra_postargs=extra_args) diff --git a/tools/build_distrib.py b/tools/build_distrib.py index 93f3cf340..a88f059cc 100644 --- a/tools/build_distrib.py +++ b/tools/build_distrib.py @@ -60,7 +60,7 @@ import re import shutil import subprocess -import tarfile +# import tarfile # Currently using zip on all platforms import zipfile # Command line args @@ -117,13 +117,13 @@ def main(): pack_prebuilt_cef("32bit") if LINUX: reduce_package_size_issue_262("32bit") - reduce_package_size_issue_321("32bit") - if pythons_64bit is not None: + remove_unnecessary_package_files("32bit") + if pythons_64bit: run_automate_prebuilt_cef(pythons_64bit[0]) pack_prebuilt_cef("64bit") if LINUX: reduce_package_size_issue_262("64bit") - reduce_package_size_issue_321("64bit") + remove_unnecessary_package_files("64bit") if not NO_REBUILD: build_cefpython_modules(pythons_32bit + pythons_64bit) if pythons_32bit: @@ -264,11 +264,21 @@ def search_for_pythons(search_arch): def check_pythons(pythons_32bit, pythons_64bit): + check_32bit = True + check_64bit = True + if MAC: + check_32bit = False + elif LINUX: + if pythons_64bit: + check_32bit = False + elif pythons_32bit: + check_64bit = False + pp = pprint.PrettyPrinter(indent=4) if pythons_32bit: print("[build_distrib.py] Pythons 32-bit found:") pp.pprint(pythons_32bit) - if WINDOWS and len(pythons_32bit) != len(SUPPORTED_PYTHON_VERSIONS): + if check_32bit and len(pythons_32bit) != len(SUPPORTED_PYTHON_VERSIONS): print("[build_distrib.py] ERROR: Couldn't find all supported" " python 32-bit installations. Found: {found}." .format(found=len(pythons_32bit))) @@ -276,7 +286,7 @@ def check_pythons(pythons_32bit, pythons_64bit): if pythons_64bit: print("[build_distrib.py] Pythons 64-bit found:") pp.pprint(pythons_64bit) - if len(pythons_64bit) != len(SUPPORTED_PYTHON_VERSIONS): + if check_64bit and len(pythons_64bit) != len(SUPPORTED_PYTHON_VERSIONS): print("[build_distrib.py] ERROR: Couldn't find all supported" " python 64-bit installations. Found: {found}." .format(found=len(pythons_64bit))) @@ -367,15 +377,17 @@ def pack_prebuilt_cef(arch): def pack_directory(path, base_path): if path.endswith(os.path.sep): path = path[:-1] - ext = ".zip" if WINDOWS or MAC else ".tar.gz" + # ext = ".zip" if WINDOWS or MAC else ".tar.gz" + ext = ".zip" archive = path + ext if os.path.exists(archive): os.remove(archive) if WINDOWS or MAC: zip_directory(path, base_path=base_path, archive=archive) else: - with tarfile.open(archive, "w:gz") as tar: - tar.add(path, arcname=os.path.basename(path)) + zip_directory(path, base_path=base_path, archive=archive) + # with tarfile.open(archive, "w:gz") as tar: + # tar.add(path, arcname=os.path.basename(path)) assert os.path.isfile(archive), archive return archive @@ -408,43 +420,19 @@ def reduce_package_size_issue_262(arch): .format(libcef_so=os.path.basename(libcef_so))) command = "strip {libcef_so}".format(libcef_so=libcef_so) pcode = subprocess.call(command, shell=True) - assert pcode, "strip command failed" + assert pcode == 0, "strip command failed" -def reduce_package_size_issue_321(arch): - """PyPI has file size limit and must reduce package size. Issue #321.""" +def remove_unnecessary_package_files(arch): + """Do not ship sample applications (cefclient etc) with the package. + They increase size and also are an additional unnecessary factor + when dealing with false-positives in Anti-Virus software.""" print("[build_distrib.py] Reduce package size for {arch} (Issue #321)" .format(arch=arch)) prebuilt_basename = get_cef_binaries_libraries_basename( get_os_postfix2_for_arch(arch)) bin_dir = os.path.join(prebuilt_basename, "bin") - - # Delete sample applications to reduce package size - sample_apps = ["cefclient", "cefsimple", "ceftests"] - for sample_app_name in sample_apps: - sample_app = os.path.join(bin_dir, sample_app_name + APP_EXT) - # Not on all platforms sample apps may be available - if os.path.exists(sample_app): - print("[build_distrib.py] Delete {sample_app}" - .format(sample_app=os.path.basename(sample_app))) - if os.path.isdir(sample_app): - shutil.rmtree(sample_app) - else: - os.remove(sample_app) - # Also delete subdirs eg. cefclient_files/, ceftests_files/ - files_subdir = os.path.join(bin_dir, sample_app_name + "_files") - if os.path.isdir(files_subdir): - print("[build_distrib.py] Delete directory: {dir}/" - .format(dir=os.path.basename(files_subdir))) - shutil.rmtree(files_subdir) - - # Strip symbols from cefpython .so modules to reduce size - modules = glob.glob(os.path.join(CEFPYTHON_BINARY, "*.so")) - for module in modules: - print("[build_distrib.py] strip {module}" - .format(module=os.path.basename(module))) - command = "strip {module}".format(module=module) - assert os.system(command) == 0, "strip command failed" + delete_cef_sample_apps(caller_script=__file__, bin_dir=bin_dir) def build_cefpython_modules(pythons): diff --git a/tools/common.py b/tools/common.py index b84476d81..3df0404b9 100644 --- a/tools/common.py +++ b/tools/common.py @@ -4,12 +4,16 @@ # Common stuff for tools such as automate.py, build.py, etc. -import struct -import platform -import sys -import os import glob +import os +import platform import re +import shutil +import struct +import sys + +# These sample apps will be deleted when creating setup/wheel packages +CEF_SAMPLE_APPS = ["cefclient", "cefsimple", "ceftests", "chrome-sandbox"] # Architecture and OS postfixes ARCH32 = (8 * struct.calcsize('P') == 32) @@ -198,6 +202,8 @@ VS2008_BUILD = VS2008_BUILD.replace("%LocalAppData%", os.environ["LOCALAPPDATA"]) +# ----------------------------------------------------------------------------- + def get_os_postfix2_for_arch(arch): return OS_POSTFIX2_ARCH[SYSTEM][arch] @@ -219,6 +225,57 @@ def sudo_command(command, python): return command +def get_python_path(): + """Get Python path.""" + return os.path.dirname(sys.executable) + + +def get_python_include_path(): + # 1) C:\Python27\include + # 2) ~/.pyenv/versions/2.7.13/bin/python + # ~/.pyenv/versions/2.7.13/include/python2.7 + # 3) ~/.pyenv/versions/3.4.6/include/python2.7m + # 4) /usr/include/python2.7 + base_dir = os.path.dirname(sys.executable) + try_dirs = ["{base_dir}/include", + "{base_dir}/../include/python{ver}", + "{base_dir}/../include/python{ver}*", + "/usr/include/python{ver}"] + ver_tuple = sys.version_info[:2] + ver = "{major}.{minor}".format(major=ver_tuple[0], minor=ver_tuple[1]) + for pattern in try_dirs: + pattern = pattern.format(base_dir=base_dir, ver=ver) + if WINDOWS: + pattern = pattern.replace("/", "\\") + results = glob.glob(pattern) + if len(results) == 1: + python_h = os.path.join(results[0], "Python.h") + if os.path.isfile(python_h): + return results[0] + return ".\\" if WINDOWS else "./" + + +def delete_cef_sample_apps(caller_script, bin_dir): + """Delete CEF sample apps to reduce package size.""" + for sample_app_name in CEF_SAMPLE_APPS: + sample_app = os.path.join(bin_dir, sample_app_name + APP_EXT) + # Not on all platforms sample apps may be available + if os.path.exists(sample_app): + print("[{script}] Delete {sample_app}" + .format(script=os.path.basename(caller_script), + sample_app=os.path.basename(sample_app))) + if os.path.isdir(sample_app): + shutil.rmtree(sample_app) + else: + os.remove(sample_app) + # Also delete subdirs eg. cefclient_files/, ceftests_files/ + files_subdir = os.path.join(bin_dir, sample_app_name + "_files") + if os.path.isdir(files_subdir): + print("[build_distrib.py] Delete directory: {dir}/" + .format(dir=os.path.basename(files_subdir))) + shutil.rmtree(files_subdir) + + def _detect_cef_binaries_libraries_dir(): """Detect cef binary directory created by automate.py eg. build/cef55_3.2883.1553.g80bd606_win32/ diff --git a/tools/installer/cefpython3.setup.py b/tools/installer/cefpython3.setup.py index 3458e09a1..08065ebd5 100644 --- a/tools/installer/cefpython3.setup.py +++ b/tools/installer/cefpython3.setup.py @@ -152,8 +152,6 @@ def main(): "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Browsers", "Topic :: Multimedia", - ("Topic :: Software Development :: Libraries" - ":: Application Frameworks"), "Topic :: Software Development :: User Interfaces", ], ) @@ -236,6 +234,8 @@ def post_install_hook(): print("[setup.py] Set execute permissions on executables") for executable in get_executables(): executable = os.path.join(installed_package_dir, executable) + if not os.path.exists(executable): + continue command = "chmod +x {executable}".format(executable=executable) print("[setup.py] {command}".format(command=command)) subprocess.call(command, shell=True) diff --git a/tools/make_installer.py b/tools/make_installer.py index 518b62cc2..058c3869a 100644 --- a/tools/make_installer.py +++ b/tools/make_installer.py @@ -76,14 +76,16 @@ def main(): (EXAMPLES_DIR, "*"), (SETUP_DIR, "examples/"), ] perform_copy_operations(copy_operations) + delete_cef_sample_apps(caller_script=__file__, bin_dir=PKG_DIR) # Linux only operations if LINUX: + os.makedirs(os.path.join(SETUP_DIR, "examples", "kivy-select-boxes")) copy_operations_linux = [ (LINUX_DIR, "binaries_64bit/kivy_.py"), - (PKG_DIR, "examples/"), + (SETUP_DIR, "examples/"), (LINUX_DIR, "binaries_64bit/kivy-select-boxes/*"), - (PKG_DIR, "examples/") + (SETUP_DIR, "examples/kivy-select-boxes/") ] perform_copy_operations(copy_operations_linux) @@ -129,6 +131,9 @@ def command_line_args(): continue if WHEEL: WHEEL_ARGS.append(arg) + if WHEEL and not len(WHEEL_ARGS): + print("ERROR: wheel requires additional args eg. --universal") + sys.exit(1) def copy_tools_installer_files(setup_dir, pkg_dir): From 6640ed2cb841a68381312d16cbe32dfb0eb3abae Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 16 Mar 2017 23:00:27 +0100 Subject: [PATCH 016/398] Fix links in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ebf04ddad..e144c7659 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ scraping or as a web crawler, or other kind of internet bots. - Documentation is in the [docs/](docs) directory - API reference is in the [api/](api) directory - Additional documentation is in issues labelled [Knowledge Base] - (../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +(../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) - Wiki pages are deprecated and for v31 only @@ -67,7 +67,7 @@ scraping or as a web crawler, or other kind of internet bots. - Documentation is in the [docs/](docs) directory - API reference is in the [api/](api) directory - Additional documentation is in issues labelled [Knowledge Base] - (../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +(../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) ### v31 release @@ -79,7 +79,7 @@ scraping or as a web crawler, or other kind of internet bots. - Mac support: 32-bit and 64-bit (requirements: MacOS 10.7+) - Documentation is on [wiki pages](../../wiki) - API reference is available in revision [169a1b2] - (../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) +(../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) ## Support development From 41fa14fe05736066b7f4523697f677e8aa68185c Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 16 Mar 2017 23:06:38 +0100 Subject: [PATCH 017/398] Fix links in docs/ and in README again --- README.md | 44 ++++++++++++++++---------------------- docs/Build-instructions.md | 6 ++---- docs/Knowledge-Base.md | 27 +++++++++-------------- 3 files changed, 30 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index e144c7659..a656c62da 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,12 @@ Table of contents: ## Introduction -CEF Python is a BSD-licensed open source project founded by [Czarek Tomczak] -(http://www.linkedin.com/in/czarektomczak) in 2012 and is based on -Google Chromium and the [CEF Framework] -(https://bitbucket.org/chromiumembedded/cef) projects. The Chromium -project focuses mainly on Google Chrome application development, while -CEF focuses on facilitating embedded browser use cases in third-party -applications. Lots of applications use CEF control, there are more than -[100 million CEF instances] -(http://en.wikipedia.org/wiki/Chromium_Embedded_Framework#Applications_using_CEF) +CEF Python is a BSD-licensed open source project founded by [Czarek Tomczak](http://www.linkedin.com/in/czarektomczak) +in 2012 and is based on Google Chromium and the [CEF Framework](https://bitbucket.org/chromiumembedded/cef) +projects. The Chromium project focuses mainly on Google Chrome application +development, while CEF focuses on facilitating embedded browser use cases +in third-party applications. Lots of applications use CEF control, there are +more than [100 million CEF instances](http://en.wikipedia.org/wiki/Chromium_Embedded_Framework#Applications_using_CEF) installed around the world. [Examples of embedding](examples/Examples-README.md) Chrome browser are available for many popular GUI toolkits including: wxPython, PyGTK, PyQt, PySide, Kivy, Panda3D and PyGame/PyOpenGL. @@ -33,22 +30,20 @@ There are many use cases for CEF. You can embed a web browser control based on Chromium with great HTML 5 support. You can use it to create a HTML 5 based GUI in an application, this can act as a replacement for standard GUI toolkits such as wxWidgets, Qt or GTK. In such case to -communicate between Python<>Javascript use [javascript bindings] -(api/JavascriptBindings.md) or embed an internal web server and talk -using http requests. You can render web content off-screen in -applications that use custom drawing frameworks. You can use it for -automated testing of existing applications. You can use it for web -scraping or as a web crawler, or other kind of internet bots. +communicate between Python<>Javascript use [javascript bindings](api/JavascriptBindings.md) +or embed an internal web server and talk using http requests. You +can render web content off-screen in applications that use custom +drawing frameworks. You can use it for automated testing of existing +applications. You can use it for web scraping or as a web crawler, +or other kind of internet bots. ## Support -- Ask questions, report problems and issues on the [Forum] - (https://groups.google.com/group/cefpython) +- Ask questions, report problems and issues on the [Forum](https://groups.google.com/group/cefpython) - Documentation is in the [docs/](docs) directory - API reference is in the [api/](api) directory -- Additional documentation is in issues labelled [Knowledge Base] -(../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +- Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) - Wiki pages are deprecated and for v31 only @@ -66,8 +61,7 @@ scraping or as a web crawler, or other kind of internet bots. (requirements: MacOS 10.9+) - Documentation is in the [docs/](docs) directory - API reference is in the [api/](api) directory -- Additional documentation is in issues labelled [Knowledge Base] -(../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +- Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) ### v31 release @@ -78,8 +72,7 @@ scraping or as a web crawler, or other kind of internet bots. - Linux support: 32-bit and 64-bit (requirements: Debian 7+ / Ubuntu 12.04+) - Mac support: 32-bit and 64-bit (requirements: MacOS 10.7+) - Documentation is on [wiki pages](../../wiki) -- API reference is available in revision [169a1b2] -(../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) +- API reference is available in revision [169a1b2](../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) ## Support development @@ -105,9 +98,8 @@ directly. the v55/v56 releases for all platforms * Thanks to JetBrains for providing an Open Source license for [PyCharm](https://www.jetbrains.com/pycharm/) -* Thanks to those who have made a Paypal donation: [Rentouch GmbH] - (http://www.rentouch.ch/), Walter Purvis, Rokas Stupuras, Alex Rattray, - Greg Kacy, Paul Korzhyk +* Thanks to those who have made a Paypal donation: [Rentouch GmbH](http://www.rentouch.ch/), + Walter Purvis, Rokas Stupuras, Alex Rattray, Greg Kacy, Paul Korzhyk * Lots of thanks goes to [Cyan Inc.](http://www.blueplanet.com/) for sponsoring this project for a long time, making CEF Python 3 mature * Thanks to those who have donated their time through code contributions, diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index 910e9d5f1..f9009799e 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -163,8 +163,7 @@ requirements common for all platforms. if does not exist * For Python 3.4 follow the instructions for installing Windows SDK 7.1. If you encounter issue with .NET Framework 4 then make registry edits - as suggested here: [Windows SDK setup failure] - (http://stackoverflow.com/a/33260090/623622). + as suggested here: [Windows SDK setup failure](http://stackoverflow.com/a/33260090/623622). * For Python 3.4, if getting error: `Cannot open include file 'ammintrin.h': No such file or directory` then Copy that `ammitrin.h` file from for example VS 2015 installation @@ -336,8 +335,7 @@ __Debug_GN_arm/ configuration error (Linux)__: Even though building on Linux for Linux, Chromium still runs ARM configuration files. If there is an error showing that pkg-config fails with GTK 3 library then see solution in the third post in this topic on CEF Forum: -[Debug_GN_arm error when building on Linux, *not* arm] -(https://magpcss.org/ceforum/viewtopic.php?f=6&t=14976). +[Debug_GN_arm error when building on Linux, *not* arm](https://magpcss.org/ceforum/viewtopic.php?f=6&t=14976). __MISSING PACKAGES (Linux)__: After the chromium sources are downloaded, it will try to build cef projects and if it fails due to missing packages diff --git a/docs/Knowledge-Base.md b/docs/Knowledge-Base.md index c43b417c4..fb9ea8658 100644 --- a/docs/Knowledge-Base.md +++ b/docs/Knowledge-Base.md @@ -19,11 +19,9 @@ Table of contents: ## Notifications on new releases -To be notified of new releases subscribe to this [RSS/Atom feed] -(../../../releases.atom). +To be notified of new releases subscribe to this [RSS/Atom feed](../../../releases.atom). -Announcements are also made on the [Forum] -(https://groups.google.com/d/forum/cefpython). +Announcements are also made on the [Forum](https://groups.google.com/d/forum/cefpython). To be notified of these via email set your Membership and Email settings and change delivery preference to Daily summaries. @@ -33,8 +31,8 @@ and change delivery preference to Daily summaries. CEF Python depends on CEF and API breaks are inevitable when updating to latest CEF. The [Migration Guide](Migration-guide.md) document which is still under works, will list most notable breaking changes since -v31 release. Until it's done go to go to the [GitHub Releases] -(../../../releases) page and check release notes for all the releases +v31 release. Until it's done go to go to the [GitHub Releases](../../../releases) +page and check release notes for all the releases that appeared between your old version and the new version. Look for lists named "Changes in API that break backward compatibility" or similar. @@ -61,9 +59,8 @@ on Mac. By default CEF expects that CEF framework is located at `Contents/Frameworks/Chromium Embedded Framework.framework` in the top-level app bundle. If that is not the case then you have -to set ApplicationSettings.[framework_dir_path] -(../api/ApplicationSettings.md#framework_dir_path) before calling -cef.Initialize(). +to set ApplicationSettings.[framework_dir_path](../api/ApplicationSettings.md#framework_dir_path) +before calling cef.Initialize(). You may also need to change the structure and embedded paths in CEF framework and in the cefpython module. Here are the default @@ -113,8 +110,7 @@ as of now. To see if some feature is working or a bug is fixed in newer CEF release perform the following steps: -1. Go to [Spotify Automated Builds] - (http://opensource.spotify.com/cefbuilds/index.html) +1. Go to [Spotify Automated Builds](http://opensource.spotify.com/cefbuilds/index.html) to download latest CEF for your platform. Choose "Sample Application" binaries. 2. Extract the archive and run sample application from the @@ -188,8 +184,7 @@ Now you should see debug information displayed in console like this: Install gdb: - On Linux type: `sudo apt-get install gdb` -- On Mac type: `brew install gdb` and then [sign gdb] - (https://sourceware.org/gdb/wiki/BuildingOnDarwin#Giving_gdb_permission_to_control_other_processes) +- On Mac type: `brew install gdb` and then [sign gdb](https://sourceware.org/gdb/wiki/BuildingOnDarwin#Giving_gdb_permission_to_control_other_processes) - Additionally on Mac to get a meaningful stack trace with gdb do these steps: - Install [macports](https://www.macports.org/install.php) and restart terminal @@ -224,8 +219,7 @@ supported XP was v49. On XP you should disable GPU acceleration by using the --disable-gpu and --disable-gpu-compositing switches. These switches must be passed -programmatically to cef.Initialize(), see [api/Command Line Switches] -(../api/CommandLineSwitches.md). +programmatically to cef.Initialize(), see [api/Command Line Switches](../api/CommandLineSwitches.md). ## Mac 32-bit support @@ -266,7 +260,6 @@ A quote by Marshall Greenblatt: > decision that may have security consequences should be evaluated > by people who are knowledgeable about security considerations. -Reference: [Question on browser security] -(http://magpcss.org/ceforum/viewtopic.php?f=10&t=10222) +Reference: [Question on browser security](http://magpcss.org/ceforum/viewtopic.php?f=10&t=10222) on the CEF Forum. From 36faf91f3a508b9e62c6c098947c71d184469981 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 18 Mar 2017 13:20:44 +0100 Subject: [PATCH 018/398] Fix PyBrowser references living forever (#330). --- api/Browser.md | 14 ++- api/Frame.md | 13 +++ api/JavascriptCallback.md | 27 ++++- src/browser.pyx | 113 ++++++++++++++------- src/cefpython.pyx | 7 +- src/frame.pyx | 41 ++++++-- src/handlers/display_handler.pyx | 11 +- src/handlers/focus_handler.pyx | 8 +- src/handlers/javascript_dialog_handler.pyx | 12 ++- src/handlers/keyboard_handler.pyx | 4 +- src/handlers/lifespan_handler.pyx | 11 +- src/handlers/load_handler.pyx | 9 +- src/handlers/render_handler.pyx | 24 ++--- src/handlers/request_handler.pyx | 23 +++-- src/handlers/v8context_handler.pyx | 11 +- src/handlers/v8function_handler.pyx | 3 +- src/javascript_callback.pyx | 22 ++-- tools/build.py | 11 ++ 18 files changed, 257 insertions(+), 107 deletions(-) diff --git a/api/Browser.md b/api/Browser.md index 46135cc0f..58e566980 100644 --- a/api/Browser.md +++ b/api/Browser.md @@ -5,8 +5,18 @@ Remember to free all browser references for the browser to shut down cleanly. Otherwise data such as cookies or other storage might not be flushed to disk -when closing app, and other issues might occur as well. To free a reference -just assign a None value to a "browser" variable. +when closing app, and other issues might occur as well. If you store +a reference to Frame somewhere in your code then to free it just assign +a None value to the variable. + +To compare browser objects always use [GetIdentifier()](#getidentifier) +method. Do not compare two Browser objects variables directly. There +are some edge cases when after the OnBeforeClose event browser objects +are no more globally referenced thus a new instance is created that +wraps upstream CefBrowser object. Browser objects that were globally +unreferenced do not have properties of the original Browser object, +for example they do not have client callbacks, javascript bindings +or user data set. Table of contents: diff --git a/api/Frame.md b/api/Frame.md index f214bdcdb..aef391b4e 100644 --- a/api/Frame.md +++ b/api/Frame.md @@ -3,6 +3,19 @@ # Frame (object) +Remember to free all frame references for the browser to shut down cleanly. +Otherwise data such as cookies or other storage might not be flushed to disk +when closing app, and other issues might occur as well. If you store +a reference to Frame somewhere in your code then to free it just assign +a None value to the variable. + +To compare frame objects always use [GetIdentifier()](#getidentifier) +method. Do not compare two Frame objects variables directly. There +are some edge cases when after the OnBeforeClose event frame objects +are no more globally referenced thus a new instance is created that +wraps upstream CefFrame object. Frame objects that were globally +unreferenced do not have properties of the original Frame object. + Table of contents: * [Methods](#methods) diff --git a/api/JavascriptCallback.md b/api/JavascriptCallback.md index 6f4ba790b..97b3bc063 100644 --- a/api/JavascriptCallback.md +++ b/api/JavascriptCallback.md @@ -15,7 +15,9 @@ See also [Issue #11](../issues/11) (Throw JS / Python exceptions according to ex Table of contents: * [Methods](#methods) * [Call](#call) - * [GetName](#getname) + * [GetFrame](#getframe) + * [GetId](#getid) + * [GetFunctionName](#getfunctionname) ## Methods @@ -30,10 +32,29 @@ Table of contents: Call the javascript callback function. -For a list of allowed types for `mixed` see [JavascriptBindings](JavascriptBindings.md).IsValueAllowed(). +For a list of allowed types for `mixed` see JavascriptBindings.[IsValueAllowed()](JavascriptBindings.md#isvalueallowed). -### GetName +### GetFrame + +| | | +| --- | --- | +| __Return__ | [Frame](Frame.md) | + +Get Frame object associated with this callback. If Browser was destroyed +then Frame may be None. + + +### GetId + +| | | +| --- | --- | +| __Return__ | int | + +Get this callback's identifier. + + +### GetFunctionName | | | | --- | --- | diff --git a/src/browser.pyx b/src/browser.pyx index c5aadd53c..d27a75e77 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -17,75 +17,109 @@ MOUSEBUTTON_RIGHT = cef_types.MBT_RIGHT # get segmentation faults, as they will be garbage collected. cdef dict g_pyBrowsers = {} + +# Unreferenced browsers are added to this list in OnBeforeClose(). +# Must keep a list of unreferenced browsers so that a new reference +# is not created in GetPyBrowser() when browser was closed. +cdef list g_unreferenced_browsers = [] # [int identifier, ..] + +# Browsers that are about to be closed are added to this list in +# CloseBrowser(). cdef list g_closed_browsers = [] # [int identifier, ..] cdef PyBrowser GetPyBrowserById(int browserId): + """May return None value so always check returned value.""" if browserId in g_pyBrowsers: return g_pyBrowsers[browserId] return None -cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser): +cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser, + callerIdStr="GetPyBrowser"): + """The second argument 'callerIdStr' is so that a debug + message can be displayed informing which CEF handler callback + is being called to which an incomplete PyBrowser instance is + provided.""" + global g_pyBrowsers + + # This probably ain't needed, but just to be sure. if cefBrowser == NULL or not cefBrowser.get(): - # noinspection PyUnresolvedReferences - Debug("GetPyBrowser(): returning None") - return None + raise Exception("{caller}: CefBrowser reference is NULL" + .format(caller=callerIdStr)) cdef PyBrowser pyBrowser cdef int browserId - cdef int identifier - browserId = cefBrowser.get().GetIdentifier() + if browserId in g_pyBrowsers: return g_pyBrowsers[browserId] + # This code probably ain't needed. + # ---- + cdef list toRemove = [] + cdef int identifier for identifier, pyBrowser in g_pyBrowsers.items(): if not pyBrowser.cefBrowser.get(): - # noinspection PyUnresolvedReferences - Debug("GetPyBrowser(): removing an empty CefBrowser reference, " - "browserId=%s" % identifier) - del g_pyBrowsers[identifier] + toRemove.append(identifier) + for identifier in toRemove: + Debug("GetPyBrowser(): removing an empty CefBrowser reference," + " browserId=%s" % identifier) + RemovePyBrowser(identifier) + # ---- - # noinspection PyUnresolvedReferences - Debug("GetPyBrowser(): creating new PyBrowser, browserId=%s" % browserId) pyBrowser = PyBrowser() pyBrowser.cefBrowser = cefBrowser - g_pyBrowsers[browserId] = pyBrowser - - # Inherit client callbacks and javascript bindings - # from parent browser. - - # Checking __outerWindowHandle as we should not inherit - # client callbacks and javascript bindings if the browser - # was created explicitily by calling CreateBrowserSync(). - - # Popups inherit client callbacks by default. - - # Popups inherit javascript bindings only when "bindToPopups" - # constructor param was set to True. cdef WindowHandle openerHandle cdef dict clientCallbacks cdef JavascriptBindings javascriptBindings cdef PyBrowser tempPyBrowser - if pyBrowser.IsPopup() and \ - not pyBrowser.GetUserData("__outerWindowHandle"): - openerHandle = pyBrowser.GetOpenerWindowHandle() - for identifier, tempPyBrowser in g_pyBrowsers.items(): - if tempPyBrowser.GetWindowHandle() == openerHandle: - clientCallbacks = tempPyBrowser.GetClientCallbacksDict() - if clientCallbacks: - pyBrowser.SetClientCallbacksDict(clientCallbacks) - javascriptBindings = tempPyBrowser.GetJavascriptBindings() - if javascriptBindings: - if javascriptBindings.GetBindToPopups(): - pyBrowser.SetJavascriptBindings(javascriptBindings) + if browserId in g_unreferenced_browsers: + # This browser was already unreferenced due to OnBeforeClose + # was already called. An incomplete new instance of Browser + # object is created. This instance doesn't have the client + # callbacks, javascript bindings or user data that was already + # available in the original Browser object. + Debug("{caller}: Browser was already globally unreferenced" + ", a new incomplete instance is created, browser id={id}" + .format(caller=callerIdStr, id=str(browserId))) + else: + # This is first creation of browser. Store a reference globally + # and inherit client callbacks and javascript bindings from + # parent browsers. + Debug("GetPyBrowser(): create new PyBrowser, browserId=%s" + % browserId) + + g_pyBrowsers[browserId] = pyBrowser + + # Inherit client callbacks and javascript bindings + # from parent browser. + # - Checking __outerWindowHandle as we should not inherit + # client callbacks and javascript bindings if the browser + # was created explicitily by calling CreateBrowserSync(). + # - Popups inherit client callbacks by default. + # - Popups inherit javascript bindings only when "bindToPopups" + # constructor param was set to True. + + if pyBrowser.IsPopup() and \ + not pyBrowser.GetUserData("__outerWindowHandle"): + openerHandle = pyBrowser.GetOpenerWindowHandle() + for identifier, tempPyBrowser in g_pyBrowsers.items(): + if tempPyBrowser.GetWindowHandle() == openerHandle: + clientCallbacks = tempPyBrowser.GetClientCallbacksDict() + if clientCallbacks: + pyBrowser.SetClientCallbacksDict(clientCallbacks) + javascriptBindings = tempPyBrowser.GetJavascriptBindings() + if javascriptBindings: + if javascriptBindings.GetBindToPopups(): + pyBrowser.SetJavascriptBindings(javascriptBindings) + return pyBrowser cdef void RemovePyBrowser(int browserId) except *: # Called from LifespanHandler_OnBeforeClose(). - global g_pyBrowsers + global g_pyBrowsers, g_unreferenced_browsers if browserId in g_pyBrowsers: if len(g_pyBrowsers) == 1: # This is the last browser remaining. @@ -97,6 +131,7 @@ cdef void RemovePyBrowser(int browserId) except *: # noinspection PyUnresolvedReferences Debug("del g_pyBrowsers[%s]" % browserId) del g_pyBrowsers[browserId] + g_unreferenced_browsers.append(browserId) else: # noinspection PyUnresolvedReferences Debug("RemovePyBrowser() FAILED: browser not found, id = %s" \ @@ -116,7 +151,7 @@ cdef public void PyBrowser_ShowDevTools(CefRefPtr[CefBrowser] cefBrowser # Called from ClientHandler::OnContextMenuCommand cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "ShowDevTools") pyBrowser.ShowDevTools() except: (exc_type, exc_value, exc_trace) = sys.exc_info() diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 941fff00b..6fb922e1b 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -838,7 +838,8 @@ def CreateBrowserSync(windowInfo=None, requestContextHandler.get().SetBrowser(cefBrowser) cdef PyBrowser pyBrowser = GetPyBrowser(cefBrowser) - pyBrowser.SetUserData("__outerWindowHandle", int(windowInfo.parentWindowHandle)) + pyBrowser.SetUserData("__outerWindowHandle", + int(windowInfo.parentWindowHandle)) return pyBrowser @@ -889,6 +890,10 @@ def Shutdown(): # Run some message loop work, force closing browsers and then run # some message loop work again for the browsers to close cleanly. # + # UPDATE: This code needs to be rechecked. There were enhancements + # to unrferencing globally stored Browser objects in + # g_pyBrowsers. See Issue #330 and its commits. + # # CASE 1: # There might be a case when python error occured after creating # browser, but before any message loop was run. In such case diff --git a/src/frame.pyx b/src/frame.pyx index 9a3faca94..9342719c6 100644 --- a/src/frame.pyx +++ b/src/frame.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "cefpython.pyx" +include "browser.pyx" cdef dict g_pyFrames = {} @@ -17,25 +18,51 @@ cdef PyFrame GetPyFrameById(int browserId, object frameId): cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): global g_pyFrames + + # This code probably ain't needed, but just to be sure. if cefFrame == NULL or not cefFrame.get(): - Debug("GetPyFrame(): returning None") - return + raise Exception("GetPyFrame(): CefFrame reference is NULL") + cdef PyFrame pyFrame cdef object frameId = cefFrame.get().GetIdentifier() # int64 cdef int browserId = cefFrame.get().GetBrowser().get().GetIdentifier() assert (frameId and browserId), "frameId or browserId empty" cdef object uniqueFrameId = GetUniqueFrameId(browserId, frameId) + if uniqueFrameId in g_pyFrames: return g_pyFrames[uniqueFrameId] + + # This code probably ain't needed. + # ---- + cdef list toRemove = [] for uFid, pyFrame in g_pyFrames.items(): if not pyFrame.cefFrame.get(): - Debug("GetPyFrame(): removing an empty CefFrame reference, " \ - "uniqueFrameId = %s" % uniqueFrameId) - del g_pyFrames[uFid] - # Debug("GetPyFrame(): creating new PyFrame, frameId=%s" % frameId) + toRemove.append(uFid) + for uFid in toRemove: + Debug("GetPyFrame(): removing an empty CefFrame reference, " + "uniqueFrameId = %s" % uniqueFrameId) + del g_pyFrames[uFid] + # ---- + pyFrame = PyFrame(browserId, frameId) pyFrame.cefFrame = cefFrame - g_pyFrames[uniqueFrameId] = pyFrame + + if browserId in g_unreferenced_browsers: + # Browser was already globally unreferenced in OnBeforeClose, + # thus all frames are globally unreferenced too. Create a new + # incomplete instance of PyFrame object. Read comments in + # browser.pyx > GetPyBrowser and in Browser.md for what + # "incomplete" means. + pass + else: + # Keep a global reference to this frame only if the browser + # wasn't destroyed in OnBeforeClose. Otherwise we would leave + # dead frames references living forever. + # SIDE EFFECT: two calls to GetPyFrame for the same frame object + # may return two different PyFrame objects. Compare + # frame objects always using GetIdentifier(). + # Debug("GetPyFrame(): create new PyFrame, frameId=%s" % frameId) + g_pyFrames[uniqueFrameId] = pyFrame return pyFrame cdef void RemovePyFrame(int browserId, object frameId) except *: diff --git a/src/handlers/display_handler.pyx b/src/handlers/display_handler.pyx index 3db61a7c6..d67cb4d17 100644 --- a/src/handlers/display_handler.pyx +++ b/src/handlers/display_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cdef public void DisplayHandler_OnAddressChange( CefRefPtr[CefBrowser] cefBrowser, @@ -14,7 +15,7 @@ cdef public void DisplayHandler_OnAddressChange( cdef py_string pyUrl cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnAddressChange") pyFrame = GetPyFrame(cefFrame) pyUrl = CefToPyString(cefUrl) callback = pyBrowser.GetClientCallback("OnAddressChange") @@ -32,7 +33,7 @@ cdef public void DisplayHandler_OnTitleChange( cdef py_string pyTitle cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnTitleChange") pyTitle = CefToPyString(cefTitle) callback = pyBrowser.GetClientCallback("OnTitleChange") if callback: @@ -51,7 +52,7 @@ cdef public cpp_bool DisplayHandler_OnTooltip( cdef object callback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnTooltip") pyText = CefToPyString(cefText) pyTextOut = [pyText] callback = pyBrowser.GetClientCallback("OnTooltip") @@ -73,7 +74,7 @@ cdef public void DisplayHandler_OnStatusMessage( cdef py_string pyValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnStatusMessage") pyValue = CefToPyString(cefValue) callback = pyBrowser.GetClientCallback("OnStatusMessage") if callback: @@ -94,7 +95,7 @@ cdef public cpp_bool DisplayHandler_OnConsoleMessage( cdef py_bool returnValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnConsoleMessage") pyMessage = CefToPyString(cefMessage) pySource = CefToPyString(cefSource) callback = pyBrowser.GetClientCallback("OnConsoleMessage") diff --git a/src/handlers/focus_handler.pyx b/src/handlers/focus_handler.pyx index f966c6132..71c04523a 100644 --- a/src/handlers/focus_handler.pyx +++ b/src/handlers/focus_handler.pyx @@ -3,6 +3,8 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" + cimport cef_types from cef_types cimport TID_UI @@ -17,7 +19,7 @@ cdef public void FocusHandler_OnTakeFocus( cdef PyBrowser browser try: assert IsThread(TID_UI), "Must be called on the UI thread" - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "OnTakeFocus") callback = browser.GetClientCallback("OnTakeFocus") if callback: callback(browser=browser, next=next_) @@ -34,7 +36,7 @@ cdef public cpp_bool FocusHandler_OnSetFocus( cdef py_bool ret try: assert IsThread(TID_UI), "Must be called on the UI thread" - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "OnSetFocus") callback = browser.GetClientCallback("OnSetFocus") if callback: ret = callback(browser=browser, source=source) @@ -52,7 +54,7 @@ cdef public void FocusHandler_OnGotFocus( cdef PyBrowser browser try: assert IsThread(TID_UI), "Must be called on the UI thread" - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "OnGotFocus") callback = browser.GetClientCallback("OnGotFocus") if callback: callback(browser=browser) diff --git a/src/handlers/javascript_dialog_handler.pyx b/src/handlers/javascript_dialog_handler.pyx index 0c7fa071b..283aad54c 100644 --- a/src/handlers/javascript_dialog_handler.pyx +++ b/src/handlers/javascript_dialog_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" # enum cef_jsdialog_type_t cimport cef_types @@ -48,7 +49,7 @@ cdef public cpp_bool JavascriptDialogHandler_OnJavascriptDialog( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnJavascriptDialog") pyOriginUrl = CefToPyString(origin_url) pyMessageText = CefToPyString(message_text) pyDefaultPromptText = CefToPyString(default_prompt_text) @@ -86,7 +87,8 @@ cdef public cpp_bool JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, + "OnBeforeUnloadJavascriptDialog") pyMessageText = CefToPyString(message_text) pyIsReload = bool(is_reload) pyCallback = CreatePyJavascriptDialogCallback(callback) @@ -110,7 +112,8 @@ cdef public void JavascriptDialogHandler_OnResetJavascriptDialogState( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, + "OnResetJavascriptDialogState") callback = pyBrowser.GetClientCallback( "OnResetJavascriptDialogState") if callback: @@ -124,7 +127,8 @@ cdef public void JavascriptDialogHandler_OnJavascriptDialogClosed( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, + "OnJavascriptDialogClosed") callback = pyBrowser.GetClientCallback("OnJavascriptDialogClosed") if callback: callback(browser=pyBrowser) diff --git a/src/handlers/keyboard_handler.pyx b/src/handlers/keyboard_handler.pyx index 408487c5f..c895b9b5e 100644 --- a/src/handlers/keyboard_handler.pyx +++ b/src/handlers/keyboard_handler.pyx @@ -54,7 +54,7 @@ cdef public cpp_bool KeyboardHandler_OnPreKeyEvent( cdef py_bool returnValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPreKeyEvent") pyEvent = CefToPyKeyEvent(cefEvent) pyIsKeyboardShortcutOut = [cefIsKeyboardShortcut[0]] callback = pyBrowser.GetClientCallback("OnPreKeyEvent") @@ -82,7 +82,7 @@ cdef public cpp_bool KeyboardHandler_OnKeyEvent( cdef py_bool returnValue cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnKeyEvent") pyEvent = CefToPyKeyEvent(cefEvent) callback = pyBrowser.GetClientCallback("OnKeyEvent") if callback: diff --git a/src/handlers/lifespan_handler.pyx b/src/handlers/lifespan_handler.pyx index dbc379434..ad6730b1b 100644 --- a/src/handlers/lifespan_handler.pyx +++ b/src/handlers/lifespan_handler.pyx @@ -47,7 +47,7 @@ cdef public cpp_bool LifespanHandler_OnBeforePopup( cdef object callback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforePopup") pyFrame = GetPyFrame(cefFrame) pyTargetUrl = CefToPyString(targetUrl) pyTargetFrameName = CefToPyString(targetFrameName) @@ -84,7 +84,7 @@ cdef public void LifespanHandler_OnAfterCreated( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnAfterCreated") callback = GetGlobalClientCallback("OnAfterCreated") if callback: callback(browser=pyBrowser) @@ -97,7 +97,7 @@ cdef public cpp_bool LifespanHandler_DoClose( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "DoClose") callback = pyBrowser.GetClientCallback("DoClose") if callback: return bool(callback(browser=pyBrowser)) @@ -112,7 +112,8 @@ cdef public void LifespanHandler_OnBeforeClose( cdef PyBrowser pyBrowser cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + Debug("LifespanHandler_OnBeforeClose") + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeClose") callback = pyBrowser.GetClientCallback("OnBeforeClose") if callback: callback(browser=pyBrowser) @@ -120,6 +121,8 @@ cdef public void LifespanHandler_OnBeforeClose( RemovePyFramesForBrowser(pyBrowser.GetIdentifier()) RemovePyBrowser(pyBrowser.GetIdentifier()) if g_MessageLoop_called and not len(g_pyBrowsers): + # Automatically quit message loop when last browser was closed. + # This is required for hello_world.py example to work. QuitMessageLoop() except: diff --git a/src/handlers/load_handler.pyx b/src/handlers/load_handler.pyx index 47e1d28df..c7627f5cf 100644 --- a/src/handlers/load_handler.pyx +++ b/src/handlers/load_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cdef public void LoadHandler_OnLoadingStateChange( CefRefPtr[CefBrowser] cefBrowser, @@ -13,7 +14,7 @@ cdef public void LoadHandler_OnLoadingStateChange( cdef PyBrowser pyBrowser cdef object callback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadingStateChange") callback = pyBrowser.GetClientCallback("OnLoadingStateChange") if callback: callback(browser=pyBrowser, @@ -32,7 +33,7 @@ cdef public void LoadHandler_OnLoadStart( cdef PyFrame pyFrame cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadStart") pyFrame = GetPyFrame(cefFrame) clientCallback = pyBrowser.GetClientCallback("OnLoadStart") if clientCallback: @@ -50,7 +51,7 @@ cdef public void LoadHandler_OnLoadEnd( cdef PyFrame pyFrame cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadEnd") pyFrame = GetPyFrame(cefFrame) clientCallback = pyBrowser.GetClientCallback("OnLoadEnd") if clientCallback: @@ -77,7 +78,7 @@ cdef public void LoadHandler_OnLoadError( # the error code will be ERR_ABORTED. In such cases calls # to OnLoadError should be ignored and not handled by user # scripts. The wxpython example implements such behavior. - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnLoadError") pyFrame = GetPyFrame(cefFrame) errorTextOut = [CefToPyString(cefErrorText)] clientCallback = pyBrowser.GetClientCallback("OnLoadError") diff --git a/src/handlers/render_handler.pyx b/src/handlers/render_handler.pyx index acbb5c285..1594d6bd6 100644 --- a/src/handlers/render_handler.pyx +++ b/src/handlers/render_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cimport cef_types @@ -29,7 +30,7 @@ cdef public cpp_bool RenderHandler_GetRootScreenRect( cdef list pyRect = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetRootScreenRect") callback = pyBrowser.GetClientCallback("GetRootScreenRect") if callback: ret = callback(browser=pyBrowser, rect_out=pyRect) @@ -56,7 +57,7 @@ cdef public cpp_bool RenderHandler_GetViewRect( cdef list pyRect = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetViewRect") callback = pyBrowser.GetClientCallback("GetViewRect") if callback: ret = callback(browser=pyBrowser, rect_out=pyRect) @@ -83,7 +84,7 @@ cdef public cpp_bool RenderHandler_GetScreenRect( cdef list pyRect = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetScreenRect") callback = pyBrowser.GetClientCallback("GetScreenRect") if callback: ret = callback(browser=pyBrowser, rect_out=pyRect) @@ -112,7 +113,7 @@ cdef public cpp_bool RenderHandler_GetScreenPoint( cdef list screenCoordinates = [] cdef py_bool ret try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetScreenPoint") callback = pyBrowser.GetClientCallback("GetScreenPoint") if callback: ret = callback(browser=pyBrowser, @@ -146,7 +147,7 @@ cdef public void RenderHandler_OnPopupShow( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPopupShow") callback = pyBrowser.GetClientCallback("OnPopupShow") if callback: callback(browser=pyBrowser, show=show) @@ -161,7 +162,7 @@ cdef public void RenderHandler_OnPopupSize( cdef PyBrowser pyBrowser cdef list pyRect try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPopupSize") callback = pyBrowser.GetClientCallback("OnPopupSize") if callback: pyRect = [cefRect.x, cefRect.y, cefRect.width, cefRect.height] @@ -187,8 +188,7 @@ cdef public void RenderHandler_OnPaint( cdef CefRect cefRect cdef PaintBuffer paintBuffer try: - pyBrowser = GetPyBrowser(cefBrowser) - + pyBrowser = GetPyBrowser(cefBrowser, "OnPaint") iterator = cefDirtyRects.begin() while iterator != cefDirtyRects.end(): cefRect = deref(iterator) @@ -223,7 +223,7 @@ cdef public void RenderHandler_OnCursorChange( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnCursorChange") callback = pyBrowser.GetClientCallback("OnCursorChange") if callback: callback(browser=pyBrowser, cursor=cursor) @@ -236,7 +236,7 @@ cdef public void RenderHandler_OnScrollOffsetChanged( ) except * with gil: cdef PyBrowser pyBrowser try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnScrollOffsetChanged") callback = pyBrowser.GetClientCallback("OnScrollOffsetChanged") if callback: callback(browser=pyBrowser) @@ -254,7 +254,7 @@ cdef public cpp_bool RenderHandler_StartDragging( cdef DragData drag_data cdef py_bool ret try: - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "StartDragging") drag_data = DragData_Init(cef_drag_data) callback = browser.GetClientCallback("StartDragging") if callback: @@ -280,7 +280,7 @@ cdef public void RenderHandler_UpdateDragCursor( ) except * with gil: cdef PyBrowser browser try: - browser = GetPyBrowser(cef_browser) + browser = GetPyBrowser(cef_browser, "UpdateDragCursor") callback = browser.GetClientCallback("UpdateDragCursor") if callback: callback(browser=browser, operation=operation) diff --git a/src/handlers/request_handler.pyx b/src/handlers/request_handler.pyx index 259e23a1c..b2cadbe7d 100644 --- a/src/handlers/request_handler.pyx +++ b/src/handlers/request_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" # cef_termination_status_t cimport cef_types @@ -67,7 +68,7 @@ cdef public cpp_bool RequestHandler_OnBeforeBrowse( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeBrowse") pyFrame = GetPyFrame(cefFrame) pyRequest = CreatePyRequest(cefRequest) pyIsRedirect = bool(cefIsRedirect) @@ -97,7 +98,7 @@ cdef public cpp_bool RequestHandler_OnBeforeResourceLoad( cdef object clientCallback cdef py_bool returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeResourceLoad") pyFrame = GetPyFrame(cefFrame) pyRequest = CreatePyRequest(cefRequest) clientCallback = pyBrowser.GetClientCallback("OnBeforeResourceLoad") @@ -125,7 +126,7 @@ cdef public CefRefPtr[CefResourceHandler] RequestHandler_GetResourceHandler( cdef object clientCallback cdef object returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetResourceHandler") pyFrame = GetPyFrame(cefFrame) pyRequest = CreatePyRequest(cefRequest) clientCallback = pyBrowser.GetClientCallback("GetResourceHandler") @@ -161,7 +162,7 @@ cdef public void RequestHandler_OnResourceRedirect( cdef PyResponse pyResponse cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnResourceRedirect") pyFrame = GetPyFrame(cefFrame) pyOldUrl = CefToPyString(cefOldUrl) pyNewUrlOut = [CefToPyString(cefNewUrl)] @@ -206,7 +207,7 @@ cdef public cpp_bool RequestHandler_GetAuthCredentials( cdef list pyPasswordOut cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetAuthCredentials") pyFrame = GetPyFrame(cefFrame) pyIsProxy = bool(cefIsProxy) pyHost = CefToPyString(cefHost) @@ -260,7 +261,7 @@ cdef public cpp_bool RequestHandler_OnQuotaRequest( cdef py_bool returnValue cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnQuotaRequest") pyOriginUrl = CefToPyString(cefOriginUrl) clientCallback = pyBrowser.GetClientCallback("OnQuotaRequest") if clientCallback: @@ -290,7 +291,7 @@ cdef public CefRefPtr[CefCookieManager] RequestHandler_GetCookieManager( cdef object clientCallback cdef PyCookieManager returnValue try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "GetCookieManager") pyMainUrl = CefToPyString(cefMainUrl) if pyBrowser: # Browser may be empty. @@ -322,7 +323,7 @@ cdef public void RequestHandler_OnProtocolExecution( cdef list pyAllowOSExecutionOut cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnProtocolExecution") pyUrl = CefToPyString(cefUrl) pyAllowOSExecutionOut = [bool(cefAllowOSExecution)] clientCallback = pyBrowser.GetClientCallback("OnProtocolExecution") @@ -357,7 +358,7 @@ cdef public cpp_bool RequestHandler_OnBeforePluginLoad( cdef py_bool returnValue cdef object clientCallback try: - py_browser = GetPyBrowser(browser) + py_browser = GetPyBrowser(browser, "OnBeforePluginLoad") py_plugin_info = CreatePyWebPluginInfo(plugin_info) clientCallback = GetGlobalClientCallback("OnBeforePluginLoad") if clientCallback: @@ -412,7 +413,7 @@ cdef public void RequestHandler_OnRendererProcessTerminated( cdef PyBrowser pyBrowser cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnRendererProcessTerminated") clientCallback = pyBrowser.GetClientCallback( "OnRendererProcessTerminated") if clientCallback: @@ -434,7 +435,7 @@ cdef public void RequestHandler_OnPluginCrashed( cdef PyBrowser pyBrowser cdef object clientCallback try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnPluginCrashed") clientCallback = pyBrowser.GetClientCallback("OnPluginCrashed") if clientCallback: clientCallback( diff --git a/src/handlers/v8context_handler.pyx b/src/handlers/v8context_handler.pyx index 741daab8c..704f6ad92 100644 --- a/src/handlers/v8context_handler.pyx +++ b/src/handlers/v8context_handler.pyx @@ -9,6 +9,7 @@ # a bit delayed due to asynchronous way this is being done. include "../cefpython.pyx" +include "../browser.pyx" cdef public void V8ContextHandler_OnContextCreated( CefRefPtr[CefBrowser] cefBrowser, @@ -19,7 +20,7 @@ cdef public void V8ContextHandler_OnContextCreated( cdef object clientCallback try: Debug("V8ContextHandler_OnContextCreated()") - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "OnContextCreated") pyBrowser.SetUserData("__v8ContextCreated", True) pyFrame = GetPyFrame(cefFrame) # User defined callback. @@ -48,6 +49,10 @@ cdef public void V8ContextHandler_OnContextReleased( # were released. Debug("V8ContextHandler_OnContextReleased()") pyBrowser = GetPyBrowserById(browserId) + if not pyBrowser: + Debug("OnContextReleased: Browser doesn't exist anymore, id={id}" + .format(id=str(browserId))) + return pyFrame = GetPyFrameById(browserId, frameId) if pyBrowser and pyFrame: clientCallback = pyBrowser.GetClientCallback("OnContextReleased") @@ -56,10 +61,10 @@ cdef public void V8ContextHandler_OnContextReleased( else: if not pyBrowser: Debug("V8ContextHandler_OnContextReleased() WARNING: " - "pyBrowser not found") + "PyBrowser not found") if not pyFrame: Debug("V8ContextHandler_OnContextReleased() WARNING: " - "pyFrame not found") + "PyFrame not found") RemovePyFrame(browserId, frameId) except: (exc_type, exc_value, exc_trace) = sys.exc_info() diff --git a/src/handlers/v8function_handler.pyx b/src/handlers/v8function_handler.pyx index c4cfd89d9..2c9b3f1cf 100644 --- a/src/handlers/v8function_handler.pyx +++ b/src/handlers/v8function_handler.pyx @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" +include "../browser.pyx" cdef public void V8FunctionHandler_Execute( CefRefPtr[CefBrowser] cefBrowser, @@ -18,7 +19,7 @@ cdef public void V8FunctionHandler_Execute( cdef object returnValue cdef py_string jsErrorMessage try: - pyBrowser = GetPyBrowser(cefBrowser) + pyBrowser = GetPyBrowser(cefBrowser, "V8FunctionHandler_Execute") pyFrame = GetPyFrame(cefFrame) functionName = CefToPyString(cefFunctionName) Debug("V8FunctionHandler_Execute(): functionName=%s" % functionName) diff --git a/src/javascript_callback.pyx b/src/javascript_callback.pyx index 81a06eb86..be474be77 100644 --- a/src/javascript_callback.pyx +++ b/src/javascript_callback.pyx @@ -3,9 +3,11 @@ # Project website: https://github.com/cztomczak/cefpython include "cefpython.pyx" +include "browser.pyx" cdef JavascriptCallback CreateJavascriptCallback(int callbackId, - CefRefPtr[CefBrowser] cefBrowser, object frameId, py_string functionName): + CefRefPtr[CefBrowser] cefBrowser, object frameId, + py_string functionName): # frameId is int64 cdef JavascriptCallback jsCallback = JavascriptCallback() jsCallback.callbackId = callbackId @@ -17,6 +19,8 @@ cdef JavascriptCallback CreateJavascriptCallback(int callbackId, return jsCallback cdef class JavascriptCallback: + """A javascript callback object may still live while browser/frame + are destroyed. Always check frame/browser for None value.""" cdef int callbackId cdef PyFrame frame cdef py_string functionName @@ -32,18 +36,24 @@ cdef class JavascriptCallback: "ExecuteJavascriptCallback", [self.callbackId] + list(args)) else: - Debug("JavascriptCallback.Call() FAILED: browser not found, " \ - "callbackId = %s" % self.callbackId) + # This code probably ain't needed + raise Exception("JavascriptCallback.Call() FAILED: browser" + " not found, callbackId = %s" + % self.callbackId) else: - Debug("JavascriptCallback.Call() FAILED: frame not found, " \ - "callbackId = %s" % self.callbackId) + # This code probably ain't needed + raise Exception("JavascriptCallback.Call() FAILED: frame not found" + ", callbackId = %s" % self.callbackId) def GetFunctionName(self): return self.functionName def GetName(self): - # DEPRECATED name. + """@deprecated.""" return self.GetFunctionName() + def GetId(self): + return self.callbackId + def GetFrame(self): return self.frame diff --git a/tools/build.py b/tools/build.py index fce02f349..9b6db3518 100644 --- a/tools/build.py +++ b/tools/build.py @@ -184,6 +184,17 @@ def check_directories(): if not os.path.exists(BUILD_CEFPYTHON): os.makedirs(BUILD_CEFPYTHON) + # Info if directory missing + if not os.path.exists(CEF_BINARIES_LIBRARIES): + prebuilt_name = get_cef_binaries_libraries_basename(OS_POSTFIX2) + print("[build.py] ERROR: Couldn't find CEF prebuilt binaries and" + " libraries: 'build/{prebuilt_dir}/'. Download it" + " from GitHub released tagged eg. 'v50-upstream` or download" + " CEF binaries from Spotify Automated Builds and then run" + "`automate.py --prebuilt-cef`." + .format(prebuilt_dir=prebuilt_name)) + sys.exit(1) + # Check directories exist assert os.path.exists(BUILD_DIR) assert os.path.exists(BUILD_CEFPYTHON) From 7f1e84b910f4992e9cdc9740b6031b3584c65563 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 19 Mar 2017 12:47:03 +0100 Subject: [PATCH 019/398] Fix segmentation fault during release of shared request context (#333) --- src/browser.pyx | 14 -------- src/cefpython.pyx | 59 ++++++++++++++++--------------- src/extern/cef/cef_ptr.pxd | 4 +-- src/extern/cef/cef_scoped_ptr.pxd | 4 +-- 4 files changed, 34 insertions(+), 47 deletions(-) diff --git a/src/browser.pyx b/src/browser.pyx index d27a75e77..59c1aaf1e 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -121,13 +121,6 @@ cdef void RemovePyBrowser(int browserId) except *: # Called from LifespanHandler_OnBeforeClose(). global g_pyBrowsers, g_unreferenced_browsers if browserId in g_pyBrowsers: - if len(g_pyBrowsers) == 1: - # This is the last browser remaining. - if g_sharedRequestContext.get(): - # A similar release is done in Shutdown and CloseBrowser. - # noinspection PyUnresolvedReferences - Debug("RemovePyBrowser: releasing shared request context") - g_sharedRequestContext.Assign(NULL) # noinspection PyUnresolvedReferences Debug("del g_pyBrowsers[%s]" % browserId) del g_pyBrowsers[browserId] @@ -302,13 +295,6 @@ cdef class PyBrowser: pass cpdef py_void CloseBrowser(self, py_bool forceClose=False): - if len(g_pyBrowsers) == 1: - # This is the last browser remaining. - if g_sharedRequestContext.get(): - # A similar release is done in Shutdown - # and RemovePyBrowser. - Debug("CloseBrowser: releasing shared request context") - g_sharedRequestContext.Assign(NULL) Debug("CefBrowser::CloseBrowser(%s)" % forceClose) cdef int browserId = self.GetCefBrowser().get().GetIdentifier() self.GetCefBrowserHost().get().CloseBrowser(bool(forceClose)) diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 6fb922e1b..64b1fe7c9 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -198,8 +198,8 @@ END OF: CHANGES in CEF since v31..v47. # | cdef cppclass _Object "Object": # # - Supporting operators that are not yet supported: -# | CefRefPtr[T]& Assign "operator="(T* p) -# | cefBrowser.Assign(CefBrowser*) +# | RetValue& Assign "operator="(T* p) +# | object.Assign(T*) # In the same way you can import function with a different name, this one # imports a static method Create() while adding a prefix "CefSome_": # | cdef extern from "..": @@ -390,9 +390,14 @@ from cef_types cimport ( int32, uint32, int64, uint64, ) +# noinspection PyUnresolvedReferences +from cef_ptr cimport CefRefPtr + +# noinspection PyUnresolvedReferences +from cef_scoped_ptr cimport scoped_ptr + from cef_task cimport * from cef_platform cimport * -from cef_ptr cimport * from cef_app cimport * from cef_browser cimport * # noinspection PyUnresolvedReferences @@ -428,9 +433,6 @@ from cef_path_util cimport * from cef_drag_data cimport * from cef_image cimport * from main_message_loop cimport * -# noinspection PyUnresolvedReferences -from cef_scoped_ptr cimport scoped_ptr - # ----------------------------------------------------------------------------- # GLOBAL VARIABLES @@ -445,6 +447,12 @@ g_debugFile = "debug.log" g_applicationSettings = {"string_encoding": "utf-8"} g_commandLineSwitches = {} +# If ApplicationSettings.unique_request_context_per_browser is False +# then a shared request context is used for all browsers. Otherwise +# a unique one is created for each call to CreateBrowserSync. +# noinspection PyUnresolvedReferences +cdef CefRefPtr[CefRequestContext] g_shared_request_context + cdef scoped_ptr[MainMessageLoopExternalPump] g_external_message_pump cdef py_bool g_MessageLoop_called = False @@ -452,12 +460,6 @@ cdef py_bool g_MessageLoopWork_called = False cdef dict g_globalClientCallbacks = {} -# If ApplicationSettings.unique_request_context_per_browser is False -# then a shared request context is used for all browsers. Otherwise -# a unique one is created for each call to CreateBrowserSync. -# noinspection PyUnresolvedReferences -cdef CefRefPtr[CefRequestContext] g_sharedRequestContext - # ----------------------------------------------------------------------------- include "utils.pyx" @@ -735,7 +737,8 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): # External message pump if GetAppSetting("external_message_pump")\ and not g_external_message_pump.get(): - g_external_message_pump.Assign(MainMessageLoopExternalPump.Create()) + g_external_message_pump.reset( + MainMessageLoopExternalPump.Create().get()) Debug("CefInitialize()") cdef cpp_bool ret @@ -786,7 +789,6 @@ def CreateBrowserSync(windowInfo=None, cdef CefWindowInfo cefWindowInfo SetCefWindowInfo(cefWindowInfo, windowInfo) - navigateUrl = GetNavigateUrl(navigateUrl) Debug("navigateUrl: %s" % navigateUrl) cdef CefString cefNavigateUrl @@ -798,7 +800,7 @@ def CreateBrowserSync(windowInfo=None, cdef CefRefPtr[CefBrowser] cefBrowser # Request context - part 1/2. - createSharedRequestContext = bool(not g_sharedRequestContext.get()) + createSharedRequestContext = bool(not g_shared_request_context.get()) cdef CefRefPtr[CefRequestContext] cefRequestContext cdef CefRefPtr[RequestContextHandler] requestContextHandler =\ new RequestContextHandler( @@ -807,15 +809,13 @@ def CreateBrowserSync(windowInfo=None, cefRequestContext = CefRequestContext.CreateContext( CefRequestContext.GetGlobalContext(), requestContextHandler) + elif createSharedRequestContext: + cefRequestContext = CefRequestContext.CreateContext( + CefRequestContext.GetGlobalContext(), + requestContextHandler) + g_shared_request_context.Assign(cefRequestContext.get()) else: - if createSharedRequestContext: - cefRequestContext = CefRequestContext.CreateContext( - CefRequestContext.GetGlobalContext(), - \ - requestContextHandler) - g_sharedRequestContext.Assign(cefRequestContext.get()) - else: - cefRequestContext.Assign(g_sharedRequestContext.get()) + cefRequestContext.Assign(g_shared_request_context.get()) # CEF browser creation. with nogil: @@ -881,11 +881,12 @@ def QuitMessageLoop(): CefQuitMessageLoop() def Shutdown(): - if g_sharedRequestContext.get(): - # A similar release is done in RemovePyBrowser and CloseBrowser. - # This one is probably redundant. Additional testing should be done. - Debug("Shutdown: releasing shared request context") - g_sharedRequestContext.Assign(NULL) + Debug("Shutdown()") + + # Release shared request context. This is sometimes causing + # segmentation fault, so disabling it for now. See Issue #333: + # https://github.com/cztomczak/cefpython/issues/333 + # OFF: g_shared_request_context.Assign(NULL) # Run some message loop work, force closing browsers and then run # some message loop work again for the browsers to close cleanly. @@ -968,7 +969,7 @@ def Shutdown(): NonCriticalError("Shutdown called, but there are still browser" " references alive") - Debug("Shutdown()") + Debug("CefShutdown()") with nogil: CefShutdown() diff --git a/src/extern/cef/cef_ptr.pxd b/src/extern/cef/cef_ptr.pxd index ed4cdcae9..c5d9877cf 100644 --- a/src/extern/cef/cef_ptr.pxd +++ b/src/extern/cef/cef_ptr.pxd @@ -10,8 +10,8 @@ cdef extern from "include/internal/cef_ptr.h": # noinspection PyUnresolvedReferences CefRefPtr(const CefRefPtr[T]& r) # noinspection PyUnresolvedReferences - CefRefPtr[T]& Assign "operator="(T* p) # cefBrowser.Assign(CefBrowser*) - # noinspection PyUnresolvedReferences T* get() # noinspection PyUnresolvedReferences void swap(CefRefPtr[T]& r) + # noinspection PyUnresolvedReferences + CefRefPtr[T]& Assign "operator="(T* p) diff --git a/src/extern/cef/cef_scoped_ptr.pxd b/src/extern/cef/cef_scoped_ptr.pxd index f23cd156b..4498360cd 100644 --- a/src/extern/cef/cef_scoped_ptr.pxd +++ b/src/extern/cef/cef_scoped_ptr.pxd @@ -10,6 +10,6 @@ cdef extern from "include/base/cef_scoped_ptr.h": # noinspection PyUnresolvedReferences void reset() # noinspection PyUnresolvedReferences - T* get() + void reset(T* p) # noinspection PyUnresolvedReferences - scoped_ptr[T]& Assign "operator="(scoped_ptr[T]& p) + T* get() From c0a218ba1f44e6cef175a236545bebd7a8ab570e Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sun, 19 Mar 2017 19:59:48 +0100 Subject: [PATCH 020/398] Updated examples' README file - add wxpython-response.py example --- examples/Examples-README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 337004d79..d7bb153cb 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -40,6 +40,11 @@ maintained. see [gist by AnishN](https://gist.github.com/AnishN/aa3bb27fc9d69319955ed9a8973cd40f) - Screenshot example: see [gist by stefanbacon](https://gist.github.com/stefanbacon/7b1571d57aee54aa9f8e9021b4848d06) +- Example of implementing [ResourceHandler](../api/ResourceHandler.md) + with the use of [WebRequest](../api/WebRequest.md) object and + [WebRequestClient](../api/WebRequestClient.md) interface to allow + for reading/modifying web requests: see the [wxpython-response.py](https://github.com/cztomczak/cefpython/blob/cefpython31/cefpython/cef3/linux/binaries_64bit/wxpython-response.py) + example in the cefpython31 branch. - Old PyWin32 example: see [pywin32.py](https://github.com/cztomczak/cefpython/blob/cefpython31/cefpython/cef3/windows/binaries_32bit/pywin32.py) in the cefpython31 branch From c5951b67bd59014f249f75912db153c8a3b3aadf Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 09:23:22 +0100 Subject: [PATCH 021/398] Provide default implementation for js and file dialogs on Linux (#241)... In wxpython.py and gtk2.py examples the js alert dialog when run from a popup gives focus to the main window. The popup window is created internally by CEF and this probably needs to be changed so that popups are created by wx in OnBeforePopup and this will resolve issue with alert focus. In qt4.py and tkinter.py and hello_world.py examples focus works fine in the popup window. Add --hello-world flag to build.py and run_examples.py tools. --- src/client_handler/Makefile | 4 +- src/client_handler/client_handler.cpp | 1 + src/client_handler/client_handler.h | 8 + src/client_handler/dialog_handler.cpp | 37 ++ src/client_handler/dialog_handler.h | 38 ++ src/client_handler/dialog_handler_gtk.cpp | 464 ++++++++++++++++++++++ src/client_handler/dialog_handler_gtk.h | 59 +++ src/client_handler/js_dialog_handler.cpp | 32 +- src/client_handler/js_dialog_handler.h | 12 +- src/compile_time_constants.pxi | 2 +- src/handlers/lifespan_handler.pyx | 9 + src/window_utils_mac.pyx | 4 + src/window_utils_win.pyx | 4 + tools/build.py | 22 +- tools/run_examples.py | 24 +- 15 files changed, 704 insertions(+), 16 deletions(-) create mode 100644 src/client_handler/dialog_handler.cpp create mode 100644 src/client_handler/dialog_handler.h create mode 100644 src/client_handler/dialog_handler_gtk.cpp create mode 100644 src/client_handler/dialog_handler_gtk.h diff --git a/src/client_handler/Makefile b/src/client_handler/Makefile index 4465d28b5..13586c8ed 100644 --- a/src/client_handler/Makefile +++ b/src/client_handler/Makefile @@ -12,7 +12,7 @@ UNAME_S = $(shell uname -s) CCFLAGS = -fPIC $(CEF_CCFLAGS) ifeq ($(UNAME_S), Linux) - SRC_MORE = x11.cpp + SRC_MORE = x11.cpp dialog_handler_gtk.cpp else ifeq ($(UNAME_S), Darwin) SRC_MORE = util_mac.mm endif @@ -22,7 +22,7 @@ SRC = client_handler.cpp cookie_visitor.cpp resource_handler.cpp \ task.cpp context_menu_handler.cpp display_handler.cpp \ download_handler.cpp focus_handler.cpp js_dialog_handler.cpp \ keyboard_handler.cpp lifespan_handler.cpp load_handler.cpp \ - render_handler.cpp request_handler.cpp \ + render_handler.cpp request_handler.cpp dialog_handler.cpp \ $(SRC_MORE) OBJ = $(filter %.o, $(SRC:.cpp=.o) $(SRC:.mm=.o)) diff --git a/src/client_handler/client_handler.cpp b/src/client_handler/client_handler.cpp index d8f6189e6..f14fbaf85 100644 --- a/src/client_handler/client_handler.cpp +++ b/src/client_handler/client_handler.cpp @@ -24,6 +24,7 @@ // CefClient // ---------------------------------------------------------------------------- + bool ClientHandler::OnProcessMessageReceived( CefRefPtr browser, CefProcessId source_process, diff --git a/src/client_handler/client_handler.h b/src/client_handler/client_handler.h index 33c125072..4a5c29b7c 100644 --- a/src/client_handler/client_handler.h +++ b/src/client_handler/client_handler.h @@ -13,6 +13,7 @@ #include "common/cefpython_public_api.h" #include "context_menu_handler.h" +#include "dialog_handler.h" #include "display_handler.h" #include "download_handler.h" #include "focus_handler.h" @@ -26,6 +27,7 @@ class ClientHandler : public CefClient, public ContextMenuHandler, + public DialogHandler, public DisplayHandler, public DownloadHandler, public FocusHandler, @@ -44,6 +46,12 @@ class ClientHandler : public CefClient, return this; } +#if defined(OS_LINUX) + CefRefPtr GetDialogHandler() override { + return this; + } +#endif + CefRefPtr GetDisplayHandler() override { return this; } diff --git a/src/client_handler/dialog_handler.cpp b/src/client_handler/dialog_handler.cpp new file mode 100644 index 000000000..ab90de9b6 --- /dev/null +++ b/src/client_handler/dialog_handler.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2017 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#include "dialog_handler.h" + + +DialogHandler::DialogHandler() +{ +#if defined(OS_LINUX) + // Provide the GTK-based default dialog implementation on Linux. + dialog_handler_ = new ClientDialogHandlerGtk(); +#endif +} + + +bool DialogHandler::OnFileDialog(CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) +{ +#if defined(OS_LINUX) + return dialog_handler_->OnFileDialog(browser, + mode, + title, + default_file_path, + accept_filters, + selected_accept_filter, + callback); +#else + return false; +#endif + +} diff --git a/src/client_handler/dialog_handler.h b/src/client_handler/dialog_handler.h new file mode 100644 index 000000000..21d79a60d --- /dev/null +++ b/src/client_handler/dialog_handler.h @@ -0,0 +1,38 @@ +// Copyright (c) 2017 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#pragma once + +#include "common/cefpython_public_api.h" +#include "include/cef_dialog_handler.h" + +#if defined(OS_LINUX) +#include "dialog_handler_gtk.h" +#endif + + +class DialogHandler : public CefDialogHandler +{ +public: + DialogHandler(); + virtual ~DialogHandler(){} + + bool OnFileDialog(CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) + override; + +public: +#if defined(OS_LINUX) + // Default dialog handler impl for GTK. + CefRefPtr dialog_handler_; +#endif + +private: + IMPLEMENT_REFCOUNTING(DialogHandler); +}; diff --git a/src/client_handler/dialog_handler_gtk.cpp b/src/client_handler/dialog_handler_gtk.cpp new file mode 100644 index 000000000..1db132907 --- /dev/null +++ b/src/client_handler/dialog_handler_gtk.cpp @@ -0,0 +1,464 @@ +// Default dialog handler implementation on Linux. +// Copied from upstream cefclient with changes: +// - Rewrote GetWindow() func +// - Removed "client" namespace +// - Changed titles of JS alerts, removed URL and "Javascript" word + +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include +#include +#include +#include + +#include "include/cef_browser.h" +#include "include/cef_parser.h" +#include "include/wrapper/cef_helpers.h" + +#include "dialog_handler_gtk.h" +#include "x11.h" + +namespace { + +const char kPromptTextId[] = "cef_prompt_text"; + +// If there's a text entry in the dialog, get the text from the first one and +// return it. +std::string GetPromptText(GtkDialog* dialog) { + GtkWidget* widget = static_cast( + g_object_get_data(G_OBJECT(dialog), kPromptTextId)); + if (widget) + return gtk_entry_get_text(GTK_ENTRY(widget)); + return std::string(); +} + +std::string GetDescriptionFromMimeType(const std::string& mime_type) { + // Check for wild card mime types and return an appropriate description. + static const struct { + const char* mime_type; + const char* label; + } kWildCardMimeTypes[] = { + { "audio", "Audio Files" }, + { "image", "Image Files" }, + { "text", "Text Files" }, + { "video", "Video Files" }, + }; + + for (size_t i = 0; + i < sizeof(kWildCardMimeTypes) / sizeof(kWildCardMimeTypes[0]); ++i) { + if (mime_type == std::string(kWildCardMimeTypes[i].mime_type) + "/*") + return std::string(kWildCardMimeTypes[i].label); + } + + return std::string(); +} + +void AddFilters(GtkFileChooser* chooser, + const std::vector& accept_filters, + bool include_all_files, + std::vector* filters) { + bool has_filter = false; + + for (size_t i = 0; i < accept_filters.size(); ++i) { + const std::string& filter = accept_filters[i]; + if (filter.empty()) + continue; + + std::vector extensions; + std::string description; + + size_t sep_index = filter.find('|'); + if (sep_index != std::string::npos) { + // Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3". + description = filter.substr(0, sep_index); + + const std::string& exts = filter.substr(sep_index + 1); + size_t last = 0; + size_t size = exts.size(); + for (size_t i = 0; i <= size; ++i) { + if (i == size || exts[i] == ';') { + std::string ext(exts, last, i - last); + if (!ext.empty() && ext[0] == '.') + extensions.push_back(ext); + last = i + 1; + } + } + } else if (filter[0] == '.') { + // Treat as an extension beginning with the '.' character. + extensions.push_back(filter); + } else { + // Otherwise convert mime type to one or more extensions. + description = GetDescriptionFromMimeType(filter); + + std::vector ext; + CefGetExtensionsForMimeType(filter, ext); + for (size_t x = 0; x < ext.size(); ++x) + extensions.push_back("." + ext[x].ToString()); + } + + if (extensions.empty()) + continue; + + GtkFileFilter* gtk_filter = gtk_file_filter_new(); + + std::string ext_str; + for (size_t x = 0; x < extensions.size(); ++x) { + const std::string& pattern = "*" + extensions[x]; + if (x != 0) + ext_str += ";"; + ext_str += pattern; + gtk_file_filter_add_pattern(gtk_filter, pattern.c_str()); + } + + if (description.empty()) + description = ext_str; + else + description += " (" + ext_str + ")"; + + gtk_file_filter_set_name(gtk_filter, description.c_str()); + gtk_file_chooser_add_filter(chooser, gtk_filter); + if (!has_filter) + has_filter = true; + + filters->push_back(gtk_filter); + } + + // Add the *.* filter, but only if we have added other filters (otherwise it + // is implied). + if (include_all_files && has_filter) { + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*"); + gtk_file_filter_set_name(filter, "All Files (*)"); + gtk_file_chooser_add_filter(chooser, filter); + } +} + +GtkWindow* GetWindow(CefRefPtr browser) { + // -- REWRITTEN FOR CEF PYTHON USE CASE -- + // X11 window handle + ::Window xwindow = browser->GetHost()->GetWindowHandle(); + // X11 display + ::Display* xdisplay = cef_get_xdisplay(); + // GDK display + GdkDisplay* gdk_display = NULL; + if (xdisplay) { + // See if we can find GDK display using X11 display + gdk_display = gdk_x11_lookup_xdisplay(xdisplay); + } + if (!gdk_display) { + // If not then get the default display + gdk_display = gdk_display_get_default(); + } + if (!gdk_display) { + // The tkinter_.py and hello_world.py examples do not use GTK + // internally, so GTK wasn't yet initialized and must do it + // now, so that display is available. Also must install X11 + // error handlers to avoid 'BadWindow' errors. + gtk_init(0, NULL); + InstallX11ErrorHandlers(); + // Now the display is available + gdk_display = gdk_display_get_default(); + } + // In kivy_.py example getting error message: + // > Can't create GtkPlug as child of non-GtkSocket + // However dialog handler works just fine. + GtkWidget* widget = gtk_plug_new_for_display(gdk_display, xwindow); + // Getting top level widget doesn't seem to be required. + // OFF: GtkWidget* toplevel = gtk_widget_get_toplevel(widget); + GtkWindow* window = GTK_WINDOW(widget); + if (!window) { + LOG(ERROR) << "No GtkWindow for browser"; + } + return window; +} + +} // namespace + + +ClientDialogHandlerGtk::ClientDialogHandlerGtk() + : gtk_dialog_(NULL) { +} + +bool ClientDialogHandlerGtk::OnFileDialog( + CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) { + std::vector files; + + GtkFileChooserAction action; + const gchar* accept_button; + + // Remove any modifier flags. + FileDialogMode mode_type = + static_cast(mode & FILE_DIALOG_TYPE_MASK); + + if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) { + action = GTK_FILE_CHOOSER_ACTION_OPEN; + accept_button = GTK_STOCK_OPEN; + } else if (mode_type == FILE_DIALOG_OPEN_FOLDER) { + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + accept_button = GTK_STOCK_OPEN; + } else if (mode_type == FILE_DIALOG_SAVE) { + action = GTK_FILE_CHOOSER_ACTION_SAVE; + accept_button = GTK_STOCK_SAVE; + } else { + NOTREACHED(); + return false; + } + + std::string title_str; + if (!title.empty()) { + title_str = title; + } else { + switch (mode_type) { + case FILE_DIALOG_OPEN: + title_str = "Open File"; + break; + case FILE_DIALOG_OPEN_MULTIPLE: + title_str = "Open Files"; + break; + case FILE_DIALOG_OPEN_FOLDER: + title_str = "Open Folder"; + break; + case FILE_DIALOG_SAVE: + title_str = "Save File"; + break; + default: + break; + } + } + + GtkWindow* window = GetWindow(browser); + if (!window) + return false; + + GtkWidget* dialog = gtk_file_chooser_dialog_new( + title_str.c_str(), + GTK_WINDOW(window), + action, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + accept_button, GTK_RESPONSE_ACCEPT, + NULL); + + if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + + if (mode_type == FILE_DIALOG_SAVE) { + gtk_file_chooser_set_do_overwrite_confirmation( + GTK_FILE_CHOOSER(dialog), + !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG)); + } + + gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), + !(mode & FILE_DIALOG_HIDEREADONLY_FLAG)); + + if (!default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) { + const std::string& file_path = default_file_path; + bool exists = false; + + struct stat sb; + if (stat(file_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) { + // Use the directory and name of the existing file. + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), + file_path.data()); + exists = true; + } + + if (!exists) { + // Set the current file name but let the user choose the directory. + std::string file_name_str = file_path; + const char* file_name = basename(const_cast(file_name_str.data())); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), file_name); + } + } + + std::vector filters; + AddFilters(GTK_FILE_CHOOSER(dialog), accept_filters, true, &filters); + if (selected_accept_filter < static_cast(filters.size())) { + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), + filters[selected_accept_filter]); + } + + bool success = false; + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_FOLDER || + mode_type == FILE_DIALOG_SAVE) { + char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + files.push_back(std::string(filename)); + success = true; + } else if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) { + GSList* filenames = + gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + if (filenames) { + for (GSList* iter = filenames; iter != NULL; + iter = g_slist_next(iter)) { + std::string path(static_cast(iter->data)); + g_free(iter->data); + files.push_back(path); + } + g_slist_free(filenames); + success = true; + } + } + } + + int filter_index = selected_accept_filter; + if (success) { + GtkFileFilter* selected_filter = + gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); + if (selected_filter != NULL) { + for (size_t x = 0; x < filters.size(); ++x) { + if (filters[x] == selected_filter) { + filter_index = x; + break; + } + } + } + } + + gtk_widget_destroy(dialog); + + if (success) + callback->Continue(filter_index, files); + else + callback->Cancel(); + + return true; +} + +bool ClientDialogHandlerGtk::OnJSDialog( + CefRefPtr browser, + const CefString& origin_url, + JSDialogType dialog_type, + const CefString& message_text, + const CefString& default_prompt_text, + CefRefPtr callback, + bool& suppress_message) { + CEF_REQUIRE_UI_THREAD(); + + GtkButtonsType buttons = GTK_BUTTONS_NONE; + GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER; + std::string title; + + switch (dialog_type) { + case JSDIALOGTYPE_ALERT: + buttons = GTK_BUTTONS_NONE; + gtk_message_type = GTK_MESSAGE_WARNING; + title = "Alert"; + break; + + case JSDIALOGTYPE_CONFIRM: + buttons = GTK_BUTTONS_CANCEL; + gtk_message_type = GTK_MESSAGE_QUESTION; + title = "Confirm"; + break; + + case JSDIALOGTYPE_PROMPT: + buttons = GTK_BUTTONS_CANCEL; + gtk_message_type = GTK_MESSAGE_QUESTION; + title = "Prompt"; + break; + } + + js_dialog_callback_ = callback; + + if (!origin_url.empty()) { + // title += " - "; + // title += CefFormatUrlForSecurityDisplay(origin_url).ToString(); + } + + GtkWindow* window = GetWindow(browser); + if (!window) + return false; + + gtk_dialog_ = gtk_message_dialog_new(GTK_WINDOW(window), + GTK_DIALOG_MODAL, + gtk_message_type, + buttons, + "%s", + message_text.ToString().c_str()); + g_signal_connect(gtk_dialog_, + "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), + NULL); + + gtk_window_set_title(GTK_WINDOW(gtk_dialog_), title.c_str()); + + GtkWidget* ok_button = gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_), + GTK_STOCK_OK, + GTK_RESPONSE_OK); + + if (dialog_type != JSDIALOGTYPE_PROMPT) + gtk_widget_grab_focus(ok_button); + + if (dialog_type == JSDIALOGTYPE_PROMPT) { + GtkWidget* content_area = + gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_)); + GtkWidget* text_box = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(text_box), + default_prompt_text.ToString().c_str()); + gtk_box_pack_start(GTK_BOX(content_area), text_box, TRUE, TRUE, 0); + g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box); + gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE); + } + + gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK); + g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this); + gtk_widget_show_all(GTK_WIDGET(gtk_dialog_)); + + return true; +} + +bool ClientDialogHandlerGtk::OnBeforeUnloadDialog( + CefRefPtr browser, + const CefString& message_text, + bool is_reload, + CefRefPtr callback) { + CEF_REQUIRE_UI_THREAD(); + + const std::string& new_message_text = + message_text.ToString() + "\n\nIs it OK to leave/reload this page?"; + bool suppress_message = false; + + return OnJSDialog(browser, CefString(), JSDIALOGTYPE_CONFIRM, + new_message_text, CefString(), callback, suppress_message); +} + +void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + if (!gtk_dialog_) + return; + gtk_widget_destroy(gtk_dialog_); + gtk_dialog_ = NULL; + js_dialog_callback_ = NULL; +} + +// static +void ClientDialogHandlerGtk::OnDialogResponse(GtkDialog* dialog, + gint response_id, + ClientDialogHandlerGtk* handler) { + CEF_REQUIRE_UI_THREAD(); + + DCHECK_EQ(dialog, GTK_DIALOG(handler->gtk_dialog_)); + switch (response_id) { + case GTK_RESPONSE_OK: + handler->js_dialog_callback_->Continue(true, GetPromptText(dialog)); + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + handler->js_dialog_callback_->Continue(false, CefString()); + break; + default: + NOTREACHED(); + } + + handler->OnResetDialogState(NULL); +} diff --git a/src/client_handler/dialog_handler_gtk.h b/src/client_handler/dialog_handler_gtk.h new file mode 100644 index 000000000..aba4857ae --- /dev/null +++ b/src/client_handler/dialog_handler_gtk.h @@ -0,0 +1,59 @@ +// Default dialog handler implementation on Linux. +// Copied from upstream cefclient with changes: +// - Removed "client" namespace + +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_DIALOG_HANDLER_GTK_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_DIALOG_HANDLER_GTK_H_ +#pragma once + +#include + +#include "include/cef_dialog_handler.h" +#include "include/cef_jsdialog_handler.h" + +class ClientDialogHandlerGtk : public CefDialogHandler, + public CefJSDialogHandler { + public: + ClientDialogHandlerGtk(); + + // CefDialogHandler methods. + bool OnFileDialog(CefRefPtr browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, + CefRefPtr callback) OVERRIDE; + + // CefJSDialogHandler methods. + bool OnJSDialog(CefRefPtr browser, + const CefString& origin_url, + JSDialogType dialog_type, + const CefString& message_text, + const CefString& default_prompt_text, + CefRefPtr callback, + bool& suppress_message) OVERRIDE; + bool OnBeforeUnloadDialog( + CefRefPtr browser, + const CefString& message_text, + bool is_reload, + CefRefPtr callback) OVERRIDE; + void OnResetDialogState(CefRefPtr browser) OVERRIDE; + + private: + static void OnDialogResponse(GtkDialog *dialog, + gint response_id, + ClientDialogHandlerGtk* handler); + + GtkWidget* gtk_dialog_; + CefRefPtr js_dialog_callback_; + + IMPLEMENT_REFCOUNTING(ClientDialogHandlerGtk); + DISALLOW_COPY_AND_ASSIGN(ClientDialogHandlerGtk); +}; + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_DIALOG_HANDLER_GTK_H_ diff --git a/src/client_handler/js_dialog_handler.cpp b/src/client_handler/js_dialog_handler.cpp index a1f641a0c..3d3ecd09f 100644 --- a/src/client_handler/js_dialog_handler.cpp +++ b/src/client_handler/js_dialog_handler.cpp @@ -4,6 +4,14 @@ #include "js_dialog_handler.h" +JSDialogHandler::JSDialogHandler() +{ +#if defined(OS_LINUX) + // Provide the GTK-based default dialog implementation on Linux. + dialog_handler_ = new ClientDialogHandlerGtk(); +#endif +} + bool JSDialogHandler::OnJSDialog(CefRefPtr browser, const CefString& origin_url, @@ -14,11 +22,18 @@ bool JSDialogHandler::OnJSDialog(CefRefPtr browser, bool& suppress_message) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnJavascriptDialog( + bool ret = JavascriptDialogHandler_OnJavascriptDialog( browser, origin_url, dialog_type, message_text, default_prompt_text, callback, suppress_message); + if (!ret) { + // Default implementation + return dialog_handler_->OnJSDialog(browser, origin_url, dialog_type, + message_text, default_prompt_text, + callback, suppress_message); + } + return ret; } @@ -29,21 +44,30 @@ bool JSDialogHandler::OnBeforeUnloadDialog( CefRefPtr callback) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( + bool ret = JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( browser, message_text, is_reload, callback); + if (!ret) { + // Default implementation + return dialog_handler_->OnBeforeUnloadDialog(browser, message_text, + is_reload, callback); + } + return ret; } void JSDialogHandler::OnResetDialogState(CefRefPtr browser) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnResetJavascriptDialogState(browser); + // Default implementation + dialog_handler_->OnResetDialogState(browser); + // User implementation + JavascriptDialogHandler_OnResetJavascriptDialogState(browser); } void JSDialogHandler::OnDialogClosed(CefRefPtr browser) { REQUIRE_UI_THREAD(); - return JavascriptDialogHandler_OnJavascriptDialogClosed(browser); + JavascriptDialogHandler_OnJavascriptDialogClosed(browser); } diff --git a/src/client_handler/js_dialog_handler.h b/src/client_handler/js_dialog_handler.h index 6d3ce3e48..43fdf2514 100644 --- a/src/client_handler/js_dialog_handler.h +++ b/src/client_handler/js_dialog_handler.h @@ -5,11 +5,15 @@ #include "common/cefpython_public_api.h" #include "include/cef_jsdialog_handler.h" +#if defined(OS_LINUX) +#include "dialog_handler_gtk.h" +#endif + class JSDialogHandler : public CefJSDialogHandler { public: - JSDialogHandler(){} + JSDialogHandler(); virtual ~JSDialogHandler(){} typedef cef_jsdialog_type_t JSDialogType; @@ -31,6 +35,12 @@ class JSDialogHandler : public CefJSDialogHandler void OnResetDialogState(CefRefPtr browser) override; void OnDialogClosed(CefRefPtr browser) override; +public: +#if defined(OS_LINUX) + // Default dialog handler impl for GTK. + CefRefPtr dialog_handler_; +#endif + private: IMPLEMENT_REFCOUNTING(JSDialogHandler); }; diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 47dedaecd..35f850027 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 3 +DEF PY_MAJOR_VERSION = 2 diff --git a/src/handlers/lifespan_handler.pyx b/src/handlers/lifespan_handler.pyx index ad6730b1b..32a3f0c46 100644 --- a/src/handlers/lifespan_handler.pyx +++ b/src/handlers/lifespan_handler.pyx @@ -113,6 +113,15 @@ cdef public void LifespanHandler_OnBeforeClose( cdef object callback try: Debug("LifespanHandler_OnBeforeClose") + # NOTE: browser_id may not necessarily be in g_pyBrowsers currently. + # I haven't yet debugged it but the logic in Shutdown that + # tries to force close browsers and removes references might + # have something to do with it. Such scenario is reproducible + # with the following steps: + # 1. Run wxpython.py example + # 2. Google "js alert" and open w3schools + # 3. Open demo popup + # 4. Close main window (not popup) pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeClose") callback = pyBrowser.GetClientCallback("OnBeforeClose") if callback: diff --git a/src/window_utils_mac.pyx b/src/window_utils_mac.pyx index 07c70b64d..d683daf81 100644 --- a/src/window_utils_mac.pyx +++ b/src/window_utils_mac.pyx @@ -37,3 +37,7 @@ class WindowUtils: def IsWindowHandle(WindowHandle windowHandle): Debug("WindowUtils::IsWindowHandle() not implemented (always True)") return True + + @staticmethod + def InstallX11ErrorHandlers(): + pass diff --git a/src/window_utils_win.pyx b/src/window_utils_win.pyx index a1b613878..0dd9ee70b 100644 --- a/src/window_utils_win.pyx +++ b/src/window_utils_win.pyx @@ -145,3 +145,7 @@ class WindowUtils(object): return bool(IsWindow(windowHandle)) ELSE: return False + + @staticmethod + def InstallX11ErrorHandlers(): + pass diff --git a/tools/build.py b/tools/build.py index 9b6db3518..7debfffcb 100644 --- a/tools/build.py +++ b/tools/build.py @@ -19,6 +19,7 @@ Usage: build.py VERSION [--rebuild-cpp] [--fast] [--clean] [--kivy] + [--hello-world] Options: VERSION Version number eg. 50.0 @@ -26,6 +27,7 @@ --fast Fast mode --clean Clean C++ projects build files on Linux/Mac --kivy Run only Kivy example + --hello-world Run only Hello World example """ # --rebuild-cpp Force rebuild of .vcproj C++ projects (DISABLED) @@ -70,6 +72,7 @@ FAST_FLAG = False CLEAN_FLAG = False KIVY_FLAG = False +HELLO_WORLD_FLAG = False REBUILD_CPP = False # First run @@ -107,7 +110,7 @@ def main(): def command_line_args(): - global DEBUG_FLAG, FAST_FLAG, CLEAN_FLAG, KIVY_FLAG,\ + global DEBUG_FLAG, FAST_FLAG, CLEAN_FLAG, KIVY_FLAG, HELLO_WORLD_FLAG, \ REBUILD_CPP, VERSION, NO_RUN_EXAMPLES VERSION = get_version_from_command_line_args(__file__) @@ -141,7 +144,12 @@ def command_line_args(): # --kivy if "--kivy" in sys.argv: KIVY_FLAG = True - print("[build.py] KIVY mode enabled") + print("[build.py] KIVY example") + + # --kivy + if "--hello-world" in sys.argv: + HELLO_WORLD_FLAG = True + print("[build.py] HELLO WORLD example") # --rebuild-cpp # Rebuild c++ projects @@ -848,12 +856,16 @@ def install_and_run(): if not NO_RUN_EXAMPLES: print("[build.py] Run examples") os.chdir(EXAMPLES_DIR) - kivy_flag = "--kivy" if KIVY_FLAG else "" + flags = "" + if KIVY_FLAG: + flags += " --kivy" + if HELLO_WORLD_FLAG: + flags += " --hello-world" run_examples = os.path.join(TOOLS_DIR, "run_examples.py") - command = ("\"{python}\" {run_examples} {kivy_flag}" + command = ("\"{python}\" {run_examples} {flags}" .format(python=sys.executable, run_examples=run_examples, - kivy_flag=kivy_flag)) + flags=flags)) ret = os.system(command) if ret != 0: print("[build.py] ERROR while running examples") diff --git a/tools/run_examples.py b/tools/run_examples.py index 6562af669..a4a1f0bbb 100644 --- a/tools/run_examples.py +++ b/tools/run_examples.py @@ -20,6 +20,19 @@ def main(): os.chdir(EXAMPLES_DIR) + # When importing Kivy package there can't be any flags unknown to Kivy, + # use sys.argv.remove to remove them. + + kivy_flag = False + if "--kivy" in sys.argv: + sys.argv.remove("--kivy") + kivy_flag = True + + hello_world_flag = False + if "--hello-world" in sys.argv: + sys.argv.remove("--hello-world") + hello_world_flag = True + packages = check_installed_packages() examples = list() examples.append("hello_world.py") @@ -83,15 +96,20 @@ def main(): print(["run_examples.py] PASS: tkinter_.py (tkinter not installed)"]) passed.append("tkinter_.py") - # kivy if LINUX and packages["kivy"] and packages["gtk"]: - if "--kivy" in sys.argv: - # When --kivy flag passed run only Kivy example + # When --kivy flag passed run only Kivy example + if kivy_flag: examples = list() passed = list() examples.append("{linux_dir}/binaries_64bit/kivy_.py" .format(linux_dir=LINUX_DIR)) + # When --hello-world flag is passed run only hello_world.py example + if hello_world_flag: + examples = list() + passed = list() + examples.append("hello_world.py") + # Run all for example in examples: print("[run_examples.py] Running '{example}'..." From b93cebfbcfa750f4047d6c243e90517e53a4708c Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 09:30:28 +0100 Subject: [PATCH 022/398] Fix 'BadWindow' x11 errors in wxpython.py example (#334) --- examples/wxpython.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/wxpython.py b/examples/wxpython.py index 144bab7ed..bc5ccf495 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -64,6 +64,12 @@ def __init__(self): title='wxPython example', size=(WIDTH, HEIGHT)) self.browser = None + # Must ignore X11 errors like 'BadWindow' and others by + # installing X11 error handlers. This must be done after + # wx was intialized. + if LINUX: + WindowUtils.InstallX11ErrorHandlers() + global g_count_windows g_count_windows += 1 From eb732d977c7addcfb692f5b099d72c0d4c71810b Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 10:49:58 +0100 Subject: [PATCH 023/398] Rename setup installer's filename and other cleanup... Delete old tools on Linux. New tools are in tools/ root dir. Delete deprecated examples on Linux and Windows. --- docs/Build-instructions.md | 12 +- src/.gitignore | 42 - .../binaries_64bit/deprecated/example.html | 58 -- src/linux/binaries_64bit/deprecated/prism.css | 130 --- src/linux/binaries_64bit/deprecated/prism.js | 5 - src/linux/binaries_64bit/deprecated/pygtk_.py | 220 ----- src/linux/binaries_64bit/deprecated/pyqt.py | 238 ----- .../deprecated/wxpython-response.py | 452 --------- .../binaries_64bit/deprecated/wxpython.html | 707 -------------- .../binaries_64bit/deprecated/wxpython.py | 769 --------------- src/linux/compile.py | 325 ------- .../.gitignore | 0 .../debian.postinst | 0 .../deps.txt | 0 .../find-deps.py | 0 .../make-deb.py | 0 .../stdeb.cfg.template | 0 src/linux/installer/README.txt | 20 - src/linux/installer/__init__.py.template | 40 - src/linux/installer/make-setup.py | 222 ----- src/linux/installer/setup.cfg.template | 2 - src/linux/installer/setup.py.template | 104 -- src/linux/setup/.gitignore | 7 - src/linux/setup/cefpython.h | 103 -- src/linux/setup/fix_pyx_files.py | 131 --- src/linux/setup/setup.py | 156 --- src/windows/deprecated_32bit/LICENSE.txt | 39 - src/windows/deprecated_32bit/README.txt | 119 --- src/windows/deprecated_32bit/cefwindow.py | 205 ---- src/windows/deprecated_32bit/example.html | 58 -- src/windows/deprecated_32bit/icon.ico | Bin 198275 -> 0 bytes src/windows/deprecated_32bit/prism.css | 130 --- src/windows/deprecated_32bit/prism.js | 5 - src/windows/deprecated_32bit/pygtk_.py | 196 ---- src/windows/deprecated_32bit/pyqt.py | 190 ---- src/windows/deprecated_32bit/pyside.py | 188 ---- src/windows/deprecated_32bit/pywin32.py | 151 --- src/windows/deprecated_32bit/smoke.css | 110 --- src/windows/deprecated_32bit/smoke.min.js | 1 - src/windows/deprecated_32bit/wxpython.html | 743 -------------- src/windows/deprecated_32bit/wxpython.py | 906 ------------------ tools/common.py | 2 +- 42 files changed, 7 insertions(+), 6779 deletions(-) delete mode 100644 src/linux/binaries_64bit/deprecated/example.html delete mode 100644 src/linux/binaries_64bit/deprecated/prism.css delete mode 100644 src/linux/binaries_64bit/deprecated/prism.js delete mode 100644 src/linux/binaries_64bit/deprecated/pygtk_.py delete mode 100644 src/linux/binaries_64bit/deprecated/pyqt.py delete mode 100644 src/linux/binaries_64bit/deprecated/wxpython-response.py delete mode 100644 src/linux/binaries_64bit/deprecated/wxpython.html delete mode 100644 src/linux/binaries_64bit/deprecated/wxpython.py delete mode 100644 src/linux/compile.py rename src/linux/{installer => deb_pkg_deprecated}/.gitignore (100%) rename src/linux/{installer => deb_pkg_deprecated}/debian.postinst (100%) rename src/linux/{installer => deb_pkg_deprecated}/deps.txt (100%) rename src/linux/{installer => deb_pkg_deprecated}/find-deps.py (100%) rename src/linux/{installer => deb_pkg_deprecated}/make-deb.py (100%) rename src/linux/{installer => deb_pkg_deprecated}/stdeb.cfg.template (100%) delete mode 100644 src/linux/installer/README.txt delete mode 100644 src/linux/installer/__init__.py.template delete mode 100644 src/linux/installer/make-setup.py delete mode 100644 src/linux/installer/setup.cfg.template delete mode 100644 src/linux/installer/setup.py.template delete mode 100644 src/linux/setup/.gitignore delete mode 100644 src/linux/setup/cefpython.h delete mode 100644 src/linux/setup/fix_pyx_files.py delete mode 100644 src/linux/setup/setup.py delete mode 100644 src/windows/deprecated_32bit/LICENSE.txt delete mode 100644 src/windows/deprecated_32bit/README.txt delete mode 100644 src/windows/deprecated_32bit/cefwindow.py delete mode 100644 src/windows/deprecated_32bit/example.html delete mode 100644 src/windows/deprecated_32bit/icon.ico delete mode 100644 src/windows/deprecated_32bit/prism.css delete mode 100644 src/windows/deprecated_32bit/prism.js delete mode 100644 src/windows/deprecated_32bit/pygtk_.py delete mode 100644 src/windows/deprecated_32bit/pyqt.py delete mode 100644 src/windows/deprecated_32bit/pyside.py delete mode 100644 src/windows/deprecated_32bit/pywin32.py delete mode 100644 src/windows/deprecated_32bit/smoke.css delete mode 100644 src/windows/deprecated_32bit/smoke.min.js delete mode 100644 src/windows/deprecated_32bit/wxpython.html delete mode 100644 src/windows/deprecated_32bit/wxpython.py diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index f9009799e..3281810eb 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -404,15 +404,15 @@ cd cefpython/build/ python ../tools/make_installer.py xx.x ``` -To create a wheel package from that installer package type: +To create a wheel package type: ``` -cd *-setup/ -python setup.py bdist_wheel -cd dist/ -ls *.whl +cd cefpython/build/ +python ../tools/make_installer.py xx.xx --wheel --universal +cd cefpython3_*/dist/ +ls ``` -Optional flags for the setup.py script above: +Additional flags when using --wheel flag: * `--python-tag cp27` to generate Python 2.7 only package * `--universal` to build package for multiple Python versions (in such case you must first build multiple cefpython modules diff --git a/src/.gitignore b/src/.gitignore index 7bc2904b7..c794c5a00 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,46 +1,4 @@ -/cef1/windows/setup/build/ -*.pyc - # If you want to ignore an already commited directory run: # git rm --cached -r .idea -/.idea/ -debug.log -error.log -console.log - -# WingIDE project files -/wingide.wpr -/wingide.wpu - -*.ncb -*.suo -*.user -*.aps -*.sdf -*.sln -Debug*/ -Release*/ -*.pdb - -ctags - -/windows/installer/Output/ - -# Inno setup files generated from a template -*.generated - -cython_debug/ - -cefpython_py27.pyd -cefpython_py32.pyd -cefpython_py27.so -cefpython_py32.so - compile_time_constants.pxi - -debug_32bit/ -debug_64bit/ - -libcef_debug/ -libcef_release/ diff --git a/src/linux/binaries_64bit/deprecated/example.html b/src/linux/binaries_64bit/deprecated/example.html deleted file mode 100644 index 891bc5e8c..000000000 --- a/src/linux/binaries_64bit/deprecated/example.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - CEF Python 3 example (utf-8: ąś) - - - - - - -

Use mouse context menu to go Back/Forward in history navigation.

- - - -

Google Search

-https://www.google.com/ - - - -

User agent

- - - - -

Popup

- - window.open('example.html') - - - -

HTML5 video and accelerated content

- -HTML 5 video
- -Accelerated canvas
- -Accelerated layers
- - - -

Advanced example

- -See the wxpython.py script for an advanced usage of CEF Python 3 -features - including javascript bindings, js and python callbacks, -client handlers and others. - - -



-



-



-



- - - diff --git a/src/linux/binaries_64bit/deprecated/prism.css b/src/linux/binaries_64bit/deprecated/prism.css deleted file mode 100644 index f94cca7c0..000000000 --- a/src/linux/binaries_64bit/deprecated/prism.css +++ /dev/null @@ -1,130 +0,0 @@ -/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+python */ -/** - * prism.js default theme for JavaScript, CSS and HTML - * Based on dabblet (http://dabblet.com) - * @author Lea Verou - */ - -code[class*="language-"], -pre[class*="language-"] { - color: black; - text-shadow: 0 1px white; - font-family: Consolas, Monaco, 'Andale Mono', monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, -code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { - text-shadow: none; - background: #b3d4fc; -} - -pre[class*="language-"]::selection, pre[class*="language-"] ::selection, -code[class*="language-"]::selection, code[class*="language-"] ::selection { - text-shadow: none; - background: #b3d4fc; -} - -@media print { - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #f5f2f0; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.builtin { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string, -.token.variable { - color: #a67f59; - background: hsla(0,0%,100%,.5); -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - -.token.function { - color: #DD4A68; -} - -.token.regex, -.token.important { - color: #e90; -} - -.token.important { - font-weight: bold; -} - -.token.entity { - cursor: help; -} - diff --git a/src/linux/binaries_64bit/deprecated/prism.js b/src/linux/binaries_64bit/deprecated/prism.js deleted file mode 100644 index ebaa4b428..000000000 --- a/src/linux/binaries_64bit/deprecated/prism.js +++ /dev/null @@ -1,5 +0,0 @@ -/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+python */ -self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content)):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(f instanceof r)){l.lastIndex=0;var h=l.exec(f);if(h){c&&(g=h[1].length);var d=h.index-1+g,h=h[0].slice(g),p=h.length,m=d+p,v=f.slice(0,d+1),y=f.slice(m+1),k=[u,1];v&&k.push(v);var b=new r(o,s?t.tokenize(h,s):h);k.push(b),y&&k.push(y),Array.prototype.splice.apply(a,k)}}}}return a},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[],r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(r&&r.length)for(var a,i=0;a=r[i++];)a(n)}}},n=t.Token=function(e,t){this.type=e,this.content=t};if(n.stringify=function(e,r,a){if("string"==typeof e)return e;if("[object Array]"==Object.prototype.toString.call(e))return e.map(function(t){return n.stringify(t,r,e)}).join("");var i={type:e.type,content:n.stringify(e.content,r,a),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:a};"comment"==i.type&&(i.attributes.spellcheck="true"),t.hooks.run("wrap",i);var o="";for(var l in i.attributes)o+=l+'="'+(i.attributes[l]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,a=n.code;self.postMessage(JSON.stringify(t.tokenize(a,t.languages[r]))),self.close()},!1),self.Prism):self.Prism;var r=document.getElementsByTagName("script");return r=r[r.length-1],r&&(t.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; -Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/gi,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/gi,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/gi,inside:{tag:{pattern:/|<\/script>/gi,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; -Prism.languages.python={comment:{pattern:/(^|[^\\])#.*?(\r?\n|$)/g,lookbehind:!0},string:/"""[\s\S]+?"""|("|')(\\?.)*?\1/g,keyword:/\b(as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|with|yield)\b/g,"boolean":/\b(True|False)\b/g,number:/\b-?(0x)?\d*\.?[\da-f]+\b/g,operator:/[-+]{1,2}|=?<|=?>|!|={1,2}|(&){1,2}|(&){1,2}|\|?\||\?|\*|\/|~|\^|%|\b(or|and|not)\b/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; diff --git a/src/linux/binaries_64bit/deprecated/pygtk_.py b/src/linux/binaries_64bit/deprecated/pygtk_.py deleted file mode 100644 index 14ec01e08..000000000 --- a/src/linux/binaries_64bit/deprecated/pygtk_.py +++ /dev/null @@ -1,220 +0,0 @@ -# An example of embedding the CEF browser in PyGTK on Linux. -# Tested with GTK "2.24.10". - -# The official CEF Python binaries come with tcmalloc hook -# disabled. But if you've built custom binaries and kept tcmalloc -# hook enabled, then be aware that in such case it is required -# for the cefpython module to be the very first import in -# python scripts. See Issue 73 in the CEF Python Issue Tracker -# for more details. - -import ctypes, os, sys -libcef_so = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.so') -if os.path.exists(libcef_so): - # Import local module - ctypes.CDLL(libcef_so, ctypes.RTLD_GLOBAL) - if 0x02070000 <= sys.hexversion < 0x03000000: - import cefpython_py27 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import from package - from cefpython3 import cefpython - -import pygtk -pygtk.require('2.0') -import gtk -import gobject -import re - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (_exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[pygtk_.py]: WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class PyGTKExample: - mainWindow = None - container = None - browser = None - exiting = None - searchEntry = None - vbox = None - menubar = None - menubar_height = None - - def __init__(self): - self.mainWindow = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.mainWindow.connect('focus-in-event', self.OnFocusIn) - self.mainWindow.connect('configure-event', self.OnConfigure) - self.mainWindow.connect('destroy', self.OnExit) - self.mainWindow.set_size_request(width=800, height=600) - self.mainWindow.set_title('PyGTK CEF example') - self.mainWindow.realize() - - self.vbox = gtk.VBox(False, 0) - self.vbox.connect('size-allocate', self.OnVBoxSize) - self.menubar = self.CreateMenu() - self.menubar.connect('size-allocate', self.OnMenubarSize) - self.vbox.pack_start(self.menubar, False, False, 0) - self.mainWindow.add(self.vbox) - - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(self.mainWindow.window.xid, [0,0,0,0]) - # Linux requires adding "file://" for local files, - # otherwise /home/some will be replaced as http://home/some - self.browser = cefpython.CreateBrowserSync( - windowInfo, - browserSettings={}, - navigateUrl="file://"+GetApplicationPath("example.html")) - - self.vbox.show() - self.mainWindow.show() - self.vbox.get_window().focus() - self.mainWindow.get_window().focus() - gobject.timeout_add(10, self.OnTimer) - - def CreateMenu(self): - file = gtk.MenuItem('File') - file.show() - filemenu = gtk.Menu() - item = gtk.MenuItem('Open') - filemenu.append(item) - item.show() - item = gtk.MenuItem('Exit') - filemenu.append(item) - item.show() - file.set_submenu(filemenu) - - about = gtk.MenuItem('About') - about.show() - aboutmenu = gtk.Menu() - item = gtk.MenuItem('CEF Python') - aboutmenu.append(item) - item.show() - about.set_submenu(aboutmenu) - - menubar = gtk.MenuBar() - menubar.append(file) - menubar.append(about) - menubar.show() - - return menubar - - # count = 0 - def OnTimer(self): - if self.exiting: - return False - # self.count += 1 - # print(self.count) - cefpython.MessageLoopWork() - return True - - def OnFocusIn(self, widget, data): - if self.browser: - self.browser.SetFocus(True) - return True - return False - - def OnConfigure(self, widget, data): - if self.browser: - self.browser.NotifyMoveOrResizeStarted() - return False - - def OnVBoxSize(self, widget, data): - if self.browser: - x = data.x - y = data.y + self.menubar_height - width = data.width - height = data.height - self.menubar_height - self.browser.SetBounds(x, y, width, height) - - def OnMenubarSize(self, widget, data): - self.menubar_height = data.height - - def OnExit(self, widget, data=None): - self.exiting = True - gtk.main_quit() - -if __name__ == '__main__': - version = '.'.join(map(str, list(gtk.gtk_version))) - print('[pygtk_.py] GTK version: %s' % version) - - # Intercept python exceptions. Exit app immediately when exception - # happens on any of the threads. - sys.excepthook = ExceptHook - - # Application settings - settings = { - "debug": True, # cefpython debug messages in console and in log_file - "log_severity": cefpython.LOGSEVERITY_INFO, # LOGSEVERITY_VERBOSE - "log_file": GetApplicationPath("debug.log"), # Set to "" to disable - # This directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess"), - } - - cefpython.Initialize(settings) - cefpython.WindowUtils.InstallX11ErrorHandlers() - - gobject.threads_init() # Timer for the message loop - PyGTKExample() - gtk.main() - - cefpython.Shutdown() diff --git a/src/linux/binaries_64bit/deprecated/pyqt.py b/src/linux/binaries_64bit/deprecated/pyqt.py deleted file mode 100644 index c43805754..000000000 --- a/src/linux/binaries_64bit/deprecated/pyqt.py +++ /dev/null @@ -1,238 +0,0 @@ -# An example of embedding CEF browser in a PyQt4 application. -# Tested with PyQt "4.9.1". -# Command for installing PyQt4: "sudo apt-get install python-qt4". - -# The official CEF Python binaries come with tcmalloc hook -# disabled. But if you've built custom binaries and kept tcmalloc -# hook enabled, then be aware that in such case it is required -# for the cefpython module to be the very first import in -# python scripts. See Issue 73 in the CEF Python Issue Tracker -# for more details. - -import ctypes, os, sys -libcef_so = os.path.join(os.path.dirname(os.path.abspath(__file__)),\ - 'libcef.so') -if os.path.exists(libcef_so): - # Import local module - ctypes.CDLL(libcef_so, ctypes.RTLD_GLOBAL) - if 0x02070000 <= sys.hexversion < 0x03000000: - import cefpython_py27 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import from package - from cefpython3 import cefpython - -from PyQt4 import QtGui -from PyQt4 import QtCore - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[pyqt.py] WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class MainWindow(QtGui.QMainWindow): - mainFrame = None - - def __init__(self): - super(MainWindow, self).__init__(None) - self.createMenu() - self.mainFrame = MainFrame(self) - self.setCentralWidget(self.mainFrame) - self.resize(1024, 768) - self.setWindowTitle('PyQT CEF 3 example') - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - def createMenu(self): - menubar = self.menuBar() - filemenu = menubar.addMenu("&File") - filemenu.addAction(QtGui.QAction("Open", self)) - filemenu.addAction(QtGui.QAction("Exit", self)) - aboutmenu = menubar.addMenu("&About") - - def focusInEvent(self, event): - # cefpython.WindowUtils.OnSetFocus( - # int(self.centralWidget().winId()), 0, 0, 0) - pass - - def closeEvent(self, event): - self.mainFrame.browser.CloseBrowser() - -class MainFrame(QtGui.QX11EmbedContainer): - browser = None - plug = None - - def __init__(self, parent=None): - super(MainFrame, self).__init__(parent) - - # QX11EmbedContainer provides an X11 window. The CEF - # browser can be embedded only by providing a GtkWidget - # pointer. So we're embedding a GtkPlug inside the X11 - # window. In latest CEF trunk it is possible to embed - # the CEF browser by providing X11 window id. So it will - # be possible to remove the GTK dependency from CEF - # Python in the future. - gtkPlugPtr = cefpython.WindowUtils.gtk_plug_new(\ - int(self.winId())) - print("[pyqt.py] MainFrame: GDK Native Window id: "+str(self.winId())) - print("[pyqt.py] MainFrame: GTK Plug ptr: "+str(gtkPlugPtr)) - - """ - Embedding GtkPlug is also possible with the pygtk module. - --------------------------------------------------------- - self.plug = gtk.Plug(self.winId()) - import re - m = re.search("GtkPlug at 0x(\w+)", str(self.plug)) - hexId = m.group(1) - gtkPlugPtr = int(hexId, 16) - ... - plug.show() - self.show() - --------------------------------------------------------- - """ - - windowInfo = cefpython.WindowInfo() - # Need to pass to CEF the GtkWidget* pointer - windowInfo.SetAsChild(gtkPlugPtr) - # Linux requires adding "file://" for local files, - # otherwise /home/some will be replaced as http://home/some - self.browser = cefpython.CreateBrowserSync(windowInfo, - browserSettings={}, - navigateUrl="file://"+GetApplicationPath("example.html")) - - cefpython.WindowUtils.gtk_widget_show(gtkPlugPtr) - self.show() - - def moveEvent(self, event): - # cefpython.WindowUtils.OnSize(int(self.winId()), 0, 0, 0) - pass - - def resizeEvent(self, event): - # cefpython.WindowUtils.OnSize(int(self.winId()), 0, 0, 0) - pass - -class CefApplication(QtGui.QApplication): - timer = None - - def __init__(self, args): - super(CefApplication, self).__init__(args) - self.createTimer() - - def createTimer(self): - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.onTimer) - self.timer.start(10) - - def onTimer(self): - # The proper way of doing message loop should be: - # 1. In createTimer() call self.timer.start(0) - # 2. In onTimer() call MessageLoopWork() only when - # QtGui.QApplication.instance()->hasPendingEvents() - # returns False. - # But there is a bug in Qt, hasPendingEvents() returns - # always true. - # (The behavior described above was tested on Windows - # with pyqt 4.8, maybe this is not true anymore, - # test it TODO) - cefpython.MessageLoopWork() - - def stopTimer(self): - # Stop the timer after Qt message loop ended, calls to - # MessageLoopWork() should not happen anymore. - self.timer.stop() - -if __name__ == '__main__': - print("[pyqt.py] PyQt version: %s" % QtCore.PYQT_VERSION_STR) - print("[pyqt.py] QtCore version: %s" % QtCore.qVersion()) - - # Intercept python exceptions. Exit app immediately when exception - # happens on any of the threads. - sys.excepthook = ExceptHook - - # Application settings - settings = { - "debug": True, # cefpython debug messages in console and in log_file - "log_severity": cefpython.LOGSEVERITY_INFO, # LOGSEVERITY_VERBOSE - "log_file": GetApplicationPath("debug.log"), # Set to "" to disable. - # This directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess"), - } - - # Command line switches set programmatically - switches = { - # "proxy-server": "socks5://127.0.0.1:8888", - # "enable-media-stream": "", - # "--invalid-switch": "" -> Invalid switch name - } - - cefpython.Initialize(settings, switches) - - app = CefApplication(sys.argv) - mainWindow = MainWindow() - mainWindow.show() - app.exec_() - app.stopTimer() - - # Need to destroy QApplication(), otherwise Shutdown() fails. - # Unset main window also just to be safe. - del mainWindow - del app - - cefpython.Shutdown() diff --git a/src/linux/binaries_64bit/deprecated/wxpython-response.py b/src/linux/binaries_64bit/deprecated/wxpython-response.py deleted file mode 100644 index 8992e14a2..000000000 --- a/src/linux/binaries_64bit/deprecated/wxpython-response.py +++ /dev/null @@ -1,452 +0,0 @@ -# An example of embedding CEF browser in wxPython on Linux. -# Tested with wxPython 2.8.12.1 (gtk2-unicode). -# To install wxPython type "sudo apt-get install python-wxtools". - -# This example implements a custom "_OnResourceResponse" callback -# that emulates reading response by utilizing Resourcehandler -# and WebRequest. - -FIX_ENCODING_BUG = True -BROWSER_DEFAULT_ENCODING = "utf-8" - -# The official CEF Python binaries come with tcmalloc hook -# disabled. But if you've built custom binaries and kept tcmalloc -# hook enabled, then be aware that in such case it is required -# for the cefpython module to be the very first import in -# python scripts. See Issue 73 in the CEF Python Issue Tracker -# for more details. - -import ctypes, os, sys -libcef_so = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'libcef.so') -if os.path.exists(libcef_so): - # Import local module - ctypes.CDLL(libcef_so, ctypes.RTLD_GLOBAL) - if 0x02070000 <= sys.hexversion < 0x03000000: - import cefpython_py27 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import from package - from cefpython3 import cefpython - -import wx -import time -import re -import uuid -import platform -import shutil - -class ClientHandler: - - # RequestHandler.GetResourceHandler() - def GetResourceHandler(self, browser, frame, request): - # Called on the IO thread before a resource is loaded. - # To allow the resource to load normally return None. - print("GetResourceHandler(): url = %s" % request.GetUrl()) - resHandler = ResourceHandler() - resHandler._clientHandler = self - resHandler._browser = browser - resHandler._frame = frame - resHandler._request = request - self._AddStrongReference(resHandler) - return resHandler - - def _OnResourceResponse(self, browser, frame, request, requestStatus, - requestError, response, data): - # This callback is emulated through ResourceHandler - # and WebRequest. Real "OnResourceResponse" is not yet - # available in CEF 3 (as of CEF revision 1450). See - # issue 515 in the CEF Issue Tracker: - # https://code.google.com/p/chromiumembedded/issues/detail?id=515 - # ---- - # requestStatus => cefpython.WebRequest.Status - # {"Unknown", "Success", "Pending", "Canceled", "Failed"} - # For "file://" requests the status will be "Unknown". - # requestError => see the NetworkError wiki page - # response.GetStatus() => http status code - print("_OnResourceResponse()") - print("data length = %s" % len(data)) - # Return the new data - you can modify it. - if request.GetUrl().startswith("file://") \ - and request.GetUrl().endswith("example.html"): - data = "This text was inserted through " \ - + "_OnResourceResponse()
" + data - # Non-english characters are not displaying correctly. - # This is a bug in CEF. A quick fix is to get the charset - # from response headers and insert into - # the html page. - # Bug reported on the CEF C++ Forum: - # http://www.magpcss.org/ceforum/viewtopic.php?p=18401#p18401 - if FIX_ENCODING_BUG: - contentType = response.GetHeader("Content-Type") - if contentType: - contentType = contentType.lower() - isHtml = False - headerCharset = "" - if contentType and "text/html" in contentType: - isHtml = True - if contentType and "charset" in contentType: - match = re.search(r"charset\s*=\s*([^\s]+)", contentType) - if match and match.group(1): - headerCharset = match.group(1).lower() - if isHtml and headerCharset \ - and headerCharset != BROWSER_DEFAULT_ENCODING.lower(): - if not re.search(r"]+charset\s*=", data, \ - re.IGNORECASE): - # Only apply the fix if there is no - # available on a page. - data = ("" % headerCharset) + data - return data - - # A strong reference to ResourceHandler must be kept - # during the request. Some helper functions for that. - # 1. Add reference in GetResourceHandler() - # 2. Release reference in ResourceHandler.ReadResponse() - # after request is completed. - - _resourceHandlers = {} - _resourceHandlerMaxId = 0 - - def _AddStrongReference(self, resHandler): - self._resourceHandlerMaxId += 1 - resHandler._resourceHandlerId = self._resourceHandlerMaxId - self._resourceHandlers[resHandler._resourceHandlerId] = resHandler - - def _ReleaseStrongReference(self, resHandler): - if resHandler._resourceHandlerId in self._resourceHandlers: - del self._resourceHandlers[resHandler._resourceHandlerId] - else: - print("_ReleaseStrongReference() FAILED: resource handler " \ - "not found, id = %s" % (resHandler._resourceHandlerId)) - -class ResourceHandler: - - # The methods of this class will always be called - # on the IO thread. - - _resourceHandlerId = None - _clientHandler = None - _browser = None - _frame = None - _request = None - _responseHeadersReadyCallback = None - _webRequest = None - _webRequestClient = None - _offsetRead = 0 - - def ProcessRequest(self, request, callback): - print("ProcessRequest()") - # 1. Start the request using WebRequest - # 2. Return True to handle the request - # 3. Once response headers are ready call - # callback.Continue() - self._responseHeadersReadyCallback = callback - self._webRequestClient = WebRequestClient() - self._webRequestClient._resourceHandler = self - # Need to set AllowCacheCredentials and AllowCookies for - # the cookies to work during POST requests (Issue 127). - # To skip cache set the SkipCache request flag. - request.SetFlags(cefpython.Request.Flags["AllowCachedCredentials"]\ - | cefpython.Request.Flags["AllowCookies"]) - # A strong reference to the WebRequest object must kept. - self._webRequest = cefpython.WebRequest.Create( - request, self._webRequestClient) - return True - - def GetResponseHeaders(self, response, responseLengthOut, redirectUrlOut): - print("GetResponseHeaders()") - # 1. If the response length is not known set - # responseLengthOut[0] to -1 and ReadResponse() - # will be called until it returns False. - # 2. If the response length is known set - # responseLengthOut[0] to a positive value - # and ReadResponse() will be called until it - # returns False or the specified number of bytes - # have been read. - # 3. Use the |response| object to set the mime type, - # http status code and other optional header values. - # 4. To redirect the request to a new URL set - # redirectUrlOut[0] to the new url. - assert self._webRequestClient._response, "Response object empty" - wrcResponse = self._webRequestClient._response - response.SetStatus(wrcResponse.GetStatus()) - response.SetStatusText(wrcResponse.GetStatusText()) - response.SetMimeType(wrcResponse.GetMimeType()) - if wrcResponse.GetHeaderMultimap(): - response.SetHeaderMultimap(wrcResponse.GetHeaderMultimap()) - print("headers: ") - print(wrcResponse.GetHeaderMap()) - responseLengthOut[0] = self._webRequestClient._dataLength - if not responseLengthOut[0]: - # Probably a cached page? Or a redirect? - pass - - def ReadResponse(self, dataOut, bytesToRead, bytesReadOut, callback): - # print("ReadResponse()") - # 1. If data is available immediately copy up to - # bytesToRead bytes into dataOut[0], set - # bytesReadOut[0] to the number of bytes copied, - # and return true. - # 2. To read the data at a later time set - # bytesReadOut[0] to 0, return true and call - # callback.Continue() when the data is available. - # 3. To indicate response completion return false. - if self._offsetRead < self._webRequestClient._dataLength: - dataChunk = self._webRequestClient._data[\ - self._offsetRead:(self._offsetRead + bytesToRead)] - self._offsetRead += len(dataChunk) - dataOut[0] = dataChunk - bytesReadOut[0] = len(dataChunk) - return True - self._clientHandler._ReleaseStrongReference(self) - print("no more data, return False") - return False - - def CanGetCookie(self, cookie): - # Return true if the specified cookie can be sent - # with the request or false otherwise. If false - # is returned for any cookie then no cookies will - # be sent with the request. - return True - - def CanSetCookie(self, cookie): - # Return true if the specified cookie returned - # with the response can be set or false otherwise. - return True - - def Cancel(self): - # Request processing has been canceled. - pass - -class WebRequestClient: - - _resourceHandler = None - _data = "" - _dataLength = -1 - _response = None - - def OnUploadProgress(self, webRequest, current, total): - pass - - def OnDownloadProgress(self, webRequest, current, total): - pass - - def OnDownloadData(self, webRequest, data): - # print("OnDownloadData()") - self._data += data - - def OnRequestComplete(self, webRequest): - print("OnRequestComplete()") - # cefpython.WebRequest.Status = {"Unknown", "Success", - # "Pending", "Canceled", "Failed"} - statusText = "Unknown" - if webRequest.GetRequestStatus() in cefpython.WebRequest.Status: - statusText = cefpython.WebRequest.Status[\ - webRequest.GetRequestStatus()] - print("status = %s" % statusText) - print("error code = %s" % webRequest.GetRequestError()) - # Emulate OnResourceResponse() in ClientHandler: - self._response = webRequest.GetResponse() - # Are webRequest.GetRequest() and - # self._resourceHandler._request the same? What if - # there was a redirect, what will GetUrl() return - # for both of them? - self._data = self._resourceHandler._clientHandler._OnResourceResponse( - self._resourceHandler._browser, - self._resourceHandler._frame, - webRequest.GetRequest(), - webRequest.GetRequestStatus(), - webRequest.GetRequestError(), - webRequest.GetResponse(), - self._data) - self._dataLength = len(self._data) - # ResourceHandler.GetResponseHeaders() will get called - # after _responseHeadersReadyCallback.Continue() is called. - self._resourceHandler._responseHeadersReadyCallback.Continue() - -# Which method to use for message loop processing. -# EVT_IDLE - wx application has priority (default) -# EVT_TIMER - cef browser has priority -# It seems that Flash content behaves better when using a timer. -# IMPORTANT! On Linux EVT_IDLE does not work, the events seems to -# be propagated only when you move your mouse, which is not the -# expected behavior, it is recommended to use EVT_TIMER on Linux, -# so set this value to False. -USE_EVT_IDLE = False - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("cefpython: WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class MainFrame(wx.Frame): - browser = None - mainPanel = None - - def __init__(self): - wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, - title='wxPython CEF 3 example', size=(800,600)) - self.CreateMenu() - - # Cannot attach browser to the main frame as this will cause - # the menu not to work. - # -- - # You also have to set the wx.WANTS_CHARS style for - # all parent panels/controls, if it's deeply embedded. - self.mainPanel = wx.Panel(self, style=wx.WANTS_CHARS) - - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(self.mainPanel.GetGtkWidget()) - # Linux requires adding "file://" for local files, - # otherwise /home/some will be replaced as http://home/some - self.browser = cefpython.CreateBrowserSync( - windowInfo, - # If there are problems with Flash you can disable it here, - # by disabling all plugins. - browserSettings={"plugins_disabled": False, - "default_encoding": BROWSER_DEFAULT_ENCODING}, - navigateUrl="file://"+GetApplicationPath("example.html")) - - clientHandler = ClientHandler() - self.browser.SetClientHandler(clientHandler) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - if USE_EVT_IDLE: - # Bind EVT_IDLE only for the main application frame. - self.Bind(wx.EVT_IDLE, self.OnIdle) - - def CreateMenu(self): - filemenu = wx.Menu() - filemenu.Append(1, "Open") - exit = filemenu.Append(2, "Exit") - self.Bind(wx.EVT_MENU, self.OnClose, exit) - aboutmenu = wx.Menu() - aboutmenu.Append(1, "CEF Python") - menubar = wx.MenuBar() - menubar.Append(filemenu,"&File") - menubar.Append(aboutmenu, "&About") - self.SetMenuBar(menubar) - - def OnClose(self, event): - # In wx.chromectrl calling browser.CloseBrowser() and/or - # self.Destroy() in OnClose is causing crashes when embedding - # multiple browser tabs. The solution is to call only - # browser.ParentWindowWillClose. Behavior of this example - # seems different as it extends wx.Frame, while ChromeWindow - # from chromectrl extends wx.Window. Calling CloseBrowser - # and Destroy does not cause crashes, but is not recommended. - # Call ParentWindowWillClose and event.Skip() instead. See - # also Issue 107. - self.browser.ParentWindowWillClose() - event.Skip() - - def OnIdle(self, event): - cefpython.MessageLoopWork() - -class MyApp(wx.App): - timer = None - timerID = 1 - timerCount = 0 - - def OnInit(self): - if not USE_EVT_IDLE: - self.CreateTimer() - frame = MainFrame() - self.SetTopWindow(frame) - frame.Show() - return True - - def CreateTimer(self): - # See "Making a render loop": - # http://wiki.wxwidgets.org/Making_a_render_loop - # Another approach is to use EVT_IDLE in MainFrame, - # see which one fits you better. - self.timer = wx.Timer(self, self.timerID) - self.timer.Start(10) # 10ms - wx.EVT_TIMER(self, self.timerID, self.OnTimer) - - def OnTimer(self, event): - self.timerCount += 1 - # print("wxpython.py: OnTimer() %d" % self.timerCount) - cefpython.MessageLoopWork() - - def OnExit(self): - # When app.MainLoop() returns, MessageLoopWork() should - # not be called anymore. - if not USE_EVT_IDLE: - self.timer.Stop() - -if __name__ == '__main__': - sys.excepthook = ExceptHook - settings = { - "debug": False, # cefpython debug messages in console and in log_file - "log_severity": cefpython.LOGSEVERITY_INFO, # LOGSEVERITY_VERBOSE - "log_file": GetApplicationPath("debug.log"), # Set to "" to disable. - # This directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess") - } - # print("browser_subprocess_path="+settings["browser_subprocess_path"]) - cefpython.Initialize(settings) - print('wx.version=%s' % wx.version()) - app = MyApp(False) - app.MainLoop() - # Let wx.App destructor do the cleanup before calling cefpython.Shutdown(). - del app - cefpython.Shutdown() diff --git a/src/linux/binaries_64bit/deprecated/wxpython.html b/src/linux/binaries_64bit/deprecated/wxpython.html deleted file mode 100644 index caf056ff9..000000000 --- a/src/linux/binaries_64bit/deprecated/wxpython.html +++ /dev/null @@ -1,707 +0,0 @@ - - - - - wxPython CEF 3 example (utf-8: ąś) - - - - - - -Use the mouse context menu to go Back/Forward in history navigation. - -

Table of contents

-
    -
  1. Google search
  2. -
  3. User agent
  4. -
  5. Popups
  6. -
  7. HTML 5 video
  8. -
  9. Developer Tools
  10. -
  11. Downloads
  12. -
  13. HTML controls
  14. -
  15. Browser object
  16. -
  17. Frame object
  18. -
  19. Javascript bindings
  20. -
  21. Javascript callbacks
  22. -
  23. Python callbacks
  24. -
  25. Display handler
  26. -
  27. Keyboard handler
  28. -
  29. Request handler
  30. -
  31. Cookie tests
  32. -
  33. Load handler
  34. -
  35. Javascript Dialog handler
  36. -
  37. Other tests
  38. -
- - - - - - -

Google search

- -http://www.google.com/ - - - - - - - -

User agent

- - - - - - - - - -

Popups

- -
    -
  1. - window.open('wxpython.html') -
  2. -
  3. - target=_blank href="wxpython.html" -
  4. -
- -

CreateAnotherBrowser

- -This will create a window on its own and embed browser in it. -When using "window.open" the window is created implicitilly -by CEF. You can intercept such popup creation using the -OnBeforePopup callback in LifespanHandler. You can return -True in OnBeforePopup and create popup window on your own -using the CreateAnotherBrowser function. - - - - external.CreateAnotherBrowser() - - - - - - - -

HTML5 video and accelerated content

- - -HTML 5 video
- - -Accelerated canvas
- - -Accelerated layers
- - - - - - -

Developer Tools

- -You can open devtools popup window in a few different ways: -
    -
  1. Call Browser.ShowDevTools() method: - - external.ShowDevTools()
  2. -
  3. Through mouse context menu
  4. -
  5. Through F12 key which is handled in KeyboardHandler.OnKeyEvent
  6. -
- - - - - - -

Downloads

- -Download sample Ubuntu wallpapers:
- - https://cefpython.googlecode.com/files/ubuntu-wallpapers2.zip - -

-Notes: On Linux it seems that OnLoadError with errorCode = ERR_ABORTED -is called even for successful downloads, you can ignore this behavior. -The proper console messages about successful/aborted download originate -from C++ Browser process code, these are: -

- -
Browser: About to download file: ubuntu-wallpapers2.zip
-Browser: Download completed, saved to: /Downloads/ubuntu-wallpapers2.zip
-
- -If download was aborted the messages will be: - -
Browser: About to download file: ubuntu-wallpapers2.zip
-Browser: Download was cancelled
-
- -

-Additionally on Linux there are more errors reported by Chromium -about org.gnome.SessionManager.inhibit. These can be safely ignored as well. -

- -A download handler with callbacks like `OnBeforeDownload` and -`OnDownloadUpdated` may be exposed to CEF Python in the future. - - - - - - -

HTML controls

- -

Textarea

- -
- -

Inputs

-Text:
-Password:
- -

Select

- - -

Buttons

-Submit:
-Button:
- - - - - - -

Browser object

- -Tests for the Browser object methods. - -

GoBack

- -external.GoBack() - -

GoForward

- -external.GoForward() - -

LoadUrl, GetUrl

- - - window.open('data:text/html,Test#Browser.LoadUrl') - -

ReloadIgnoreCache, StopLoad

-Press F5 to reload page and ignore cache.
-Press Esc during webpage loading to abort.
- -Also, when Esc is pressed OnLoadError may get called. See how abort -of page loading or file download is handled: - - - - - - - -

Frame object

- -Tests for the Frame object methods. TODO. - - - - - - -

Javascript bindings

- -

PyPrint

- - - window.PyPrint('printing in python console from js') -
- -

Window properties

- -
jsBindings.SetProperty("pyProperty", "This was set in Python")
-jsBindings.SetProperty("pyConfig", ["This was set in Python",
-        {"name": "Nested dictionary", "isNested": True},
-        [1,"2", None]])
-
- - - window.alert(window.pyProperty)
- - window.alert(JSON.stringify(window.pyConfig)) -
- -

Print

- - - - external.Print('printing again from js') -
- -

TestAllTypes

- - - - external.TestAllTypes - (undefined, null, true, 1, - ((1<<31)>>>0), 2.14, 'Date not yet supported', 'string', - {key1: 1, key2: 2}, {key1: {'key1.1': 'nested object'}, 'key1.2': [1]}, - [1, 2], [1, [2.1, 'nested array']], [{key1: [{}]}]) -
- -

ExecuteFunction

- - - -
<script>
-function JavascriptAlert(message) { window.alert(message); }
-</script>
-
- - - - external.ExecuteFunction('JavascriptAlert', - 'python called from js and then js called from python') -
- -

GetSource, GetText

- - - - - - - external.GetSource() -
- - external.GetText() - - - - - - -

Javascript callbacks

- -

TestJSCallback

- - - -
<script>
-function JSCallback(arg1) {
-    window.alert(arg1)
-}
-</script>
-
- - - - external.TestJSCallback(JSCallback) - -

TestJSCallbackComplexArguments

- - - -
<script>
-function JSCallback2() {
-    window.alert(JSON.stringify(arguments))
-}
-</script>
-
- - - - external.TestJSCallbackComplexArguments({"myCallback": JSCallback2}) - - - - - - - -

Python callbacks

- -

TestPythonCallback

- - - -
<script>
-function JSCallback3(pyCallback) {
-    pyCallback(1, 2.14, "string", [1, [2, {"key": "value"}]], {"list": [1,2]});
-}
-</script>
-
- - - - - - external.TestPythonCallback(JSCallback3) - - - - - - -

Display handler

- -

OnAddressChange

- -See messages in the console during loading of a webpage. - -

OnTitleChange

- -See messages in the console during loading of a webpage. - -

OnTooltip

- -See messages in the console when hovering over a google logo: -http://www.google.com/ - -

OnStatusMessage

- -See messages in the console when hovering over links. - -

OnConsoleMessage

- -Try this: - - http://patik.com/code/console-log-polyfill/ - - - - - - -

Keyboard handler

- -

- Press F5 to reload the page.
- On Linux it is required to click anywhere in the window first - so that keyboard focus is set. See Issue 77 in the CEF Python - Issue Tracker. -

- - - - - - - - - -

Request handler

- -

OnBeforeResourceLoad

- -See messages in the console. - -

OnResourceRedirect

- -Try this: - - http://tinyurl.com/google404redirect - -

GetAuthCredentials

- -Try this: - - http://browserspy.dk/password-ok.php - -

OnQuotaRequest

- - - - -
<script>
-function DoRequestQuota() {
-    // Request Quota (only for File System API)  
-    try {
-        navigator.webkitPersistentStorage.requestQuota(PERSISTENT, 1024*1024,
-                function(bytes){ window.alert("Granted bytes: "+bytes);},
-                function(error){ window.alert(error); });
-    } catch(e) {
-        navigator.webkitPersistentStorage.requestQuota(1024*1024,
-                function(bytes){ window.alert("Granted bytes: "+bytes);},
-                function(error){ window.alert(error); });
-    }
-}
-</script>
-
- -Try this: - - https://googledrive.com/host/0B1di2XiBBfacMnhRRkI1YlotUEk/requestquota.html - -

OnProtocolExecution

- -Try this: - - magnet:?xt=urn:btih:a4224b45b27f436374391379cc5c7e629e2e5189 - -

_OnBeforePluginLoad

- -Try OnBeforePluginLoad() with Flash: - - http://www.adobe.com/software/flash/about/ - -

_OnCertificateError

- - -The url below won't be allowed. Click twice "Back" from context menu to return back -here after visiting the url:
- - https://tv.eurosport.com/do-not-allow -
- -This url will be allowed:
- - https://tv.eurosport.com/ - -

OnRendererProcessTerminated

- -Try to terminate the "subprocess.exe" renderer process through -task manager. - -

OnPluginCrashed

- -No test for that yet. - - - - - - -

Cookie tests

- -See messages in the console. - -

GetCookieManager

- - - -RequestHandler.GetCookieManager() - an example of having an unique -cookie manager for each browser. -
    -
  1. Visit the url below and set some cookies. Use "Back" from - context menu to get back here (you might have to click "Back" - multiple times).
    - Visit it in the current browser:
    - - http://www.html-kit.com/tools/cookietester/ -
    - Or visit it in a js popup:
    - - javascript:window.open('http://www.html-kit.com/tools/cookietester/') -
  2. -
  3. Open cookietester in a popup:
    - - javascript:external.CreateAnotherBrowser('http://www.html-kit.com/tools/cookietester/') -
  4. -
- -

-Popup browsers created javascript's window.open share -the same renderer process and request context. If you want -to have separate cookie managers for popups created using -window.open then you have to implement the -LifespanHandler.`OnBeforePopup` callback. Return True in that -callback to cancel popup creation and instead create the -window on your own and embed browser in it. The CreateAnotherBrowser() -function from the wxpython example does that. -

- -

VisitAllCookies

- -Visit all cookies: -external.VisitAllCookies() -

- -Note: visit some http:// webpage first, otherwise cookie manager is not -yet created. -
- -

VisitUrlCookies

- -Visit a subset of cookies for the given url: - - external.VisitUrlCookies("http://www.html-kit.com/tools/cookietester/") -
- -

SetCookie

- -Set a cookie: -external.SetCookie() -
- -

DeleteCookies

- -Delete the single cookie previously created via SetCookie(): - - external.DeleteCookies() -
- - - - - - -

Load Handler

- -See messages in the console during loading of a webpage. - -

OnLoadingStateChange

- - -

OnLoadStart

- - -

OnLoadEnd

- - -

OnLoadError

- -Try this: - - http://www.non-existent.nono/ -

- -Note: after you see the custom error message you have to hit -twice the Back from the context menu, to get back to this page. - - - - - - -

Javascript Dialog Handler

- -See messages in the console. - -

OnJavascriptDialog

- - - alert('Test js dialog handler') - - -

OnBeforeUnloadJavascriptDialog

- - - -
<script>
-function TestOnBeforeUnloadJavascriptDialog() {
-    window.onbeforeunload = function() {
-        return 'Testing the OnBeforeUnloadJavascriptDialog() callback';
-    }
-    location.href = "wxpython.html";
-}
-</script>
-
- - - TestOnBeforeUnloadJavascriptDialog() - - -

OnResetJavascriptDialogState

- - -

OnJavascriptDialogClosed

- - - - - - - -

Other tests

- -

HTTPS caching with SSL certificate errors

-Set ApplicationSettings["ignore_certificate_errors"] to True. - - - - - - - - - - - - - diff --git a/src/linux/binaries_64bit/deprecated/wxpython.py b/src/linux/binaries_64bit/deprecated/wxpython.py deleted file mode 100644 index 6dee3c150..000000000 --- a/src/linux/binaries_64bit/deprecated/wxpython.py +++ /dev/null @@ -1,769 +0,0 @@ -# An example of embedding CEF browser in wxPython on Linux. -# Tested with wxPython 2.8.12.1 (gtk2-unicode). -# To install wxPython type "sudo apt-get install python-wxtools". - -# The official CEF Python binaries come with tcmalloc hook -# disabled. But if you've built custom binaries and kept tcmalloc -# hook enabled, then be aware that in such case it is required -# for the cefpython module to be the very first import in -# python scripts. See Issue 73 in the CEF Python Issue Tracker -# for more details. - -import ctypes, os, sys -libcef_so = os.path.join(os.path.dirname(os.path.abspath(__file__)),\ - 'libcef.so') -if os.path.exists(libcef_so): - # Import a local module - ctypes.CDLL(libcef_so, ctypes.RTLD_GLOBAL) - if 0x02070000 <= sys.hexversion < 0x03000000: - import cefpython_py27 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython - -import wx -import time -import re -import uuid -import platform -import inspect - -g_browserSettings = None - -# Which method to use for message loop processing. -# EVT_IDLE - wx application has priority (default) -# EVT_TIMER - cef browser has priority -# It seems that Flash content behaves better when using a timer. -# IMPORTANT! On Linux EVT_IDLE does not work, the events seems to -# be propagated only when you move your mouse, which is not the -# expected behavior, it is recommended to use EVT_TIMER on Linux, -# so set this value to False. -USE_EVT_IDLE = False - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[wxpython.py] WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class MainFrame(wx.Frame): - browser = None - mainPanel = None - - def __init__(self, url=None): - wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, - title='wxPython CEF 3 example', size=(1024,768)) - if not url: - url = "file://"+GetApplicationPath("wxpython.html") - # Test hash in url. - # url += "#test-hash" - - self.CreateMenu() - - # Cannot attach browser to the main frame as this will cause - # the menu not to work. - # -- - # You also have to set the wx.WANTS_CHARS style for - # all parent panels/controls, if it's deeply embedded. - self.mainPanel = wx.Panel(self, style=wx.WANTS_CHARS) - - # Global client callbacks must be set before browser is created. - clientHandler = ClientHandler() - cefpython.SetGlobalClientCallback("OnCertificateError", - clientHandler._OnCertificateError) - cefpython.SetGlobalClientCallback("OnBeforePluginLoad", - clientHandler._OnBeforePluginLoad) - cefpython.SetGlobalClientCallback("OnAfterCreated", - clientHandler._OnAfterCreated) - - windowInfo = cefpython.WindowInfo() - # Must show window otherwise GetHandle() returns 0 - self.Show() - cefpython.WindowUtils.InstallX11ErrorHandlers() - windowInfo.SetAsChild(self.mainPanel.GetHandle(), - [0,0,0,0]) - # Linux requires adding "file://" for local files, - # otherwise /home/some will be replaced as http://home/some - self.browser = cefpython.CreateBrowserSync( - windowInfo, - # If there are problems with Flash you can disable it here, - # by disabling all plugins. - browserSettings=g_browserSettings, - navigateUrl=url) - - clientHandler.mainBrowser = self.browser - self.browser.SetClientHandler(clientHandler) - - jsBindings = cefpython.JavascriptBindings( - bindToFrames=False, bindToPopups=True) - jsBindings.SetFunction("PyPrint", PyPrint) - jsBindings.SetProperty("pyProperty", "This was set in Python") - jsBindings.SetProperty("pyConfig", ["This was set in Python", - {"name": "Nested dictionary", "isNested": True}, - [1,"2", None]]) - jsBindings.SetObject("external", JavascriptExternal(self.browser)) - jsBindings.SetProperty("sources", GetSources()) - self.browser.SetJavascriptBindings(jsBindings) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - if USE_EVT_IDLE: - # Bind EVT_IDLE only for the main application frame. - self.Bind(wx.EVT_IDLE, self.OnIdle) - - def CreateMenu(self): - filemenu = wx.Menu() - filemenu.Append(1, "Open") - exit = filemenu.Append(2, "Exit") - self.Bind(wx.EVT_MENU, self.OnClose, exit) - aboutmenu = wx.Menu() - aboutmenu.Append(1, "CEF Python") - menubar = wx.MenuBar() - menubar.Append(filemenu,"&File") - menubar.Append(aboutmenu, "&About") - self.SetMenuBar(menubar) - - def OnClose(self, event): - # In wx.chromectrl calling browser.CloseBrowser() and/or - # self.Destroy() in OnClose is causing crashes when embedding - # multiple browser tabs. The solution is to call only - # browser.ParentWindowWillClose. Behavior of this example - # seems different as it extends wx.Frame, while ChromeWindow - # from chromectrl extends wx.Window. Calling CloseBrowser - # and Destroy does not cause crashes, but is not recommended. - # Call ParentWindowWillClose and event.Skip() instead. See - # also Issue 107. - self.browser.ParentWindowWillClose() - event.Skip() - - def OnIdle(self, event): - cefpython.MessageLoopWork() - -def PyPrint(message): - print("[wxpython.py] PyPrint: "+message) - -class JavascriptExternal: - mainBrowser = None - stringVisitor = None - - def __init__(self, mainBrowser): - self.mainBrowser = mainBrowser - - def GoBack(self): - self.mainBrowser.GoBack() - - def GoForward(self): - self.mainBrowser.GoForward() - - def CreateAnotherBrowser(self, url=None): - """ - TODO: There are errors in the console when closing the window: - >> Check failed: window - >> Gdk: _gdk_window_destroy_hierarchy: assertion `GDK_IS_WINDOW\ - >> (window)' failed - >> GLib-GObject: g_object_unref: assertion `G_IS_OBJECT (object)' failed - """ - frame = MainFrame(url=url) - frame.Show() - - def Print(self, message): - print("[wxpython.py] Print: "+message) - - def TestAllTypes(self, *args): - print("[wxpython.py] TestAllTypes: "+str(args)) - - def ExecuteFunction(self, *args): - self.mainBrowser.GetMainFrame().ExecuteFunction(*args) - - def TestJSCallback(self, jsCallback): - print("[wxpython.py] jsCallback.GetFunctionName() = %s"\ - % jsCallback.GetFunctionName()) - print("[wxpython.py] jsCallback.GetFrame().GetIdentifier() = %s" % \ - jsCallback.GetFrame().GetIdentifier()) - jsCallback.Call("This message was sent from python using js callback") - - def TestJSCallbackComplexArguments(self, jsObject): - jsCallback = jsObject["myCallback"]; - jsCallback.Call(1, None, 2.14, "string", ["list", ["nested list", \ - {"nested object":None}]], \ - {"nested list next":[{"deeply nested object":1}]}) - - def TestPythonCallback(self, jsCallback): - jsCallback.Call(self.PyCallback) - - def PyCallback(self, *args): - message = "PyCallback() was executed successfully! "\ - "Arguments: %s" % str(args) - print("[wxpython.py] "+message) - self.mainBrowser.GetMainFrame().ExecuteJavascript( - "window.alert(\"%s\")" % message) - - def GetSource(self): - # Must keep a strong reference to the StringVisitor object - # during the visit. - self.stringVisitor = StringVisitor() - self.mainBrowser.GetMainFrame().GetSource(self.stringVisitor) - - def GetText(self): - # Must keep a strong reference to the StringVisitor object - # during the visit. - self.stringVisitor = StringVisitor() - self.mainBrowser.GetMainFrame().GetText(self.stringVisitor) - - def ShowDevTools(self): - print("[wxpython.py] external.ShowDevTools called") - self.mainBrowser.ShowDevTools() - - # ------------------------------------------------------------------------- - # Cookies - # ------------------------------------------------------------------------- - cookieVisitor = None - - def VisitAllCookies(self): - # Need to keep the reference alive. - self.cookieVisitor = CookieVisitor() - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - " the cookietester website first and create some cookies") - return - cookieManager.VisitAllCookies(self.cookieVisitor) - - def VisitUrlCookies(self): - # Need to keep the reference alive. - self.cookieVisitor = CookieVisitor() - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - " the cookietester website first and create some cookies") - return - cookieManager.VisitUrlCookies( - "http://www.html-kit.com/tools/cookietester/", - False, self.cookieVisitor) - # .www.html-kit.com - - def SetCookie(self): - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - "the cookietester website first and create some cookies") - return - cookie = cefpython.Cookie() - cookie.SetName("Created_Via_Python") - cookie.SetValue("yeah really") - cookieManager.SetCookie("http://www.html-kit.com/tools/cookietester/", - cookie) - print("\n[wxpython.py] Cookie created! Visit html-kit cookietester to"\ - " see it") - - def DeleteCookies(self): - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - " the cookietester website first and create some cookies") - return - cookieManager.DeleteCookies( - "http://www.html-kit.com/tools/cookietester/", - "Created_Via_Python") - print("\n[wxpython.py] Cookie deleted! Visit html-kit cookietester "\ - "to see the result") - -class StringVisitor: - def Visit(self, string): - print("\n[wxpython.py] StringVisitor.Visit(): string:") - print("--------------------------------") - print(string) - print("--------------------------------") - -class CookieVisitor: - def Visit(self, cookie, count, total, deleteCookie): - if count == 0: - print("\n[wxpython.py] CookieVisitor.Visit(): total cookies: %s"\ - % total) - print("\n[wxpython.py] CookieVisitor.Visit(): cookie:") - print(" "+str(cookie.Get())) - # True to continue visiting cookies - return True - -class ClientHandler: - mainBrowser = None # May be None for global client callbacks. - - def __init__(self): - pass - - # ------------------------------------------------------------------------- - # DisplayHandler - # ------------------------------------------------------------------------- - - def OnAddressChange(self, browser, frame, url): - print("[wxpython.py] DisplayHandler::OnAddressChange()") - print(" url = %s" % url) - - def OnTitleChange(self, browser, title): - print("[wxpython.py] DisplayHandler::OnTitleChange()") - print(" title = %s" % title) - - def OnTooltip(self, browser, textOut): - # OnTooltip not yet implemented (both Linux and Windows), - # will be fixed in next CEF release, see Issue 783: - # https://code.google.com/p/chromiumembedded/issues/detail?id=783 - print("[wxpython.py] DisplayHandler::OnTooltip()") - print(" text = %s" % textOut[0]) - - statusMessageCount = 0 - def OnStatusMessage(self, browser, value): - if not value: - # Do not notify in the console about empty statuses. - return - self.statusMessageCount += 1 - if self.statusMessageCount > 3: - # Do not spam too much. - return - print("[wxpython.py] DisplayHandler::OnStatusMessage()") - print(" value = %s" % value) - - def OnConsoleMessage(self, browser, message, source, line): - print("[wxpython.py] DisplayHandler::OnConsoleMessage()") - print(" message = %s" % message) - print(" source = %s" % source) - print(" line = %s" % line) - - # ------------------------------------------------------------------------- - # KeyboardHandler - # ------------------------------------------------------------------------- - - def OnPreKeyEvent(self, browser, event, eventHandle, - isKeyboardShortcutOut): - - stype = event["type"] - if stype == cefpython.KEYEVENT_RAWKEYDOWN: - stype = "RAWKEYDOWN" - elif stype == cefpython.KEYEVENT_KEYDOWN: - stype = "KEYDOWN" - elif stype == cefpython.KEYEVENT_KEYUP: - stype = "KEYUP" - elif stype == cefpython.KEYEVENT_CHAR: - stype = "CHAR" - - print("[wxpython.py] KeyboardHandler::OnPreKeyEvent()") - print(" type=%s" % stype) - print(" modifiers=%s" % event["modifiers"]) - print(" windows_key_code=%s" % event["windows_key_code"]) - print(" native_key_code=%s" % event["native_key_code"]) - print(" is_system_key=%s" % event["is_system_key"]) - print(" character=%s" % event["character"]) - print(" unmodified_character=%s" % event["unmodified_character"]) - print(" focus_on_editable_field=%s"\ - % event["focus_on_editable_field"]) - - def OnKeyEvent(self, browser, event, eventHandle): - pass - - # ------------------------------------------------------------------------- - # RequestHandler - # ------------------------------------------------------------------------- - - def OnBeforeBrowse(self, browser, frame, request, isRedirect): - print("[wxpython.py] RequestHandler::OnBeforeBrowse()") - print(" url = %s" % request.GetUrl()[:100]) - return False - - def OnBeforeResourceLoad(self, browser, frame, request): - print("[wxpython.py] RequestHandler::OnBeforeResourceLoad()") - print(" url = %s" % request.GetUrl()[:100]) - return False - - def OnResourceRedirect(self, browser, frame, oldUrl, newUrlOut, request): - print("[wxpython.py] RequestHandler::OnResourceRedirect()") - print(" old url = %s" % oldUrl[:100]) - print(" new url = %s" % newUrlOut[0][:100]) - - def GetAuthCredentials(self, browser, frame, isProxy, host, port, realm, - scheme, callback): - # This callback is called on the IO thread, thus print messages - # may not be visible. - print("[wxpython.py] RequestHandler::GetAuthCredentials()") - print(" host = %s" % host) - print(" realm = %s" % realm) - callback.Continue(username="test", password="test") - return True - - def OnQuotaRequest(self, browser, originUrl, newSize, callback): - print("[wxpython.py] RequestHandler::OnQuotaRequest()") - print(" origin url = %s" % originUrl) - print(" new size = %s" % newSize) - callback.Continue(True) - return True - - def GetCookieManager(self, browser, mainUrl): - # Create unique cookie manager for each browser. - # You must set the "unique_request_context_per_browser" - # application setting to True for the cookie manager - # to work. - # Return None to have one global cookie manager for - # all CEF browsers. - if not browser: - # The browser param may be empty in some exceptional - # case, see docs. - return None - cookieManager = browser.GetUserData("cookieManager") - if cookieManager: - return cookieManager - else: - print("[wxpython.py] RequestHandler::GetCookieManager():"\ - " created cookie manager") - cookieManager = cefpython.CookieManager.CreateManager("") - browser.SetUserData("cookieManager", cookieManager) - return cookieManager - - def OnProtocolExecution(self, browser, url, allowExecutionOut): - # There's no default implementation for OnProtocolExecution on Linux, - # you have to make OS system call on your own. You probably also need - # to use LoadHandler::OnLoadError() when implementing this on Linux. - print("[wxpython.py] RequestHandler::OnProtocolExecution()") - print(" url = %s" % url) - if url.startswith("magnet:"): - print("[wxpython.py] Magnet link allowed!") - allowExecutionOut[0] = True - - def _OnBeforePluginLoad(self, browser, mimeType, pluginUrl, topOriginUrl, - info): - # This is a global callback set using SetGlobalClientCallback(). - # Plugins are loaded on demand, only when website requires it, - # the same plugin may be called multiple times. - # This callback is called on various threads, thus print messages - # may not be visible. - print("[wxpython.py] RequestHandler::_OnBeforePluginLoad()") - print(" mimeType = %s" % mimeType) - print(" pluginUrl = %s" % pluginUrl) - print(" topOriginUrl = %s" % topOriginUrl) - print(" info.GetName() = %s" % info.GetName()) - print(" info.GetPath() = %s" % info.GetPath()) - print(" info.GetVersion() = %s" % info.GetVersion()) - print(" info.GetDescription() = %s" % info.GetDescription()) - # False to allow, True to block plugin. - return False - - def _OnCertificateError(self, certError, requestUrl, callback): - # This is a global callback set using SetGlobalClientCallback(). - print("[wxpython.py] RequestHandler::_OnCertificateError()") - print(" certError = %s" % certError) - print(" requestUrl = %s" % requestUrl) - if requestUrl.startswith( - "https://tv.eurosport.com/do-not-allow"): - print(" Not allowed!") - return False - if requestUrl.startswith( - "https://tv.eurosport.com/"): - print(" Allowed!") - callback.Continue(True) - return True - return False - - def OnRendererProcessTerminated(self, browser, status): - print("[wxpython.py] RequestHandler::OnRendererProcessTerminated()") - statuses = { - cefpython.TS_ABNORMAL_TERMINATION: "TS_ABNORMAL_TERMINATION", - cefpython.TS_PROCESS_WAS_KILLED: "TS_PROCESS_WAS_KILLED", - cefpython.TS_PROCESS_CRASHED: "TS_PROCESS_CRASHED" - } - statusName = "Unknown" - if status in statuses: - statusName = statuses[status] - print(" status = %s" % statusName) - - def OnPluginCrashed(self, browser, pluginPath): - print("[wxpython.py] RequestHandler::OnPluginCrashed()") - print(" plugin path = %s" % pluginPath) - - # ------------------------------------------------------------------------- - # LoadHandler - # ------------------------------------------------------------------------- - - def OnLoadingStateChange(self, browser, isLoading, canGoBack, - canGoForward): - print("[wxpython.py] LoadHandler::OnLoadingStateChange()") - print(" isLoading = %s, canGoBack = %s, canGoForward = %s" \ - % (isLoading, canGoBack, canGoForward)) - - def OnLoadStart(self, browser, frame): - print("[wxpython.py] LoadHandler::OnLoadStart()") - print(" frame url = %s" % frame.GetUrl()[:100]) - - def OnLoadEnd(self, browser, frame, httpStatusCode): - print("[wxpython.py] LoadHandler::OnLoadEnd()") - print(" frame url = %s" % frame.GetUrl()[:100]) - # For file:// urls the status code = 0 - print(" http status code = %s" % httpStatusCode) - # Tests for the Browser object methods - self._Browser_LoadUrl(browser) - - def _Browser_LoadUrl(self, browser): - if browser.GetUrl() == "data:text/html,Test#Browser.LoadUrl": - browser.LoadUrl("file://"+GetApplicationPath("wxpython.html")) - - def OnLoadError(self, browser, frame, errorCode, errorTextList, failedUrl): - print("[wxpython.py] LoadHandler::OnLoadError()") - print(" frame url = %s" % frame.GetUrl()[:100]) - print(" error code = %s" % errorCode) - print(" error text = %s" % errorTextList[0]) - print(" failed url = %s" % failedUrl) - # Handle ERR_ABORTED error code, to handle the following cases: - # 1. Esc key was pressed which calls browser.StopLoad() in OnKeyEvent - # 2. Download of a file was aborted - if errorCode == cefpython.ERR_ABORTED: - print("[wxpython.py] LoadHandler::OnLoadError(): Ignoring load"\ - " error: Esc was pressed or file download was aborted") - return; - customErrorMessage = "My custom error message!" - frame.LoadUrl("data:text/html,%s" % customErrorMessage) - - # ------------------------------------------------------------------------- - # LifespanHandler - # ------------------------------------------------------------------------- - - # ** This callback is executed on the IO thread ** - # Empty place-holders: popupFeatures, client. - def OnBeforePopup(self, browser, frame, targetUrl, targetFrameName, - targetDisposition, userGesture, - popupFeatures, windowInfo, client, browserSettings, - noJavascriptAccess): - print("[wxpython.py] LifespanHandler::OnBeforePopup()") - print(" targetUrl = %s" % targetUrl) - # Custom browser settings for popups: - # > browserSettings[0] = {"plugins_disabled": True} - # Set WindowInfo object: - # > windowInfo[0] = cefpython.WindowInfo() - allowPopups = True - return not allowPopups - - def _OnAfterCreated(self, browser): - # This is a global callback set using SetGlobalClientCallback(). - print("[wxpython.py] LifespanHandler::_OnAfterCreated()") - print(" browserId=%s" % browser.GetIdentifier()) - - def DoClose(self, browser): - print("[wxpython.py] LifespanHandler::DoClose()") - print(" browserId=%s" % browser.GetIdentifier()) - - def OnBeforeClose(self, browser): - print("[wxpython.py] LifespanHandler::OnBeforeClose") - print(" browserId=%s" % browser.GetIdentifier()) - - # ------------------------------------------------------------------------- - # JavascriptDialogHandler - # ------------------------------------------------------------------------- - - def OnJavascriptDialog(self, browser, originUrl, dialogType, - messageText, defaultPromptText, callback, - suppressMessage): - print("[wxpython.py] JavascriptDialogHandler::OnJavascriptDialog()") - print(" originUrl="+originUrl) - print(" dialogType="+str(dialogType)) - print(" messageText="+messageText) - print(" defaultPromptText="+defaultPromptText) - # If you want to suppress the javascript dialog: - # suppressMessage[0] = True - return False - - def OnBeforeUnloadJavascriptDialog(self, browser, messageText, isReload, - callback): - print("[wxpython.py] OnBeforeUnloadJavascriptDialog()") - print(" messageText="+messageText) - print(" isReload="+str(isReload)) - # Return True if the application will use a custom dialog: - # callback.Continue(allow=True, userInput="") - # return True - return False - - def OnResetJavascriptDialogState(self, browser): - print("[wxpython.py] OnResetDialogState()") - - def OnJavascriptDialogClosed(self, browser): - print("[wxpython.py] OnDialogClosed()") - - -class MyApp(wx.App): - timer = None - timerID = 1 - timerCount = 0 - - def OnInit(self): - if not USE_EVT_IDLE: - self.CreateTimer() - frame = MainFrame() - self.SetTopWindow(frame) - frame.Show() - return True - - def CreateTimer(self): - # See "Making a render loop": - # http://wiki.wxwidgets.org/Making_a_render_loop - # Another approach is to use EVT_IDLE in MainFrame, - # see which one fits you better. - self.timer = wx.Timer(self, self.timerID) - self.timer.Start(10) # 10ms - wx.EVT_TIMER(self, self.timerID, self.OnTimer) - - def OnTimer(self, event): - self.timerCount += 1 - # print("[wxpython.py] OnTimer() %d" % self.timerCount) - cefpython.MessageLoopWork() - - def OnExit(self): - # When app.MainLoop() returns, MessageLoopWork() should - # not be called anymore. - if not USE_EVT_IDLE: - self.timer.Stop() - -def GetSources(): - # Get sources of all python functions and methods from this file. - # This is to provide sources preview to wxpython.html. - # The dictionary of functions is binded to "window.sources". - thisModule = sys.modules[__name__] - functions = inspect.getmembers(thisModule, inspect.isfunction) - classes = inspect.getmembers(thisModule, inspect.isclass) - sources = {} - for funcTuple in functions: - sources[funcTuple[0]] = inspect.getsource(funcTuple[1]) - for classTuple in classes: - className = classTuple[0] - classObject = classTuple[1] - methods = inspect.getmembers(classObject) - for methodTuple in methods: - try: - sources[methodTuple[0]] = inspect.getsource(\ - methodTuple[1]) - except: - pass - return sources - -if __name__ == '__main__': - print('[wxpython.py] wx.version=%s' % wx.version()) - - # Intercept python exceptions. Exit app immediately when exception - # happens on any of the threads. - sys.excepthook = ExceptHook - - # Application settings - settings = { - # CEF Python debug messages in console and in log_file - "debug": True, - # Set it to LOGSEVERITY_VERBOSE for more details - "log_severity": cefpython.LOGSEVERITY_INFO, - # Set to "" to disable logging to a file - "log_file": GetApplicationPath("debug.log"), - # These directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - # The "subprocess" executable that launches the Renderer - # and GPU processes among others. You may rename that - # executable if you like. - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess"), - # This option is required for the GetCookieManager callback - # to work. It affects renderer processes, when this option - # is set to True. It will force a separate renderer process - # for each browser created using CreateBrowserSync. - "unique_request_context_per_browser": True, - # Downloads are handled automatically. A default SaveAs file - # dialog provided by OS will be displayed. - "downloads_enabled": True, - # Remote debugging port, required for Developer Tools support. - # A value of 0 will generate a random port. To disable devtools - # support set it to -1. - "remote_debugging_port": 0, - # Mouse context menu - "context_menu": { - "enabled": True, - "navigation": True, # Back, Forward, Reload - "print": True, - "view_source": True, - "external_browser": True, # Open in external browser - "devtools": True, # Developer Tools - }, - # See also OnCertificateError which allows you to ignore - # certificate errors for specific websites. - "ignore_certificate_errors": False, - } - - # Browser settings. You may have different settings for each - # browser, see the call to CreateBrowserSync. - g_browserSettings = { - # "plugins_disabled": True, - # "file_access_from_file_urls_allowed": True, - # "universal_access_from_file_urls_allowed": True, - } - - # Command line switches set programmatically - switches = { - # "proxy-server": "socks5://127.0.0.1:8888", - # "no-proxy-server": "", - # "enable-media-stream": "", - # "remote-debugging-port": "12345", - # "disable-gpu": "", - # "--invalid-switch": "" -> Invalid switch name - } - - cefpython.Initialize(settings, switches) - - app = MyApp(False) - app.MainLoop() - # Let wx.App destructor do the cleanup before calling cefpython.Shutdown(). - del app - - cefpython.Shutdown() diff --git a/src/linux/compile.py b/src/linux/compile.py deleted file mode 100644 index 16acf092d..000000000 --- a/src/linux/compile.py +++ /dev/null @@ -1,325 +0,0 @@ -""" -Build the cefpython module, install package and run example. - -Usage: - compile.py VERSION [--debug] [--fast] - -Options: - VERSION Version in format xx.xx - --debug Debug mode - --fast Fast mode, don't delete C++ .o .a files, nor the setup/build/ - directory, and disable optimization flags when building - the so/pyd module. - --kivy Run Kivy example -""" - -# TODO: Check Cython version using info from tools/requirements.txt - -import sys -import os -import glob -import shutil -import subprocess -import platform -import re -import struct - -# raw_input() was renamed to input() in Python 3 - -try: - # noinspection PyUnresolvedReferences - # noinspection PyShadowingBuiltins - input = raw_input -except NameError: - pass - - -def check_cython_version(): - with open("../../tools/requirements.txt", "r") as fileobj: - contents = fileobj.read() - match = re.search(r"cython\s*==\s*([\d.]+)", contents, - flags=re.IGNORECASE) - assert match, "cython package not found in requirements.txt" - require_version = match.group(1) - try: - import Cython - version = Cython.__version__ - except ImportError: - # noinspection PyUnusedLocal - Cython = None - print("ERROR: Cython is not installed ({0} required)" - .format(require_version)) - sys.exit(1) - if version != require_version: - print("ERROR: Wrong Cython version: {0}. Required: {1}" - .format(version, require_version)) - sys.exit(1) - print("Cython version: {0}".format(version)) - -check_cython_version() - -# This will not show "Segmentation fault" error message: -# | subprocess.call(["python", "./wxpython.py"]) -# You need to call it with shell=True for this kind of -# error message to be shown: -# | subprocess.call("python wxpython.py", shell=True) - -# How to debug: -# 1. Install "python-dbg" package -# 2. Install "python-wxgtk2.8-dbg" package -# 3. Run "python compile.py debug" -# 4. In cygdb type "cy run" -# 5. To display debug backtrace type "cy bt" -# 6. More commands: http://docs.cython.org/src/userguide/debugging.html - -# -- debug flag -if len(sys.argv) > 1 and "--debug" in sys.argv: - DEBUG_FLAG = True - print("DEBUG mode On") -else: - DEBUG_FLAG = False - -# --fast flag -if len(sys.argv) > 1 and "--fast" in sys.argv: - # Fast mode doesn't delete C++ .o .a files. - # Fast mode also disables optimization flags in setup/setup.py . - FAST_FLAG = True - print("FAST mode On") -else: - FAST_FLAG = False - -# --kivy flag -if len(sys.argv) > 1 and "--kivy" in sys.argv: - KIVY_FLAG = True - print("KIVY_FLAG flag enabled") -else: - KIVY_FLAG = False - - -# version arg -if len(sys.argv) > 1 and re.search(r"^\d+\.\d+$", sys.argv[1]): - VERSION = sys.argv[1] -else: - print("[compile.py] ERROR: expected first argument to be version number") - print(" Allowed version format: \\d+\.\\d+") - sys.exit(1) - -print("VERSION=%s" % VERSION) - -# Architecture and OS postfixes -ARCH32 = (8 * struct.calcsize('P') == 32) -ARCH64 = (8 * struct.calcsize('P') == 64) -OS_POSTFIX = ("win" if platform.system() == "Windows" else - "linux" if platform.system() == "Linux" else - "mac" if platform.system() == "Darwin" else "unknown") -OS_POSTFIX2 = "unknown" -if OS_POSTFIX == "win": - OS_POSTFIX2 = "win32" if ARCH32 else "win64" -elif OS_POSTFIX == "mac": - OS_POSTFIX2 = "mac32" if ARCH32 else "mac64" -elif OS_POSTFIX == "linux": - OS_POSTFIX2 = "linux32" if ARCH32 else "linux64" - -PYVERSION = str(sys.version_info[0])+str(sys.version_info[1]) -print("PYVERSION = %s" % PYVERSION) -print("OS_POSTFIX2 = %s" % OS_POSTFIX2) - -# Directories -LINUX_DIR = os.path.abspath(os.path.dirname(__file__)) -SRC_DIR = os.path.abspath(os.path.join(LINUX_DIR, "..")) -CPP_UTILS_DIR = os.path.abspath(os.path.join(SRC_DIR, "cpp_utils")) -CLIENT_HANDLER_DIR = os.path.abspath(os.path.join(SRC_DIR, "client_handler")) -SUBPROCESS_DIR = os.path.abspath(os.path.join(SRC_DIR, "subprocess")) -SETUP_DIR = os.path.abspath(os.path.join(LINUX_DIR, "setup")) -CEFPYTHON_DIR = os.path.abspath(os.path.join(SRC_DIR, "..")) -BUILD_DIR = os.path.abspath(os.path.join(CEFPYTHON_DIR, "build")) -CEF_BINARY = os.path.abspath(os.path.join(BUILD_DIR, "cef_"+OS_POSTFIX2)) -CEFPYTHON_BINARY = os.path.abspath(os.path.join(BUILD_DIR, - "cefpython_"+OS_POSTFIX2)) - -# Create directories if necessary -if not os.path.exists(CEFPYTHON_BINARY): - os.mkdir(CEFPYTHON_BINARY) - -# Check directories -assert os.path.exists(CEF_BINARY) -assert os.path.exists(CEFPYTHON_BINARY) - -print("Compiling C++ projects") - -# Need to allow continuing even when make fails, as it may -# fail because the "public" function declaration is not yet -# in "cefpython.h", but for it to be generated we need to run -# cython compiling, so in this case you continue even when make -# fails and then run the compile.py script again and this time -# make should succeed. - -# -------- CPP_UTILS_DIR - -os.chdir(CPP_UTILS_DIR) -if not FAST_FLAG: - subprocess.call("rm -f *.o *.a", shell=True) - -ret = subprocess.call("make -f Makefile", shell=True) -if ret != 0: - # noinspection PyUnboundLocalVariable - what = input("make failed, press 'y' to continue, 'n' to stop: ") - if what != "y": - sys.exit(1) - -# -------- CLIENT_HANDLER_DIR - -os.chdir(CLIENT_HANDLER_DIR) -if not FAST_FLAG: - subprocess.call("rm -f *.o *.a", shell=True) - -ret = subprocess.call("make -f Makefile", shell=True) -if ret != 0: - what = input("make failed, press 'y' to continue, 'n' to stop: ") - if what != "y": - sys.exit(1) - -# -------- SUBPROCESS_DIR - -os.chdir(SUBPROCESS_DIR) -if not FAST_FLAG: - subprocess.call("rm -f *.o *.a", shell=True) - subprocess.call("rm -f subprocess", shell=True) - -ret = subprocess.call("make -f Makefile-libcefpythonapp", shell=True) -if ret != 0: - what = input("make failed, press 'y' to continue, 'n' to stop: ") - if what != "y": - sys.exit(1) - -ret = subprocess.call("make -f Makefile", shell=True) -if ret != 0: - what = input("make failed, press 'y' to continue, 'n' to stop: ") - if what != "y": - sys.exit(1) -subprocess_exe = os.path.join(CEFPYTHON_BINARY, "subprocess") -if os.path.exists("./subprocess"): - # .copy() will also copy Permission bits - shutil.copy("./subprocess", subprocess_exe) - -# -------- LINUX_DIR - -os.chdir(LINUX_DIR) -try: - cefpython_module = os.path.join(CEFPYTHON_BINARY, - "cefpython_py{0}.so".format(PYVERSION)) - os.remove(cefpython_module) -except OSError: - pass - - -# -------- SETUP_DIR - -os.chdir(SETUP_DIR) - -os.system("rm -f ./cefpython_py*.so") - -pyx_files = glob.glob("./*.pyx") -for f in pyx_files: - os.remove(f) - -try: - if not FAST_FLAG: - shutil.rmtree(os.path.join(SETUP_DIR, "build")) -except OSError: - pass - -ret = subprocess.call("{python} fix_pyx_files.py" - .format(python=sys.executable), shell=True) -if ret != 0: - sys.exit("ERROR") - -# Create __version__.pyx after fix_pyx_files.py was called, -# as that script deletes old pyx files before copying new ones. -print("Creating __version__.pyx file") -with open("__version__.pyx", "w") as fo: - fo.write('__version__ = "{}"\n'.format(VERSION)) - -# if DEBUG_FLAG: -# ret = subprocess.call("python-dbg setup.py build_ext --inplace" -# " --cython-gdb", shell=True) -if FAST_FLAG: - ret = subprocess.call("{python} setup.py build_ext --inplace --fast" - .format(python=sys.executable), shell=True) -else: - ret = subprocess.call("{python} setup.py build_ext --inplace" - .format(python=sys.executable), shell=True) - -# if DEBUG_FLAG: -# shutil.rmtree("./../binaries_%s/cython_debug/" % BITS, -# ignore_errors=True) -# shutil.copytree("./cython_debug/", -# "./../binaries_%s/cython_debug/" % BITS) - -oldpyxfiles = glob.glob("./*.pyx") -print("Removing old pyx files in /setup/: %s" % oldpyxfiles) -for pyxfile in oldpyxfiles: - if os.path.exists(pyxfile): - os.remove(pyxfile) - -if ret != 0: - sys.exit("ERROR") - -exitcode = os.system("mv ./cefpython_py{pyver}*.so" - " {cefpython_binary}/cefpython_py{pyver}.so" - .format(pyver=PYVERSION, - cefpython_binary=CEFPYTHON_BINARY)) -if exitcode: - raise RuntimeError("Failed to move the cefpython module") - -print("DONE") - -# -------- LINUX_DIR - -os.chdir(LINUX_DIR) - -# if DEBUG_FLAG: -# os.chdir("./binaries_%s" % BITS) -# subprocess.call("cygdb . --args python-dbg wxpython.py", shell=True) - -print("Make installer and run setup.py install...") - -# Clean installer directory from previous run -exit_code = os.system("rm -rf ./installer/cefpython3-{ver}-*-setup/" - .format(ver=VERSION)) -if exit_code: - os.system("sudo rm -rf ./installer/cefpython3-{ver}-*-setup/" - .format(ver=VERSION)) - -# System python requires sudo when installing package -if sys.executable in ["/usr/bin/python", "/usr/bin/python3"]: - sudo = "sudo" -else: - sudo = "" - -# Make installer, install, run examples and unit tests, -# and return to src/linux/ dir. -if KIVY_FLAG: - run_examples = " && {python} ../src/linux/deprecated_64bit/kivy_.py" -else: - run_examples = (" && {python} hello_world.py" - " && {python} wxpython.py" - " && {python} gtk2.py" - " && {python} gtk2.py --message-loop-timer" - # " && {python} gtk3.py" - " && {python} tkinter_.py" - " && {python} qt.py pyqt" - " && {python} qt.py pyside" - " && {python} ../src/linux/deprecated_64bit/kivy_.py") -commands = ("cd ./installer/" - " && {python} make-setup.py --version {ver}" - " && cd cefpython3-{ver}-*-setup/" - " && {sudo} {python} setup.py install" - " && cd ../" - " && {sudo} rm -rf ./cefpython3-{ver}-*-setup/" - " && cd ../../../unittests/" - " && {python} _test_runner.py" - " && cd ../examples/" - + run_examples + - " && cd ../src/linux/") -os.system(commands.format(python=sys.executable, ver=VERSION, sudo=sudo)) diff --git a/src/linux/installer/.gitignore b/src/linux/deb_pkg_deprecated/.gitignore similarity index 100% rename from src/linux/installer/.gitignore rename to src/linux/deb_pkg_deprecated/.gitignore diff --git a/src/linux/installer/debian.postinst b/src/linux/deb_pkg_deprecated/debian.postinst similarity index 100% rename from src/linux/installer/debian.postinst rename to src/linux/deb_pkg_deprecated/debian.postinst diff --git a/src/linux/installer/deps.txt b/src/linux/deb_pkg_deprecated/deps.txt similarity index 100% rename from src/linux/installer/deps.txt rename to src/linux/deb_pkg_deprecated/deps.txt diff --git a/src/linux/installer/find-deps.py b/src/linux/deb_pkg_deprecated/find-deps.py similarity index 100% rename from src/linux/installer/find-deps.py rename to src/linux/deb_pkg_deprecated/find-deps.py diff --git a/src/linux/installer/make-deb.py b/src/linux/deb_pkg_deprecated/make-deb.py similarity index 100% rename from src/linux/installer/make-deb.py rename to src/linux/deb_pkg_deprecated/make-deb.py diff --git a/src/linux/installer/stdeb.cfg.template b/src/linux/deb_pkg_deprecated/stdeb.cfg.template similarity index 100% rename from src/linux/installer/stdeb.cfg.template rename to src/linux/deb_pkg_deprecated/stdeb.cfg.template diff --git a/src/linux/installer/README.txt b/src/linux/installer/README.txt deleted file mode 100644 index 8b481c2a8..000000000 --- a/src/linux/installer/README.txt +++ /dev/null @@ -1,20 +0,0 @@ -1. To install CEF Python 3 package type: - - sudo python setup.py install - - This will install the cefpython3 package to - /usr/local/lib/python2.7/dist-packages/ - -2. In the same directory that setup.py resides there is - an examples/ directory. Run some examples from there: - - cd examples/ - python pygtk_.py - python pyqt.py - python wxpython.py - python kivy_.py - - cd wx/ - python sample1.py - python sample2.py - python sample3.py diff --git a/src/linux/installer/__init__.py.template b/src/linux/installer/__init__.py.template deleted file mode 100644 index baebba0f2..000000000 --- a/src/linux/installer/__init__.py.template +++ /dev/null @@ -1,40 +0,0 @@ -__all__ = ["cefpython", "wx"] -__version__ = "%(APP_VERSION)s" -__author__ = "The CEF Python authors" - -import ctypes, os - -# If this is a debian package then package_dir returns: -# /usr/lib/pymodules/python2.7/cefpython3 -# The above path consists of symbolic links to the real directory: -# /usr/share/pyshared/cefpython3 - -# If package was installed using PIP or setup.py then package -# dir is here: -# /usr/local/lib/python2.7/dist-packages/cefpython3/ - -package_dir = os.path.dirname(os.path.abspath(__file__)) - -# This loads the libcef.so library for the subprocess executable. -os.environ["LD_LIBRARY_PATH"] = package_dir - -# This env variable will be returned by cefpython.GetModuleDirectory(). -os.environ["CEFPYTHON3_PATH"] = package_dir - -# This loads the libcef.so library for the main python executable. -# The libffmpegsumo.so library does not need to be loaded here, -# it may cause issues to load it here in the browser process. -libcef_so = os.path.join(package_dir, "libcef.so") -ctypes.CDLL(libcef_so, ctypes.RTLD_GLOBAL) - -import sys -if (2, 7) <= sys.version_info < (2, 8): - from . import cefpython_py27 as cefpython -elif (3, 4) <= sys.version_info < (3, 5): - from . import cefpython_py34 as cefpython -elif (3, 5) <= sys.version_info < (3, 6): - from . import cefpython_py35 as cefpython -elif (3, 6) <= sys.version_info < (3, 7): - from . import cefpython_py36 as cefpython -else: - raise Exception("Unsupported python version: " + sys.version) diff --git a/src/linux/installer/make-setup.py b/src/linux/installer/make-setup.py deleted file mode 100644 index bd016cb1a..000000000 --- a/src/linux/installer/make-setup.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright (c) 2012-2014 The CEF Python authors. All rights reserved. -# License: New BSD License. -# Website: http://code.google.com/p/cefpython/ - -# Create a setup package. - -import sys -import os -import re -import platform -import shutil -import glob -import sysconfig -import subprocess -import struct - -PACKAGE_NAME = "cefpython3" - -# Bits -BITS = platform.architecture()[0] -assert (BITS == "32bit" or BITS == "64bit") -if BITS == "32bit": - LINUX_BITS = "linux32" -else: - LINUX_BITS = "linux64" - -# Architecture and OS postfixes -ARCH32 = (8 * struct.calcsize('P') == 32) -ARCH64 = (8 * struct.calcsize('P') == 64) -OS_POSTFIX = ("win" if platform.system() == "Windows" else - "linux" if platform.system() == "Linux" else - "mac" if platform.system() == "Darwin" else "unknown") -OS_POSTFIX2 = "unknown" -if OS_POSTFIX == "win": - OS_POSTFIX2 = "win32" if ARCH32 else "win64" -elif OS_POSTFIX == "mac": - OS_POSTFIX2 = "mac32" if ARCH32 else "mac64" -elif OS_POSTFIX == "linux": - OS_POSTFIX2 = "linux32" if ARCH32 else "linux64" - -# Directories -INSTALLER_DIR = os.path.dirname(os.path.abspath(__file__)) -LINUX_DIR = os.path.abspath(os.path.join(INSTALLER_DIR, "..")) -SRC_DIR = os.path.abspath(os.path.join(LINUX_DIR, "..")) -CEFPYTHON_DIR = os.path.abspath(os.path.join(SRC_DIR, "..")) -BUILD_DIR = os.path.abspath(os.path.join(CEFPYTHON_DIR, "build")) -CEF_BINARY = os.path.abspath(os.path.join(BUILD_DIR, "cef_"+OS_POSTFIX2)) -CEFPYTHON_BINARY = os.path.abspath(os.path.join(BUILD_DIR, - "cefpython_"+OS_POSTFIX2)) - -# Check directories -assert os.path.exists(CEF_BINARY) -assert os.path.exists(CEFPYTHON_BINARY) - -README_FILE = os.getcwd()+r"/README.txt" -INIT_TEMPLATE = os.getcwd()+r"/__init__.py.template" -SETUP_TEMPLATE = os.getcwd()+r"/setup.py.template" -# SETUP_CFG_TEMPLATE = os.getcwd()+r"/setup.cfg.template" - - -def str_format(string, dictionary): - orig_string = string - for key, value in dictionary.items(): - string = string.replace("%("+key+")s", value) - if string == orig_string: - raise Exception("Nothing to format") - if re.search(r"%\([a-zA-Z0-9_]+\)s", string): - raise Exception("Not all strings formatted") - return string - - -def main(): - args = ' '.join(sys.argv) - match = re.search(r"\d+\.\d+", args) - if match: - version = match.group(0) - else: - print("Usage make-setup.py {version}") - sys.exit(1) - - template_vars = dict() - template_vars["APP_VERSION"] = version - template_vars["PLATFORM"] = sysconfig.get_platform() - template_vars["PY_VERSION_DIGITS_ONLY"] = ( - str(sys.version_info.major) + - str(sys.version_info.minor)) # e.g. "27" or "34" - - print("Reading template: %s" % README_FILE) - f = open(README_FILE) - README_CONTENT = f.read() - f.close() - - print("Reading template: %s" % INIT_TEMPLATE) - f = open(INIT_TEMPLATE) - INIT_CONTENT = str_format(f.read(), template_vars) - f.close() - - print("Reading template: %s" % SETUP_TEMPLATE) - f = open(SETUP_TEMPLATE) - SETUP_CONTENT = str_format(f.read(), template_vars) - f.close() - - # print("Reading template: %s" % SETUP_CFG_TEMPLATE) - # f = open(SETUP_CFG_TEMPLATE) - # SETUP_CFG_CONTENT = str_format(f.read(), template_vars) - # f.close() - - setup_dir = (INSTALLER_DIR + "/" + PACKAGE_NAME+"-" + - template_vars["APP_VERSION"] + "-" + OS_POSTFIX2 + "-setup") - print("Creating setup dir: "+setup_dir) - os.mkdir(setup_dir) - - package_dir = setup_dir+"/"+PACKAGE_NAME - print("Creating package dir") - os.mkdir(package_dir) - - print("Copying License file") - shutil.copy("../../../License", package_dir) - - print("Creating README.txt from template") - with open(setup_dir+"/README.txt", "w") as f: - f.write(README_CONTENT) - - print("Creating setup.py from template") - with open(setup_dir+"/setup.py", "w") as f: - f.write(SETUP_CONTENT) - - # print("Creating setup.cfg from template") - # with open(setup_dir+"/setup.cfg", "w") as f: - # f.write(SETUP_CFG_CONTENT) - - print("Copying binaries to package dir") - # Copy Kivy - old_binaries_dir = os.path.abspath(INSTALLER_DIR+"/../binaries_"+BITS+"/") - ret = os.system("cp -rf "+old_binaries_dir+"/kivy_.py "+package_dir) - assert ret == 0 - ret = os.system("cp -rf "+old_binaries_dir+"/kivy-select-boxes/ " - + package_dir) - assert ret == 0 - # Copy binaries - ret = os.system("cp -rf "+CEF_BINARY+"/*.txt "+package_dir) - assert ret == 0 - ret = os.system("cp -rf "+CEF_BINARY+"/bin/* "+package_dir) - assert ret == 0 - ret = os.system("cp -rf "+CEFPYTHON_BINARY+"/* "+package_dir) - assert ret == 0 - - os.chdir(package_dir) - print("Removing .log files from the package dir") - os.system("rm *.log") - # assert ret == 0 - if there are no .log files this assert would fail. - os.chdir(INSTALLER_DIR) - - print("Creating __init__.py from template") - with open(package_dir+"/__init__.py", "w") as f: - f.write(INIT_CONTENT) - - print("Creating examples dir in package dir") - os.mkdir(package_dir+"/examples/") - - print("Copying root examples/ directory") - ret = os.system("rm ../../../examples/*.log") - ret = os.system("cp -r ../../../examples/* "+package_dir+"/examples/") - assert ret == 0 - - print("Moving kivy-select-boxes dir to examples dir") - assert os.path.exists(package_dir+"/kivy-select-boxes") - shutil.move(package_dir+"/kivy-select-boxes", - package_dir+"/examples/kivy-select-boxes") - - print("Creating wx dir in package dir") - os.mkdir(package_dir+"/wx/") - - print("Moving example scripts from package dir to examples dir") - examples = glob.glob(package_dir+"/*.py") - for example in examples: - # Ignore: cefpython_py27.py - dummy API script - if os.path.basename(example).startswith("cefpython_"): - continue - # Ignore: __init__.py - if os.path.basename(example).startswith("__"): - continue - os.rename(example, package_dir+"/examples/"+os.path.basename(example)) - ret = os.system("mv "+package_dir+"/*.html "+package_dir+"/examples/") - # assert ret == 0 - ret = os.system("mv "+package_dir+"/*.js "+package_dir+"/examples/") - # assert ret == 0 - ret = os.system("mv "+package_dir+"/*.css "+package_dir+"/examples/") - # assert ret == 0 - - print("Copying wx/ to package dir") - wx_subpackage_dir = os.path.abspath(INSTALLER_DIR+"/../../cefpython3.wx/") - ret = os.system("cp -rf " + wx_subpackage_dir + "/* " + package_dir - + "/wx/") - assert ret == 0 - - # print("Moving wx examples from wx/examples to examples/wx") - # shutil.move(package_dir+"/wx/examples", package_dir+"/wx/wx/") - # shutil.move(package_dir+"/wx/wx/", package_dir+"/examples/") - - print("Copying package dir examples to setup dir") - ret = os.system("cp -rf "+package_dir+"/examples/ "+setup_dir+"/examples/") - assert ret == 0 - - # Create empty debug.log files so that package uninstalls cleanly - # in case examples were launched. Issue 149. - debug_log_dirs = [package_dir, - package_dir+"/examples/", - # package_dir+"/examples/wx/" - ] - for curdir in debug_log_dirs: - print("Creating empty debug.log in %s" % curdir) - with open(curdir+"/debug.log", "w") as f: - f.write("") - # Set write permissions so that Wheel package files have it - # right. So that examples may be run from package directory. - subprocess.call("chmod 666 %s/debug.log" % curdir, shell=True) - - print("Setup Package created successfully.") - -if __name__ == "__main__": - main() diff --git a/src/linux/installer/setup.cfg.template b/src/linux/installer/setup.cfg.template deleted file mode 100644 index 067dcbfda..000000000 --- a/src/linux/installer/setup.cfg.template +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -python-tag=cp%(PY_VERSION_DIGITS_ONLY)s diff --git a/src/linux/installer/setup.py.template b/src/linux/installer/setup.py.template deleted file mode 100644 index 91517037e..000000000 --- a/src/linux/installer/setup.py.template +++ /dev/null @@ -1,104 +0,0 @@ -try: - # The setuptools package is not installed by default - # on a clean Ubuntu. Might be also a case on Windows. - # Python Eggs and Wheels can be created only with setuptools. - from setuptools import setup - from setuptools.command.install import install as _install - from setuptools.dist import Distribution - print("[setup.py] Using setuptools") -except: - from distutils.core import setup - from distutils.command.install import install as _install - from distutils.dist import Distribution - print("[setup.py] Using distutils") - -import sys -import os -import subprocess - -def post_install(): - """ Post install tasks """ - print("[setup.py] post_install()") - - # Find package directory. - # Do not import from local cefpython3/ directory. - del sys.path[0] - sys.path.append('') - import cefpython3 - package_dir = os.path.dirname(cefpython3.__file__) - - # Make sure this is not a local package imported - print("[setup.py] package_dir = %s" % package_dir) - assert not package_dir.startswith( - os.path.dirname(os.path.abspath(__file__))) - - # Execute permissions for subprocess.exe and cefclient.exe - subprocess_exe = os.path.join(package_dir, "subprocess") - cefclient_exe = os.path.join(package_dir, "cefclient") - print("[setup.py] chmod +x " + subprocess_exe) - subprocess.call("chmod +x "+subprocess_exe, shell=True) - print("[setup.py] chmod +x " + cefclient_exe) - subprocess.call("chmod +x "+cefclient_exe, shell=True) - - # Write permissions for debug.log files - commands = [ - "chmod 666 %s/debug.log" % package_dir, - "chmod 666 %s/examples/debug.log" % package_dir, - # "chmod 666 %s/examples/wx/debug.log" % package_dir, - ] - for command in commands: - print("[setup.py] %s" % command) - subprocess.call(command, shell=True) - -class install(_install): - def run(self): - _install.run(self) - post_install() - -class BinaryDistribution(Distribution): - def is_pure(self): - return False - -setup( - distclass=BinaryDistribution, - cmdclass={'install': install}, - name='cefpython3', # No spaces here, so that it works with deb packages. - version='%(APP_VERSION)s', - description='Python bindings for the Chromium Embedded Framework', - license='BSD 3-Clause', - author='Czarek Tomczak', - author_email='czarek.tomczak@gmail.com', - url='http://code.google.com/p/cefpython/', - platforms=['%(PLATFORM)s'], - packages=['cefpython3', 'cefpython3.wx'], - package_data={'cefpython3': [ - 'examples/*.py', - 'examples/*.html', - 'examples/*.js', - 'examples/*.css', - 'examples/*.png', - 'examples/*.txt', - 'examples/kivy-select-boxes/*.html', - 'examples/kivy-select-boxes/*.js', - 'examples/kivy-select-boxes/*.css', - 'examples/kivy-select-boxes/*.md', - 'examples/resources/*.png', - # 'examples/wx/*.py', - # 'examples/wx/*.html', - # 'examples/wx/*.png', - 'locales/*.pak', - 'wx/*.txt', - 'wx/images/*.png', - '*.txt', - 'License', - 'cefclient', - 'subprocess', - '*.so', - '*.pak', - '*.bin', - '*.dat', - 'debug.log', - 'examples/debug.log', - # 'examples/wx/debug.log', - ]} -) diff --git a/src/linux/setup/.gitignore b/src/linux/setup/.gitignore deleted file mode 100644 index a9d6b1b30..000000000 --- a/src/linux/setup/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -build/ -cefpython.cpp -cython_debug/ -*.pyx -*.cpp -lib_64bit/ -lib_32bit/ diff --git a/src/linux/setup/cefpython.h b/src/linux/setup/cefpython.h deleted file mode 100644 index 2bdae4328..000000000 --- a/src/linux/setup/cefpython.h +++ /dev/null @@ -1,103 +0,0 @@ -/* Generated by Cython 0.25.2 */ - -#ifndef __PYX_HAVE__cefpython_py27 -#define __PYX_HAVE__cefpython_py27 - - -#ifndef __PYX_HAVE_API__cefpython_py27 - -#ifndef __PYX_EXTERN_C - #ifdef __cplusplus - #define __PYX_EXTERN_C extern "C" - #else - #define __PYX_EXTERN_C extern - #endif -#endif - -#ifndef DL_IMPORT - #define DL_IMPORT(_T) _T -#endif - -__PYX_EXTERN_C DL_IMPORT(void) PyBrowser_ShowDevTools(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) PyTaskRunnable(int); -__PYX_EXTERN_C DL_IMPORT(void) RemovePythonCallbacksForFrame(int); -__PYX_EXTERN_C DL_IMPORT(bool) ExecutePythonCallback(CefRefPtr , int, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) CookieVisitor_Visit(int, CefCookie const &, int, int, bool &); -__PYX_EXTERN_C DL_IMPORT(void) StringVisitor_Visit(int, CefString const &); -__PYX_EXTERN_C DL_IMPORT(void) WebRequestClient_OnUploadProgress(int, CefRefPtr , int64, int64); -__PYX_EXTERN_C DL_IMPORT(void) WebRequestClient_OnDownloadProgress(int, CefRefPtr , int64, int64); -__PYX_EXTERN_C DL_IMPORT(void) WebRequestClient_OnDownloadData(int, CefRefPtr , void const *, size_t); -__PYX_EXTERN_C DL_IMPORT(void) WebRequestClient_OnRequestComplete(int, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) App_OnBeforeCommandLineProcessing_BrowserProcess(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) BrowserProcessHandler_OnRenderProcessThreadCreated(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) BrowserProcessHandler_OnBeforeChildProcessLaunch(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) DisplayHandler_OnAddressChange(CefRefPtr , CefRefPtr , CefString const &); -__PYX_EXTERN_C DL_IMPORT(void) DisplayHandler_OnTitleChange(CefRefPtr , CefString const &); -__PYX_EXTERN_C DL_IMPORT(bool) DisplayHandler_OnTooltip(CefRefPtr , CefString &); -__PYX_EXTERN_C DL_IMPORT(void) DisplayHandler_OnStatusMessage(CefRefPtr , CefString const &); -__PYX_EXTERN_C DL_IMPORT(bool) DisplayHandler_OnConsoleMessage(CefRefPtr , CefString const &, CefString const &, int); -__PYX_EXTERN_C DL_IMPORT(void) FocusHandler_OnTakeFocus(CefRefPtr , bool); -__PYX_EXTERN_C DL_IMPORT(bool) FocusHandler_OnSetFocus(CefRefPtr , cef_focus_source_t); -__PYX_EXTERN_C DL_IMPORT(void) FocusHandler_OnGotFocus(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) JavascriptDialogHandler_OnJavascriptDialog(CefRefPtr , CefString const &, cef_jsdialog_type_t, CefString const &, CefString const &, CefRefPtr , bool &); -__PYX_EXTERN_C DL_IMPORT(bool) JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog(CefRefPtr , CefString const &, bool, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) JavascriptDialogHandler_OnResetJavascriptDialogState(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) JavascriptDialogHandler_OnJavascriptDialogClosed(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) KeyboardHandler_OnPreKeyEvent(CefRefPtr , CefKeyEvent const &, CefEventHandle, bool *); -__PYX_EXTERN_C DL_IMPORT(bool) KeyboardHandler_OnKeyEvent(CefRefPtr , CefKeyEvent const &, CefEventHandle); -__PYX_EXTERN_C DL_IMPORT(bool) LifespanHandler_OnBeforePopup(CefRefPtr , CefRefPtr , CefString const &, CefString const &, cef_window_open_disposition_t, bool, int const , CefWindowInfo &, CefRefPtr &, CefBrowserSettings &, bool *); -__PYX_EXTERN_C DL_IMPORT(void) LifespanHandler_OnAfterCreated(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) LifespanHandler_DoClose(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) LifespanHandler_OnBeforeClose(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) LoadHandler_OnLoadingStateChange(CefRefPtr , bool, bool, bool); -__PYX_EXTERN_C DL_IMPORT(void) LoadHandler_OnLoadStart(CefRefPtr , CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) LoadHandler_OnLoadEnd(CefRefPtr , CefRefPtr , int); -__PYX_EXTERN_C DL_IMPORT(void) LoadHandler_OnLoadError(CefRefPtr , CefRefPtr , cef_errorcode_t, CefString const &, CefString const &); -__PYX_EXTERN_C DL_IMPORT(bool) RenderHandler_GetRootScreenRect(CefRefPtr , CefRect &); -__PYX_EXTERN_C DL_IMPORT(bool) RenderHandler_GetViewRect(CefRefPtr , CefRect &); -__PYX_EXTERN_C DL_IMPORT(bool) RenderHandler_GetScreenRect(CefRefPtr , CefRect &); -__PYX_EXTERN_C DL_IMPORT(bool) RenderHandler_GetScreenPoint(CefRefPtr , int, int, int &, int &); -__PYX_EXTERN_C DL_IMPORT(bool) RenderHandler_GetScreenInfo(CefRefPtr , CefScreenInfo &); -__PYX_EXTERN_C DL_IMPORT(void) RenderHandler_OnPopupShow(CefRefPtr , bool); -__PYX_EXTERN_C DL_IMPORT(void) RenderHandler_OnPopupSize(CefRefPtr , CefRect const &); -__PYX_EXTERN_C DL_IMPORT(void) RenderHandler_OnPaint(CefRefPtr , cef_paint_element_type_t, std::vector &, void const *, int, int); -__PYX_EXTERN_C DL_IMPORT(void) RenderHandler_OnCursorChange(CefRefPtr , CefCursorHandle); -__PYX_EXTERN_C DL_IMPORT(void) RenderHandler_OnScrollOffsetChanged(CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) RenderHandler_StartDragging(CefRefPtr , CefRefPtr , PY_LONG_LONG, int, int); -__PYX_EXTERN_C DL_IMPORT(void) RenderHandler_UpdateDragCursor(CefRefPtr , PY_LONG_LONG); -__PYX_EXTERN_C DL_IMPORT(bool) ResourceHandler_ProcessRequest(int, CefRefPtr , CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) ResourceHandler_GetResponseHeaders(int, CefRefPtr , int64 &, CefString &); -__PYX_EXTERN_C DL_IMPORT(bool) ResourceHandler_ReadResponse(int, void *, int, int &, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) ResourceHandler_CanGetCookie(int, CefCookie const &); -__PYX_EXTERN_C DL_IMPORT(bool) ResourceHandler_CanSetCookie(int, CefCookie const &); -__PYX_EXTERN_C DL_IMPORT(void) ResourceHandler_Cancel(int); -__PYX_EXTERN_C DL_IMPORT(bool) RequestHandler_OnBeforeBrowse(CefRefPtr , CefRefPtr , CefRefPtr , bool); -__PYX_EXTERN_C DL_IMPORT(bool) RequestHandler_OnBeforeResourceLoad(CefRefPtr , CefRefPtr , CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(CefRefPtr ) RequestHandler_GetResourceHandler(CefRefPtr , CefRefPtr , CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) RequestHandler_OnResourceRedirect(CefRefPtr , CefRefPtr , CefString const &, CefString &, CefRefPtr , CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) RequestHandler_GetAuthCredentials(CefRefPtr , CefRefPtr , bool, CefString const &, int, CefString const &, CefString const &, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(bool) RequestHandler_OnQuotaRequest(CefRefPtr , CefString const &, int64, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(CefRefPtr ) RequestHandler_GetCookieManager(CefRefPtr , CefString const &); -__PYX_EXTERN_C DL_IMPORT(void) RequestHandler_OnProtocolExecution(CefRefPtr , CefString const &, bool &); -__PYX_EXTERN_C DL_IMPORT(bool) RequestHandler_OnBeforePluginLoad(CefRefPtr , CefString const &, CefString const &, bool, CefString const &, CefRefPtr , cef_plugin_policy_t *); -__PYX_EXTERN_C DL_IMPORT(bool) RequestHandler_OnCertificateError(int, CefString const &, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) RequestHandler_OnRendererProcessTerminated(CefRefPtr , cef_termination_status_t); -__PYX_EXTERN_C DL_IMPORT(void) RequestHandler_OnPluginCrashed(CefRefPtr , CefString const &); -__PYX_EXTERN_C DL_IMPORT(void) V8ContextHandler_OnContextCreated(CefRefPtr , CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) V8ContextHandler_OnContextReleased(int, int64); -__PYX_EXTERN_C DL_IMPORT(void) V8FunctionHandler_Execute(CefRefPtr , CefRefPtr , CefString &, CefRefPtr ); -__PYX_EXTERN_C DL_IMPORT(void) cefpython_GetDebugOptions(bool *, std::string *); -__PYX_EXTERN_C DL_IMPORT(bool) ApplicationSettings_GetBool(char const *); -__PYX_EXTERN_C DL_IMPORT(bool) ApplicationSettings_GetBoolFromDict(char const *, char const *); -__PYX_EXTERN_C DL_IMPORT(std::string) ApplicationSettings_GetString(char const *); -__PYX_EXTERN_C DL_IMPORT(int) CommandLineSwitches_GetInt(char const *); - -#endif /* !__PYX_HAVE_API__cefpython_py27 */ - -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC initcefpython_py27(void); -#else -PyMODINIT_FUNC PyInit_cefpython_py27(void); -#endif - -#endif /* !__PYX_HAVE__cefpython_py27 */ diff --git a/src/linux/setup/fix_pyx_files.py b/src/linux/setup/fix_pyx_files.py deleted file mode 100644 index 9c503ec40..000000000 --- a/src/linux/setup/fix_pyx_files.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2012-2014 The CEF Python authors. All rights reserved. -# License: New BSD License. -# Website: http://code.google.com/p/cefpython/ - -# First, it copies all .pyx files from upper directory to setup/. -# Then, fixes repeating of "include" statements in pyx files. - -# Only the mainfile needs to have "include" statements, -# but we're using PyCharm and to get rid of "unresolved references" -# and other errors displayed in pycharm we are adding "include" -# statements in all of the pyx files. - -# I'm not 100% sure how includes work in Cython, but I suspect that -# a few includes of the same file will include the same content more -# than once, it should work, but function and variable definitions are -# duplicated, it is some kind of overhead and it could lead to some -# problems in the future, better to fix it now. - -# It also checks cdef & cpdef functions whether they are not missing "except *", -# it is required to add it when returning non-python type. - -import glob -import os -import re -import shutil -import sys - -def ExceptAllMissing(content): - - # This is not perfect, won't detect C++ custom types, but will find - # the built-in types, templates and pointers. - patterns = [] - patterns.append( - r"\bcp?def\s+" - "((int|short|long|double|char|unsigned|float|double|cpp_bool" - "|cpp_string|cpp_wstring|uint64_t|uintptr_t|void" - "|CefString)\s+)+" - "\w+\([^)]*\)\s*(with\s+(gil|nogil))?\s*:") - patterns.append( - r"\bcp?def\s+" - # A template ends with bracket: CefRefPtr[CefBrowser] - # or a pointer ends with asterisk: CefBrowser* - "[^\s]+[\]*]\s+" - "\w+\([^)]*\)\s*(with\s+(gil|nogil))?\s*:") - patterns.append( - r"\bcp?def\s+" - # A reference, eg. CefString& - "[^\s]+&\s+" - "\w+\([^)]*\)\s*(with\s+(gil|nogil))?\s*:") - - for pattern in patterns: - match = re.search(pattern, content) - if match: break - - if match: - lineNumber = (content.count("\n", 0, match.start()) + 1) - return lineNumber - -print("\n") -mainfile = "cefpython.pyx" - -pyxfiles = glob.glob("../../*.pyx") -if not len(pyxfiles): - print("ERROR: no .pyx files found in root") - sys.exit(1) -pyxfiles = [file for file in pyxfiles if file.find(mainfile) == -1] -# Now, pyxfiles contains all pyx files except the mainfile (cefpython.pyx), -# we do not fix includes in mainfile. - -pyxfiles2 = glob.glob("../../handlers/*.pyx") -if not len(pyxfiles2): - print("ERROR: no .pyx files found in handlers/") - sys.exit(1) - -pyxfiles = pyxfiles + pyxfiles2 - -# So that this is the right directory we're in. -if os.path.exists("setup"): - print("Wrong directory, we should be inside setup!") - sys.exit(1) - -# Remove old pyx files in setup directory. -oldpyxfiles = glob.glob("./*.pyx") -print("Removing old pyx files in /setup/: %s" % oldpyxfiles) -for pyxfile in oldpyxfiles: - if os.path.exists(pyxfile): - os.remove(pyxfile) - -# Copying pyxfiles and reading its contents. -print("Copying .pyx files to /setup/: %s" % pyxfiles) - -# Copying cefpython.pyx -# and Fix includes in cefpython.pyx -# * include "handlers/focus_handler.pyx" becomes include "focus_handler.pyx" -shutil.copy("../../%s" % mainfile, "./%s" % mainfile) -with open("./%s" % mainfile, "r") as fo: - content = fo.read() - (content, subs) = re.subn(r"^include \"handlers/", - "include \"", - content, - flags=re.MULTILINE) -with open("./%s" % mainfile, "w") as fo: - fo.write(content) - print("%s includes fixed in %s" % (subs, mainfile)) - - -# Rest of the files will be copied in for loop below. - -print("Fixing includes in .pyx files:") -for pyxfile in pyxfiles: - newfile = "./%s" % os.path.basename(pyxfile) - shutil.copy(pyxfile, newfile) - pyxfile = newfile - with open(pyxfile, "r") as pyxfileopened: - content = pyxfileopened.read() - lineNumber = ExceptAllMissing(content) - if lineNumber: - print("WARNING: 'except *' missing in a cdef/cpdef function, " - "in file %s on line %d" % (os.path.basename(pyxfile), lineNumber)) - sys.exit(1) - # Do not remove the newline - so that line numbers are exact with originals. - (content, subs) = re.subn(r"^include[\t ]+[\"'][^\"'\n\r]+[\"'][\t ]*", "", content, flags=re.MULTILINE) - if subs: - print("%s includes removed in: %s" % (subs, os.path.basename(pyxfile))) - # Reading and writing with the same handle using "r+" mode doesn't work, - # you need to seek(0) and write the same amount of bytes that was in the - # file, otherwise old data from the end of file stays. - with open(pyxfile, "w") as pyxfileopened: - pyxfileopened.write(content) - -print("\n") diff --git a/src/linux/setup/setup.py b/src/linux/setup/setup.py deleted file mode 100644 index d06a688fb..000000000 --- a/src/linux/setup/setup.py +++ /dev/null @@ -1,156 +0,0 @@ -from distutils.core import setup -# Use "Extension" from Cython.Distutils so that "cython_directives" works. -# from distutils.extension import Extension -from Cython.Distutils import build_ext, Extension -import sys -import platform -from Cython.Compiler import Options -import Cython -import os -import struct - -print("Cython version: %s" % Cython.__version__) - -if len(sys.argv) > 1 and "--fast" in sys.argv: - sys.argv.remove("--fast") - # Fast mode disables optimization flags - FAST = True - print("FAST mode On") - COMPILE_OPTIMIZE_FLAGS = ['-flto', '-std=gnu++11'] - LINK_OPTIMIZE_FLAGS = ['-flto'] -else: - FAST = False - # Fix "ImportError ... undefined symbol ..." caused by CEF's include/base/ - # headers by adding the -flto flag (Issue #230). Unfortunately -flto - # prolongs compilation time significantly. - # More on the other flags: https://stackoverflow.com/questions/6687630/ - COMPILE_OPTIMIZE_FLAGS = ['-flto', '-fdata-sections', '-ffunction-sections', - '-std=gnu++11'] - LINK_OPTIMIZE_FLAGS = ['-flto', '-Wl,--gc-sections'] - - -# Architecture and OS postfixes -ARCH32 = (8 * struct.calcsize('P') == 32) -ARCH64 = (8 * struct.calcsize('P') == 64) -OS_POSTFIX = ("win" if platform.system() == "Windows" else - "linux" if platform.system() == "Linux" else - "mac" if platform.system() == "Darwin" else "unknown") -OS_POSTFIX2 = "unknown" -if OS_POSTFIX == "win": - OS_POSTFIX2 = "win32" if ARCH32 else "win64" -elif OS_POSTFIX == "mac": - OS_POSTFIX2 = "mac32" if ARCH32 else "mac64" -elif OS_POSTFIX == "linux": - OS_POSTFIX2 = "linux32" if ARCH32 else "linux64" - -# Directories -SETUP_DIR = os.path.abspath(os.path.dirname(__file__)) -LINUX_DIR = os.path.abspath(os.path.join(SETUP_DIR, "..")) -SRC_DIR = os.path.abspath(os.path.join(LINUX_DIR, "..")) -CEFPYTHON_DIR = os.path.abspath(os.path.join(SRC_DIR, "..")) -BUILD_DIR = os.path.abspath(os.path.join(CEFPYTHON_DIR, "build")) -CEF_BINARY = os.path.abspath(os.path.join(BUILD_DIR, "cef_"+OS_POSTFIX2)) -CEFPYTHON_BINARY = os.path.abspath(os.path.join(BUILD_DIR, - "cefpython_"+OS_POSTFIX2)) - -# Stop on first error, otherwise hundreds of errors appear in the console. -Options.fast_fail = True - -# Python version string: "27" or "32". -PYTHON_VERSION = str(sys.version_info.major) + str(sys.version_info.minor) - - -def CompileTimeConstants(): - - print("Generating: compile_time_constants.pxi") - with open("./../../compile_time_constants.pxi", "w") as fd: - fd.write('# This file was generated by setup.py\n') - # A way around Python 3.2 bug: UNAME_SYSNAME is not set. - fd.write('DEF UNAME_SYSNAME = "%s"\n' % platform.uname()[0]) - fd.write('DEF PY_MAJOR_VERSION = %s\n' % sys.version_info.major) - -CompileTimeConstants() - -ext_modules = [Extension( - - "cefpython_py%s" % PYTHON_VERSION, - ["cefpython.pyx"], - - # Ignore the warning in the console: - # > C:\Python27\lib\distutils\extension.py:133: UserWarning: - # > Unknown Extension options: 'cython_directives' warnings.warn(msg) - cython_directives={ - # Any conversion to unicode must be explicit using .decode(). - "c_string_type": "bytes", - "c_string_encoding": "utf-8", - }, - - language='c++', - include_dirs=[ - r'./../', - r'./../../', - r'./../../common/', - r'./../../extern/', - r'./../../extern/cef/', - '/usr/include/gtk-2.0', - '/usr/include/glib-2.0', - '/usr/include/gtk-unix-print-2.0', - '/usr/include/cairo', - '/usr/include/pango-1.0', - '/usr/include/gdk-pixbuf-2.0', - '/usr/include/atk-1.0', - # Ubuntu - '/usr/lib/x86_64-linux-gnu/gtk-2.0/include', - '/usr/lib/x86_64-linux-gnu/gtk-unix-print-2.0', - '/usr/lib/x86_64-linux-gnu/glib-2.0/include', - '/usr/lib/i386-linux-gnu/gtk-2.0/include', - '/usr/lib/i386-linux-gnu/gtk-unix-print-2.0', - '/usr/lib/i386-linux-gnu/glib-2.0/include', - # Fedora - '/usr/lib64/gtk-2.0/include', - '/usr/lib64/gtk-unix-print-2.0', - '/usr/lib64/glib-2.0/include', - '/usr/lib/gtk-2.0/include', - '/usr/lib/gtk-2.0/gtk-unix-print-2.0', - '/usr/lib/glib-2.0/include', - ], - - library_dirs=[ - os.path.join(CEF_BINARY, "lib"), - r'./../../client_handler/', - r'./../../subprocess/', # libcefpythonapp - r'./../../cpp_utils/' - ], - - # Static libraries only. Order is important, if library A depends on B, - # then B must be included before A. - libraries=[ - 'X11', - 'gobject-2.0', - 'glib-2.0', - 'gtk-x11-2.0', - # CEF and CEF Python libraries - 'cef_dll_wrapper', - 'cefpythonapp', - 'client_handler', - 'cpp_utils', - ], - - # When you put "./" in here, loading of libcef.so will only work when - # running scripts from the same directory that libcef.so resides in. - # runtime_library_dirs=[ - # './' - # ], - - extra_compile_args=COMPILE_OPTIMIZE_FLAGS, - extra_link_args=LINK_OPTIMIZE_FLAGS, - - # Defining macros: - # define_macros = [("UNICODE","1"), ("_UNICODE","1"), ] -)] - -setup( - name='cefpython_py%s' % PYTHON_VERSION, - cmdclass={'build_ext': build_ext}, - ext_modules=ext_modules -) diff --git a/src/windows/deprecated_32bit/LICENSE.txt b/src/windows/deprecated_32bit/LICENSE.txt deleted file mode 100644 index 45e6f23ea..000000000 --- a/src/windows/deprecated_32bit/LICENSE.txt +++ /dev/null @@ -1,39 +0,0 @@ -Copyright (c) 2012 The CEF Python authors. All rights -reserved. Website: http://code.google.com/p/cefpython/ - -This product includes the following third party libraries: -* Chromium Embedded Framework licensed under the BSD 3-clause - license. Website: http://code.google.com/p/chromiumembedded/ - -Redistribution and use in source and binary forms, with -or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of Google Inc. nor the name Chromium - Embedded Framework nor the name of CEF Python nor the - names of its contributors may be used to endorse or - promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/windows/deprecated_32bit/README.txt b/src/windows/deprecated_32bit/README.txt deleted file mode 100644 index 73fbae0cf..000000000 --- a/src/windows/deprecated_32bit/README.txt +++ /dev/null @@ -1,119 +0,0 @@ -Chromium Embedded Framework (CEF) Standard Binary Distribution for Windows -------------------------------------------------------------------------------- - -Date: August 08, 2014 - -CEF Version: 3.1650.1646 -CEF URL: http://chromiumembedded.googlecode.com/svn/branches/1650/cef3@1646 - -Chromium Verison: 31.0.1650.69 -Chromium URL: http://src.chromium.org/svn/branches/1650/src@241641 - -This distribution contains all components necessary to build and distribute an -application using CEF on the Windows platform. Please see the LICENSING -section of this document for licensing terms and conditions. - - -CONTENTS --------- - -cefclient Contains the cefclient sample application configured to build - using the files in this distribution. - -Debug Contains libcef.dll, libcef.lib and other components required to - build and run the debug version of CEF-based applications. By - default these files should be placed in the same directory as the - executable and will be copied there as part of the build process. - -include Contains all required CEF header files. - -libcef_dll Contains the source code for the libcef_dll_wrapper static library - that all applications using the CEF C++ API must link against. - -Release Contains libcef.dll, libcef.lib and other components required to - build and run the release version of CEF-based applications. By - default these files should be placed in the same directory as the - executable and will be copied there as part of the build process. - -Resources Contains resources required by libcef.dll. By default these files - should be placed in the same directory as libcef.dll. By default - these files should be placed in the same directory as libcef.dll - and will be copied there as part of the build process. - - -USAGE ------ - -Visual Studio 2012 and Visual Studio 2010: - Open the cefclient2010.sln solution in Visual Studio and build. - -Visual Studio 2008: - Open the cefclient2008.sln solution in Visual Studio and build. - -Visual Studio 2005: - 1. Open the cefclient.vcproj and libcef_dll_wrapper.vcproj files in a text - editor. Change Version="9.00" to Version="8.00". - 2. Open the cefclient2005.sln file in a text editor. Change "Version 9.00" to - "Version 8.00". - 3. Open the cefclient2005.sln solution in Visual Studio and build. - -Please visit the CEF Website for additional usage information. - -http://code.google.com/p/chromiumembedded - - -REDISTRIBUTION --------------- - -This binary distribution contains the below components. Components listed under -the "required" section must be redistributed with all applications using CEF. -Components listed under the "optional" section may be excluded if the related -features will not be used. - -Required components: - -* CEF core library - libcef.dll - -* Unicode support - icudt.dll - -Optional components: - -* Localized resources - locales/ - Note: Contains localized strings for WebKit UI controls. A .pak file is loaded - from this folder based on the CefSettings.locale value. Only configured - locales need to be distributed. If no locale is configured the default locale - of "en-US" will be used. Locale file loading can be disabled completely using - CefSettings.pack_loading_disabled. The locales folder path can be customized - using CefSettings.locales_dir_path. - -* Other resources - cef.pak - devtools_resources.pak - Note: Contains WebKit image and inspector resources. Pack file loading can be - disabled completely using CefSettings.pack_loading_disabled. The resources - directory path can be customized using CefSettings.resources_dir_path. - -* FFmpeg audio and video support - ffmpegsumo.dll - Note: Without this component HTML5 audio and video will not function. - -* Angle and Direct3D support - d3dcompiler_43.dll (required for Windows XP) - d3dcompiler_46.dll (required for Windows Vista and newer) - libEGL.dll - libGLESv2.dll - Note: Without these components HTML5 accelerated content like 2D canvas, 3D - CSS and WebGL will not function. - - -LICENSING ---------- - -The CEF project is BSD licensed. Please read the LICENSE.txt file included with -this binary distribution for licensing terms and conditions. Other software -included in this distribution is provided under other licenses. Please visit -"about:credits" in a CEF-based application for complete Chromium and third-party -licensing information. diff --git a/src/windows/deprecated_32bit/cefwindow.py b/src/windows/deprecated_32bit/cefwindow.py deleted file mode 100644 index fd0ac73ea..000000000 --- a/src/windows/deprecated_32bit/cefwindow.py +++ /dev/null @@ -1,205 +0,0 @@ -# Copyright (c) 2012-2014 The CEF Python authors. All rights reserved. -# License: New BSD License. -# Website: http://code.google.com/p/cefpython/ - -import win32gui -import win32con -import win32api -import time -import math -import os -import sys -import re - -if sys.version_info.major == 2: - from urllib import pathname2url as urllib_pathname2url -else: - from urllib.request import pathname2url as urllib_pathname2url - -g_debug = False -g_windows = {} # windowID(int): className -g_registeredClasses = {} - -def Debug(msg): - - if not g_debug: - return - msg = "cefwindow: "+str(msg) - print(msg) - with open(GetRealPath("debug.log"), "a") as file: - file.write(msg+"\n") - -def GetRealPath(file=None, encodeURL=False): - - # This function is defined in 2 files: cefpython.pyx and cefwindow.py, if you make changes edit both files. - # If file is None return current directory, without trailing slash. - - # encodeURL param - will call urllib.pathname2url(), only when file is empty (current dir) - # or is relative path ("test.html", "some/test.html"), we need to encode it before passing - # to CreateBrowser(), otherwise it is encoded by CEF internally and becomes (chinese characters): - # >> %EF%BF%97%EF%BF%80%EF%BF%83%EF%BF%A6 - # but should be: - # >> %E6%A1%8C%E9%9D%A2 - - if file is None: file = "" - if file.find("/") != 0 and file.find("\\") != 0 and not re.search(r"^[a-zA-Z]+:[/\\]?", file): - # Execute this block only when relative path ("test.html", "some\test.html") or file is empty (current dir). - # 1. find != 0 >> not starting with / or \ (/ - linux absolute path, \ - just to be sure) - # 2. not re.search >> not (D:\\ or D:/ or D: or http:// or ftp:// or file://), - # "D:" is also valid absolute path ("D:cefpython" in chrome becomes "file:///D:/cefpython/") - if hasattr(sys, "frozen"): path = os.path.dirname(sys.executable) - elif "__file__" in globals(): path = os.path.dirname(os.path.realpath(__file__)) - else: path = os.getcwd() - path = path + os.sep + file - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) # directory without trailing slash. - if encodeURL: - return urllib_pathname2url(path) - else: - return path - return file - -def CreateWindow(title, className, width, height, xpos=None, ypos=None, icon=None, windowProc=None): - - """ - for key in g_windows: - if g_windows[key] == className: - raise Exception("There was already created a window with that className: %s." - "Each created window must have an unique className." % className) - """ - - if not windowProc: - windowProc = {win32con.WM_CLOSE: WM_CLOSE} - - bigIcon = "" - smallIcon = "" - - if icon: - icon = GetRealPath(icon) - - # Load small and big icon. - # WNDCLASSEX (along with hIconSm) is not supported by pywin32, - # we need to use WM_SETICON message after window creation. - - # http://stackoverflow.com/questions/2234988/how-to-set-hicon-on-a-window-ico-with-multiple-sizes - # http://blog.barthe.ph/2009/07/17/wmseticon/ - - bigX = win32api.GetSystemMetrics(win32con.SM_CXICON) - bigY = win32api.GetSystemMetrics(win32con.SM_CYICON) - bigIcon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, bigX, bigY, win32con.LR_LOADFROMFILE) - smallX = win32api.GetSystemMetrics(win32con.SM_CXSMICON) - smallY = win32api.GetSystemMetrics(win32con.SM_CYSMICON) - smallIcon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, smallX, smallY, win32con.LR_LOADFROMFILE) - - wndclass = win32gui.WNDCLASS() - wndclass.hInstance = win32api.GetModuleHandle(None) - wndclass.lpszClassName = className - wndclass.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW - # win32con.CS_GLOBALCLASS - wndclass.hbrBackground = win32con.COLOR_WINDOW - wndclass.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) - wndclass.lpfnWndProc = windowProc - - #noinspection PyUnusedLocal - global g_registeredClasses - if not className in g_registeredClasses: - g_registeredClasses[className] = True - atomclass = win32gui.RegisterClass(wndclass) - Debug("win32gui.RegisterClass(%s)" % className) - - if xpos is None or ypos is None: - # Center window on the screen. - Debug("Centering window on the screen.") - screenx = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) - screeny = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) - xpos = int(math.floor((screenx - width) / 2)) - ypos = int(math.floor((screeny - height) / 2)) - if xpos < 0: xpos = 0 - if ypos < 0: ypos = 0 - - windowID = win32gui.CreateWindow(className, title, - win32con.WS_OVERLAPPEDWINDOW | win32con.WS_CLIPCHILDREN | win32con.WS_VISIBLE, - xpos, ypos, width, height, # xpos, ypos, width, height - 0, 0, wndclass.hInstance, None) - g_windows[windowID] = className - - if icon: - if bigIcon: - win32api.SendMessage(windowID, win32con.WM_SETICON, win32con.ICON_BIG, bigIcon) - if smallIcon: - win32api.SendMessage(windowID, win32con.WM_SETICON, win32con.ICON_SMALL, smallIcon) - - Debug("windowID = %s" % windowID) - return windowID - - -# Memory error when calling win32gui.DestroyWindow() -# after we called cefpython.CloseBrowser() - -def DestroyWindow(windowID): - - win32gui.DestroyWindow(windowID) - #className = GetWindowClassName(windowID) - #win32gui.UnregisterClass(className, None) - #del g_windows[windowID] # Let window with this className be created again. - - -def GetWindowClassName(windowID): - - for key in g_windows: - if key == windowID: - return g_windows[key] - -def MoveWindow(windowID, xpos=None, ypos=None, width=None, height=None, center=None): - - (left, top, right, bottom) = win32gui.GetWindowRect(windowID) - if xpos is None and ypos is None: - xpos = left - ypos = top - if width is None and height is None: - width = right - left - height = bottom - top - # Case: only ypos provided - if xpos is None and ypos is not None: - xpos = left - if ypos is None and xpos is not None: - ypos = top - # Case: only height provided - if not width: - width = right - left - if not height: - height = bottom - top - if center: - screenx = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) - screeny = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) - xpos = int(math.floor((screenx - width) / 2)) - ypos = int(math.floor((screeny - height) / 2)) - if xpos < 0: xpos = 0 - if ypos < 0: ypos = 0 - win32gui.MoveWindow(windowID, xpos, ypos, width, height, 1) - - -#noinspection PyUnusedLocal -def WM_CLOSE(windowID, msg, wparam, lparam): - - DestroyWindow(windowID) - win32gui.PostQuitMessage(0) - - -def GetLastError(): - - code = win32api.GetLastError() - return "(%d) %s" % (code, win32api.FormatMessage(code)) - -#noinspection PyUnusedLocal -def MessageLoop(className): - - while not win32gui.PumpWaitingMessages(): - time.sleep(0.001) - - -if __name__ == "__main__": - - g_debug = True - hwnd = CreateWindow("Test window", "testwindow", 800, 600) - MessageLoop("testwindow") \ No newline at end of file diff --git a/src/windows/deprecated_32bit/example.html b/src/windows/deprecated_32bit/example.html deleted file mode 100644 index 96bbac9b3..000000000 --- a/src/windows/deprecated_32bit/example.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - CEF Python 3 example (utf-8: ąś) - - - - - - -

Use mouse context menu to go Back/Forward in history navigation.

- - - -

Google Search

-https://www.google.com/ - - - -

User agent

- - - - -

Popup

- - window.open('example.html') - - - -

HTML5 video and accelerated content

- -HTML 5 video
- -Accelerated canvas
- -Accelerated layers
- - - -

Advanced example

- -See the wxpython.py script for an advanced usage of CEF Python 3 -features - including javascript bindings, js and python callbacks, -client handlers and others. - - -



-



-



-



- - - diff --git a/src/windows/deprecated_32bit/icon.ico b/src/windows/deprecated_32bit/icon.ico deleted file mode 100644 index 435045aec271dc2677d61810be947aa2420cd76d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198275 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 Jm0$eg{{a!zwfz79 diff --git a/src/windows/deprecated_32bit/prism.css b/src/windows/deprecated_32bit/prism.css deleted file mode 100644 index f94cca7c0..000000000 --- a/src/windows/deprecated_32bit/prism.css +++ /dev/null @@ -1,130 +0,0 @@ -/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+python */ -/** - * prism.js default theme for JavaScript, CSS and HTML - * Based on dabblet (http://dabblet.com) - * @author Lea Verou - */ - -code[class*="language-"], -pre[class*="language-"] { - color: black; - text-shadow: 0 1px white; - font-family: Consolas, Monaco, 'Andale Mono', monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, -code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { - text-shadow: none; - background: #b3d4fc; -} - -pre[class*="language-"]::selection, pre[class*="language-"] ::selection, -code[class*="language-"]::selection, code[class*="language-"] ::selection { - text-shadow: none; - background: #b3d4fc; -} - -@media print { - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #f5f2f0; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.builtin { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string, -.token.variable { - color: #a67f59; - background: hsla(0,0%,100%,.5); -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - -.token.function { - color: #DD4A68; -} - -.token.regex, -.token.important { - color: #e90; -} - -.token.important { - font-weight: bold; -} - -.token.entity { - cursor: help; -} - diff --git a/src/windows/deprecated_32bit/prism.js b/src/windows/deprecated_32bit/prism.js deleted file mode 100644 index ebaa4b428..000000000 --- a/src/windows/deprecated_32bit/prism.js +++ /dev/null @@ -1,5 +0,0 @@ -/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+python */ -self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content)):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(f instanceof r)){l.lastIndex=0;var h=l.exec(f);if(h){c&&(g=h[1].length);var d=h.index-1+g,h=h[0].slice(g),p=h.length,m=d+p,v=f.slice(0,d+1),y=f.slice(m+1),k=[u,1];v&&k.push(v);var b=new r(o,s?t.tokenize(h,s):h);k.push(b),y&&k.push(y),Array.prototype.splice.apply(a,k)}}}}return a},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[],r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(r&&r.length)for(var a,i=0;a=r[i++];)a(n)}}},n=t.Token=function(e,t){this.type=e,this.content=t};if(n.stringify=function(e,r,a){if("string"==typeof e)return e;if("[object Array]"==Object.prototype.toString.call(e))return e.map(function(t){return n.stringify(t,r,e)}).join("");var i={type:e.type,content:n.stringify(e.content,r,a),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:a};"comment"==i.type&&(i.attributes.spellcheck="true"),t.hooks.run("wrap",i);var o="";for(var l in i.attributes)o+=l+'="'+(i.attributes[l]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,a=n.code;self.postMessage(JSON.stringify(t.tokenize(a,t.languages[r]))),self.close()},!1),self.Prism):self.Prism;var r=document.getElementsByTagName("script");return r=r[r.length-1],r&&(t.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; -Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/gi,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/gi,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/gi,inside:{tag:{pattern:/|<\/script>/gi,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; -Prism.languages.python={comment:{pattern:/(^|[^\\])#.*?(\r?\n|$)/g,lookbehind:!0},string:/"""[\s\S]+?"""|("|')(\\?.)*?\1/g,keyword:/\b(as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|with|yield)\b/g,"boolean":/\b(True|False)\b/g,number:/\b-?(0x)?\d*\.?[\da-f]+\b/g,operator:/[-+]{1,2}|=?<|=?>|!|={1,2}|(&){1,2}|(&){1,2}|\|?\||\?|\*|\/|~|\^|%|\b(or|and|not)\b/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};; diff --git a/src/windows/deprecated_32bit/pygtk_.py b/src/windows/deprecated_32bit/pygtk_.py deleted file mode 100644 index 85e4d4f75..000000000 --- a/src/windows/deprecated_32bit/pygtk_.py +++ /dev/null @@ -1,196 +0,0 @@ -# An example of embedding CEF browser in PyGTK on Windows. -# Tested with PyGTK 2.24.10 - -import os, sys -libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.dll') -if os.path.exists(libcef_dll): - # Import a local module - if (2,7) <= sys.version_info < (2,8): - import cefpython_py27 as cefpython - elif (3,4) <= sys.version_info < (3,4): - import cefpython_py34 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython - -import pygtk -pygtk.require('2.0') -import gtk -import gobject - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (_exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[pygtk_.py]: WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class PyGTKExample: - mainWindow = None - container = None - browser = None - exiting = None - searchEntry = None - - def __init__(self): - gobject.timeout_add(10, self.OnTimer) - - self.mainWindow = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.mainWindow.connect('destroy', self.OnExit) - self.mainWindow.set_size_request(width=1024, height=768) - self.mainWindow.set_title('PyGTK CEF 3 example') - self.mainWindow.realize() - - self.container = gtk.DrawingArea() - self.container.set_property('can-focus', True) - self.container.connect('size-allocate', self.OnSize) - self.container.show() - - self.searchEntry = gtk.Entry() - # By default, clicking a GTK widget doesn't grab the focus away from a native Win32 control (browser). - self.searchEntry.connect('button-press-event', self.OnWidgetClick) - self.searchEntry.show() - - table = gtk.Table(3, 1, homogeneous=False) - self.mainWindow.add(table) - table.attach(self.CreateMenu(), 0, 1, 0, 1, yoptions=gtk.SHRINK) - table.attach(self.searchEntry, 0, 1, 1, 2, yoptions=gtk.SHRINK) - table.attach(self.container, 0, 1, 2, 3) - table.show() - - windowID = self.container.get_window().handle - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(windowID) - self.browser = cefpython.CreateBrowserSync(windowInfo, - browserSettings={}, - navigateUrl=GetApplicationPath('example.html')) - - self.mainWindow.show() - - # Browser took focus, we need to get it back and give to searchEntry. - self.mainWindow.get_window().focus() - self.searchEntry.grab_focus() - - def CreateMenu(self): - file = gtk.MenuItem('File') - file.show() - filemenu = gtk.Menu() - item = gtk.MenuItem('Open') - filemenu.append(item) - item.show() - item = gtk.MenuItem('Exit') - filemenu.append(item) - item.show() - file.set_submenu(filemenu) - about = gtk.MenuItem('About') - about.show() - menubar = gtk.MenuBar() - menubar.append(file) - menubar.append(about) - menubar.show() - return menubar - - def OnWidgetClick(self, widget, data): - self.mainWindow.get_window().focus() - - def OnTimer(self): - if self.exiting: - return False - cefpython.MessageLoopWork() - return True - - def OnFocusIn(self, widget, data): - # This function is currently not called by any of code, but if you would like - # for browser to have automatic focus add such line: - # self.mainWindow.connect('focus-in-event', self.OnFocusIn) - cefpython.WindowUtils.OnSetFocus(self.container.get_window().handle, 0, 0, 0) - - def OnSize(self, widget, sizeAlloc): - cefpython.WindowUtils.OnSize(self.container.get_window().handle, 0, 0, 0) - - def OnExit(self, widget, data=None): - self.exiting = True - gtk.main_quit() - -if __name__ == '__main__': - version = '.'.join(map(str, list(gtk.gtk_version))) - print('[pygtk_.py] GTK version: %s' % version) - - # Intercept python exceptions. Exit app immediately when exception - # happens on any of the threads. - sys.excepthook = ExceptHook - - # Application settings - settings = { - "debug": True, # cefpython debug messages in console and in log_file - "log_severity": cefpython.LOGSEVERITY_INFO, # LOGSEVERITY_VERBOSE - "log_file": GetApplicationPath("debug.log"), # Set to "" to disable - # This directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess"), - } - - cefpython.Initialize(settings) - - gobject.threads_init() # Timer for the message loop - PyGTKExample() - gtk.main() - - cefpython.Shutdown() diff --git a/src/windows/deprecated_32bit/pyqt.py b/src/windows/deprecated_32bit/pyqt.py deleted file mode 100644 index ae7d7a850..000000000 --- a/src/windows/deprecated_32bit/pyqt.py +++ /dev/null @@ -1,190 +0,0 @@ -# An example of embedding CEF browser in a PyQt4 application. -# Tested with PyQt 4.10.3 (Qt 4.8.5). - -import os, sys -libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.dll') -if os.path.exists(libcef_dll): - # Import a local module - if (2,7) <= sys.version_info < (2,8): - import cefpython_py27 as cefpython - elif (3,4) <= sys.version_info < (3,4): - import cefpython_py34 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython - -from PyQt4 import QtGui -from PyQt4 import QtCore - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[pyqt.py] WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class MainWindow(QtGui.QMainWindow): - mainFrame = None - - def __init__(self): - super(MainWindow, self).__init__(None) - self.createMenu() - self.mainFrame = MainFrame(self) - self.setCentralWidget(self.mainFrame) - self.resize(1024, 768) - self.setWindowTitle('PyQT CEF 3 example') - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - def createMenu(self): - menubar = self.menuBar() - filemenu = menubar.addMenu("&File") - filemenu.addAction(QtGui.QAction("Open", self)) - filemenu.addAction(QtGui.QAction("Exit", self)) - aboutmenu = menubar.addMenu("&About") - - def focusInEvent(self, event): - cefpython.WindowUtils.OnSetFocus(int(self.centralWidget().winId()), 0, 0, 0) - - def closeEvent(self, event): - self.mainFrame.browser.CloseBrowser() - -class MainFrame(QtGui.QWidget): - browser = None - - def __init__(self, parent=None): - super(MainFrame, self).__init__(parent) - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(int(self.winId())) - self.browser = cefpython.CreateBrowserSync(windowInfo, - browserSettings={}, - navigateUrl=GetApplicationPath("example.html")) - self.show() - - def moveEvent(self, event): - cefpython.WindowUtils.OnSize(int(self.winId()), 0, 0, 0) - - def resizeEvent(self, event): - cefpython.WindowUtils.OnSize(int(self.winId()), 0, 0, 0) - -class CefApplication(QtGui.QApplication): - timer = None - - def __init__(self, args): - super(CefApplication, self).__init__(args) - self.createTimer() - - def createTimer(self): - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.onTimer) - self.timer.start(10) - - def onTimer(self): - # The proper way of doing message loop should be: - # 1. In createTimer() call self.timer.start(0) - # 2. In onTimer() call MessageLoopWork() only when - # QtGui.QApplication.instance()->hasPendingEvents() returns False. - # But... there is a bug in Qt, hasPendingEvents() returns always true. - cefpython.MessageLoopWork() - - def stopTimer(self): - # Stop the timer after Qt message loop ended, calls to MessageLoopWork() - # should not happen anymore. - self.timer.stop() - -if __name__ == '__main__': - print("[pyqt.py] PyQt version: %s" % QtCore.PYQT_VERSION_STR) - print("[pyqt.py] QtCore version: %s" % QtCore.qVersion()) - - # Intercept python exceptions. Exit app immediately when exception - # happens on any of the threads. - sys.excepthook = ExceptHook - - # Application settings - settings = { - # "cache_path": "webcache/", # Disk cache - "debug": True, # cefpython debug messages in console and in log_file - "log_severity": cefpython.LOGSEVERITY_INFO, # LOGSEVERITY_VERBOSE - "log_file": GetApplicationPath("debug.log"), # Set to "" to disable. - # This directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess") - } - - # Command line switches set programmatically - switches = { - # "proxy-server": "socks5://127.0.0.1:8888", - # "enable-media-stream": "", - # "--invalid-switch": "" -> Invalid switch name - } - - cefpython.Initialize(settings, switches) - - app = CefApplication(sys.argv) - mainWindow = MainWindow() - mainWindow.show() - app.exec_() - app.stopTimer() - - # Need to destroy QApplication(), otherwise Shutdown() fails. - # Unset main window also just to be safe. - del mainWindow - del app - - cefpython.Shutdown() diff --git a/src/windows/deprecated_32bit/pyside.py b/src/windows/deprecated_32bit/pyside.py deleted file mode 100644 index 9a5f68fe0..000000000 --- a/src/windows/deprecated_32bit/pyside.py +++ /dev/null @@ -1,188 +0,0 @@ -# An example of embedding CEF Python in PySide application. - -import os, sys -libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.dll') -if os.path.exists(libcef_dll): - # Import a local module - if (2,7) <= sys.version_info < (2,8): - import cefpython_py27 as cefpython - elif (3,4) <= sys.version_info < (3,4): - import cefpython_py34 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython - -import PySide -from PySide import QtGui -from PySide import QtCore -import ctypes - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("cefpython: WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class MainWindow(QtGui.QMainWindow): - mainFrame = None - - def __init__(self): - super(MainWindow, self).__init__(None) - self.createMenu() - self.mainFrame = MainFrame(self) - self.setCentralWidget(self.mainFrame) - self.resize(1024, 768) - self.setWindowTitle('PySide example') - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - def createMenu(self): - menubar = self.menuBar() - filemenu = menubar.addMenu("&File") - filemenu.addAction(QtGui.QAction("Open", self)) - filemenu.addAction(QtGui.QAction("Exit", self)) - aboutmenu = menubar.addMenu("&About") - - def focusInEvent(self, event): - cefpython.WindowUtils.OnSetFocus(int(self.centralWidget().winIdFixed()), 0, 0, 0) - - def closeEvent(self, event): - self.mainFrame.browser.CloseBrowser() - -class MainFrame(QtGui.QWidget): - browser = None - - def __init__(self, parent=None): - super(MainFrame, self).__init__(parent) - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(int(self.winIdFixed())) - self.browser = cefpython.CreateBrowserSync(windowInfo, - browserSettings={}, - navigateUrl=GetApplicationPath("example.html")) - self.show() - - def winIdFixed(self): - # PySide bug: QWidget.winId() returns , - # there is no easy way to convert it to int. - try: - return int(self.winId()) - except: - if sys.version_info[0] == 2: - ctypes.pythonapi.PyCObject_AsVoidPtr.restype = ctypes.c_void_p - ctypes.pythonapi.PyCObject_AsVoidPtr.argtypes = [ctypes.py_object] - return ctypes.pythonapi.PyCObject_AsVoidPtr(self.winId()) - elif sys.version_info[0] == 3: - ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p - ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object] - return ctypes.pythonapi.PyCapsule_GetPointer(self.winId(), None) - - def moveEvent(self, event): - cefpython.WindowUtils.OnSize(int(self.winIdFixed()), 0, 0, 0) - - def resizeEvent(self, event): - cefpython.WindowUtils.OnSize(int(self.winIdFixed()), 0, 0, 0) - -class CefApplication(QtGui.QApplication): - timer = None - - def __init__(self, args): - super(CefApplication, self).__init__(args) - self.createTimer() - - def createTimer(self): - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.onTimer) - self.timer.start(10) - - def onTimer(self): - # The proper way of doing message loop should be: - # 1. In createTimer() call self.timer.start(0) - # 2. In onTimer() call MessageLoopWork() only when - # QtGui.QApplication.instance()->hasPendingEvents() returns False. - # But... there is a bug in Qt, hasPendingEvents() returns always true. - cefpython.MessageLoopWork() - - def stopTimer(self): - # Stop the timer after Qt message loop ended, calls to MessageLoopWork() - # should not happen anymore. - self.timer.stop() - -if __name__ == '__main__': - print("PySide version: %s" % PySide.__version__) - print("QtCore version: %s" % QtCore.__version__) - - sys.excepthook = ExceptHook - settings = {} - settings["log_file"] = GetApplicationPath("debug.log") - settings["log_severity"] = cefpython.LOGSEVERITY_INFO - settings["browser_subprocess_path"] = "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess") - cefpython.Initialize(settings) - - app = CefApplication(sys.argv) - mainWindow = MainWindow() - mainWindow.show() - app.exec_() - app.stopTimer() - - # Need to destroy QApplication(), otherwise Shutdown() fails. - # Unset main window also just to be safe. - del mainWindow - del app - - cefpython.Shutdown() diff --git a/src/windows/deprecated_32bit/pywin32.py b/src/windows/deprecated_32bit/pywin32.py deleted file mode 100644 index 747762696..000000000 --- a/src/windows/deprecated_32bit/pywin32.py +++ /dev/null @@ -1,151 +0,0 @@ -# Example of embedding CEF browser using the PyWin32 extension. -# Tested with pywin32 version 218. - -import os, sys -libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.dll') -if os.path.exists(libcef_dll): - # Import a local module - if (2,7) <= sys.version_info < (2,8): - import cefpython_py27 as cefpython - elif (3,4) <= sys.version_info < (3,4): - import cefpython_py34 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython - -import cefwindow -import win32con -import win32gui -import win32api -import time - -DEBUG = True - -# ----------------------------------------------------------------------------- -# Helper functions. - -def Log(msg): - print("[pywin32.py] %s" % str(msg)) - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[pywin32.py] WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -# ----------------------------------------------------------------------------- - -def CefAdvanced(): - sys.excepthook = ExceptHook - - appSettings = dict() - # appSettings["cache_path"] = "webcache/" # Disk cache - if DEBUG: - # cefpython debug messages in console and in log_file - appSettings["debug"] = True - cefwindow.g_debug = True - appSettings["log_file"] = GetApplicationPath("debug.log") - appSettings["log_severity"] = cefpython.LOGSEVERITY_INFO - appSettings["browser_subprocess_path"] = "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess") - cefpython.Initialize(appSettings) - - wndproc = { - win32con.WM_CLOSE: CloseWindow, - win32con.WM_DESTROY: QuitApplication, - win32con.WM_SIZE: cefpython.WindowUtils.OnSize, - win32con.WM_SETFOCUS: cefpython.WindowUtils.OnSetFocus, - win32con.WM_ERASEBKGND: cefpython.WindowUtils.OnEraseBackground - } - - browserSettings = dict() - browserSettings["universal_access_from_file_urls_allowed"] = True - browserSettings["file_access_from_file_urls_allowed"] = True - - if os.path.exists("icon.ico"): - icon = os.path.abspath("icon.ico") - else: - icon = "" - - windowHandle = cefwindow.CreateWindow(title="pywin32 example", - className="cefpython3_example", width=1024, height=768, - icon=icon, windowProc=wndproc) - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(windowHandle) - browser = cefpython.CreateBrowserSync(windowInfo, browserSettings, - navigateUrl=GetApplicationPath("example.html")) - cefpython.MessageLoop() - cefpython.Shutdown() - -def CloseWindow(windowHandle, message, wparam, lparam): - browser = cefpython.GetBrowserByWindowHandle(windowHandle) - browser.CloseBrowser() - return win32gui.DefWindowProc(windowHandle, message, wparam, lparam) - -def QuitApplication(windowHandle, message, wparam, lparam): - win32gui.PostQuitMessage(0) - return 0 - -def GetPywin32Version(): - fixed_file_info = win32api.GetFileVersionInfo(win32api.__file__, '\\') - return fixed_file_info['FileVersionLS'] >> 16 - -if __name__ == "__main__": - Log("pywin32 version = %s" % GetPywin32Version()) - CefAdvanced() diff --git a/src/windows/deprecated_32bit/smoke.css b/src/windows/deprecated_32bit/smoke.css deleted file mode 100644 index 2936c818a..000000000 --- a/src/windows/deprecated_32bit/smoke.css +++ /dev/null @@ -1,110 +0,0 @@ -.smoke-base { - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - visibility: hidden; - opacity: 0; -} - -.smoke-base.smoke-visible { - opacity: 1; - visibility: visible; -} - -.smokebg { - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; -} - -.smoke-base .dialog { - position: absolute; -} - -.dialog-prompt { - margin-top: 15px; - text-align: center; -} - -.dialog-buttons { - margin: 20px 0 5px 0 -} - -.smoke { -} - -.dialog-buttons button { - display: inline-block; - vertical-align: baseline; - cursor: pointer; - font-family: Menlo, 'Andale Mono', monospace; - font-style: normal; - text-decoration: none; - border: 0; - outline: 0; - margin: 0 5px; - -webkit-background-clip: padding-box; - font-size: 13px; - line-height: 13px; - font-weight: normal; - padding: 9px 12px; -} - -.dialog-prompt input { - margin: 0; - border: 0; - font-family: sans-serif; - outline: none; - font-family: Menlo, 'Andale Mono', monospace; - border: 1px solid #aaa; - width: 75%; - display: inline-block; - background-color: transparent; - font-size: 16px; - padding: 8px; -} - -.smoke-base { - background: rgba(0,0,0,.3); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#90000000,endColorstr=#900000000); -} - -.smoke-base .dialog { - top: 25%; - width: 40%; - left: 50%; - margin-left: -20%; -} - -.smoke-base .dialog-inner { - padding: 15px; - - color:#202020; -} - -.smoke { - background-color: rgba(255,255,255,0.95); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#ffffff); - box-shadow: 0 2px 8px #666; -} - - -.dialog-buttons button { - background-color: rgba(0,0,0,.85); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#222222,endColorstr=#222222); - border-radius: 0; - color: #fff; -} - -button.cancel { - background-color: rgba(0,0,0,.40); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#444444,endColorstr=#444444); -} - -.queue{ - display:none; -} diff --git a/src/windows/deprecated_32bit/smoke.min.js b/src/windows/deprecated_32bit/smoke.min.js deleted file mode 100644 index 61fb4f0c0..000000000 --- a/src/windows/deprecated_32bit/smoke.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(e,t){var n={smoketimeout:[],init:false,zindex:1e3,i:0,bodyload:function(e){var r=t.createElement("div");r.setAttribute("id","smoke-out-"+e);r.className="smoke-base";r.style.zIndex=n.zindex;n.zindex++;t.body.appendChild(r)},newdialog:function(){var t=(new Date).getTime();t=Math.random(1,99)+t;if(!n.init){n.listen(e,"load",function(){n.bodyload(t)})}else{n.bodyload(t)}return t},forceload:function(){},build:function(t,r){n.i++;r.stack=n.i;t=t.replace(/\n/g,"
");t=t.replace(/\r/g,"
");var i="",s="OK",o="Cancel",u="",a="",f;if(r.type==="prompt"){i='

"}if(r.params.ok){s=r.params.ok}if(r.params.cancel){o=r.params.cancel}if(r.params.classname){u=r.params.classname}if(r.type!=="signal"){a='
';if(r.type==="alert"){a+='"}else if(r.type==="quiz"){if(r.params.button_1){a+='"}if(r.params.button_2){a+='"}if(r.params.button_3){a+='"}if(r.params.button_cancel){a+='"}}else if(r.type==="prompt"||r.type==="confirm"){if(r.params.reverseButtons){a+='"+'"}else{a+='"+'"}}a+="
"}f='
'+'
'+'
'+t+i+a+"
"+"
";if(!n.init){n.listen(e,"load",function(){n.finishbuild(t,r,f)})}else{n.finishbuild(t,r,f)}},finishbuild:function(e,r,i){var s=t.getElementById("smoke-out-"+r.newid);s.className="smoke-base smoke-visible smoke-"+r.type;s.innerHTML=i;while(s.innerHTML===""){s.innerHTML=i}if(n.smoketimeout[r.newid]){clearTimeout(n.smoketimeout[r.newid])}n.listen(t.getElementById("smoke-bg-"+r.newid),"click",function(){n.destroy(r.type,r.newid);if(r.type==="prompt"||r.type==="confirm"||r.type==="quiz"){r.callback(false)}else if(r.type==="alert"&&typeof r.callback!=="undefined"){r.callback()}});switch(r.type){case"alert":n.finishbuildAlert(e,r,i);break;case"confirm":n.finishbuildConfirm(e,r,i);break;case"quiz":n.finishbuildQuiz(e,r,i);break;case"prompt":n.finishbuildPrompt(e,r,i);break;case"signal":n.finishbuildSignal(e,r,i);break;default:throw"Unknown type: "+r.type}},finishbuildAlert:function(r,i,s){n.listen(t.getElementById("alert-ok-"+i.newid),"click",function(){n.destroy(i.type,i.newid);if(typeof i.callback!=="undefined"){i.callback()}});t.onkeyup=function(t){if(!t){t=e.event}if(t.keyCode===13||t.keyCode===32||t.keyCode===27){n.destroy(i.type,i.newid);if(typeof i.callback!=="undefined"){i.callback()}}}},finishbuildConfirm:function(r,i,s){n.listen(t.getElementById("confirm-cancel-"+i.newid),"click",function(){n.destroy(i.type,i.newid);i.callback(false)});n.listen(t.getElementById("confirm-ok-"+i.newid),"click",function(){n.destroy(i.type,i.newid);i.callback(true)});t.onkeyup=function(t){if(!t){t=e.event}if(t.keyCode===13||t.keyCode===32){n.destroy(i.type,i.newid);i.callback(true)}else if(t.keyCode===27){n.destroy(i.type,i.newid);i.callback(false)}}},finishbuildQuiz:function(r,i,s){var o,u,a;n.listen(t.getElementById("quiz-cancel-"+i.newid),"click",function(){n.destroy(i.type,i.newid);i.callback(false)});if(o=t.getElementById("quiz-ok1-"+i.newid))n.listen(o,"click",function(){n.destroy(i.type,i.newid);i.callback(o.innerHTML)});if(u=t.getElementById("quiz-ok2-"+i.newid))n.listen(u,"click",function(){n.destroy(i.type,i.newid);i.callback(u.innerHTML)});if(a=t.getElementById("quiz-ok3-"+i.newid))n.listen(a,"click",function(){n.destroy(i.type,i.newid);i.callback(a.innerHTML)});t.onkeyup=function(t){if(!t){t=e.event}if(t.keyCode===27){n.destroy(i.type,i.newid);i.callback(false)}}},finishbuildPrompt:function(r,i,s){var o=t.getElementById("dialog-input-"+i.newid);setTimeout(function(){o.focus();o.select()},100);n.listen(t.getElementById("prompt-cancel-"+i.newid),"click",function(){n.destroy(i.type,i.newid);i.callback(false)});n.listen(t.getElementById("prompt-ok-"+i.newid),"click",function(){n.destroy(i.type,i.newid);i.callback(o.value)});t.onkeyup=function(t){if(!t){t=e.event}if(t.keyCode===13){n.destroy(i.type,i.newid);i.callback(o.value)}else if(t.keyCode===27){n.destroy(i.type,i.newid);i.callback(false)}}},finishbuildSignal:function(r,i,s){t.onkeyup=function(t){if(!t){t=e.event}if(t.keyCode===27){n.destroy(i.type,i.newid);if(typeof i.callback!=="undefined"){i.callback()}}};n.smoketimeout[i.newid]=setTimeout(function(){n.destroy(i.type,i.newid);if(typeof i.callback!=="undefined"){i.callback()}},i.timeout)},destroy:function(e,r){var i=t.getElementById("smoke-out-"+r);if(e!=="quiz"){var s=t.getElementById(e+"-ok-"+r)}var o=t.getElementById(e+"-cancel-"+r);i.className="smoke-base";if(s){n.stoplistening(s,"click",function(){});t.onkeyup=null}if(e==="quiz"){var u=t.getElementsByClassName("quiz-button");for(var a=0;a - - - - wxPython CEF 3 example (utf-8: ąś) - - - - - - - - - -Use the mouse context menu to go Back/Forward in history navigation. - -

Table of contents

-
    -
  1. Google search
  2. -
  3. User agent
  4. -
  5. Popups
  6. -
  7. HTML 5 video
  8. -
  9. Developer Tools
  10. -
  11. Downloads
  12. -
  13. HTML controls
  14. -
  15. Browser object
  16. -
  17. Frame object
  18. -
  19. Javascript bindings
  20. -
  21. Javascript callbacks
  22. -
  23. Python callbacks
  24. -
  25. Display handler
  26. -
  27. Keyboard handler
  28. -
  29. Request handler
  30. -
  31. Cookie tests
  32. -
  33. Load handler
  34. -
  35. Javascript Dialog handler
  36. -
  37. Other tests
  38. -
- - - - - - -

Google search

- -http://www.google.com/ - - - - - - - -

User agent

- - - - - - - - - -

Popups

- -
    -
  1. - window.open('wxpython.html') -
  2. -
  3. - target=_blank href="wxpython.html" -
  4. -
  5. - window.open('https://www.google.com/') -
  6. -
- -There are problems with keyboard in wxPython when popup windows are created -by CEF ( -Issue 80). To create the popup window of our own, the -LifespanHandler::OnBeforePopup callback was implemented. Note that this has -its implications, the popup window and parent window will not be able to -script each other. There will be no "window.opener" property available -in the popup window. - -See its source: - - - - -

CreateAnotherBrowser

- -This will create a window on its own and embed browser in it. -When using "window.open" the window is created implicitilly -by CEF. You can intercept such popup creation using the -OnBeforePopup callback in LifespanHandler. You can return -True in OnBeforePopup and create popup window on your own -using the CreateAnotherBrowser function. - - - - external.CreateAnotherBrowser() - - - - - - - -

HTML5 video and accelerated content

- - -HTML 5 video
- - -Accelerated canvas
- - -Accelerated layers
- - - - - - -

Developer Tools

- -You can open devtools popup window in a few different ways: -
    -
  1. Call Browser.ShowDevTools() method: - - external.ShowDevTools()
  2. -
  3. Through mouse context menu
  4. -
  5. Through F12 key which is handled in KeyboardHandler.OnKeyEvent
  6. -
- - - - - - -

Downloads

- -Download sample Ubuntu wallpapers:
- - https://cefpython.googlecode.com/files/ubuntu-wallpapers2.zip - -

-Notes: On Linux it seems that OnLoadError with errorCode = ERR_ABORTED -is called even for successful downloads, you can ignore this behavior. -The proper console messages about successful/aborted download originate -from C++ Browser process code, these are: -

- -
Browser: About to download file: ubuntu-wallpapers2.zip
-Browser: Download completed, saved to: /Downloads/ubuntu-wallpapers2.zip
-
- -If download was aborted the messages will be: - -
Browser: About to download file: ubuntu-wallpapers2.zip
-Browser: Download was cancelled
-
- -

-Additionally on Linux there are more errors reported by Chromium -about org.gnome.SessionManager.inhibit. These can be safely ignored as well. -

- -A download handler with callbacks like `OnBeforeDownload` and -`OnDownloadUpdated` may be exposed to CEF Python in the future. - - - - - - -

HTML controls

- -

Textarea

- -
- -

Inputs

-Text:
-Password:
- -

Select

- - -

Buttons

-Submit:
-Button:
- - - - - - -

Browser object

- -Tests for the Browser object methods. - -

GoBack

- -external.GoBack() - -

GoForward

- -external.GoForward() - -

LoadUrl, GetUrl

- - - window.open('data:text/html,Test#Browser.LoadUrl') - -

SetZoomLevel

-(You must set ApplicationSettings.auto_zooming = "" for SetZoomLevel calls -to work)
-external.SetZoomLevel(2.0)
-external.SetZoomLevel(1.0) - -

ReloadIgnoreCache, StopLoad

-Press F5 to reload page and ignore cache.
-Press Esc during webpage loading to abort.
- -Also, when Esc is pressed OnLoadError may get called. See how abort -of page loading or file download is handled: - - - - - - - -

Frame object

- -Tests for the Frame object methods. TODO. - - - - - - -

Javascript bindings

- -

PyPrint

- - - window.PyPrint('printing in python console from js') -
- -

Window properties

- -
jsBindings.SetProperty("pyProperty", "This was set in Python")
-jsBindings.SetProperty("pyConfig", ["This was set in Python",
-        {"name": "Nested dictionary", "isNested": True},
-        [1,"2", None]])
-
- - - window.alert(window.pyProperty)
- - window.alert(JSON.stringify(window.pyConfig)) -
- -

Print

- - - - external.Print('printing again from js') -
- -

TestAllTypes

- - - - external.TestAllTypes - (undefined, null, true, 1, - ((1<<31)>>>0), 2.14, 'Date not yet supported', 'string', - {key1: 1, key2: 2}, {key1: {'key1.1': 'nested object'}, 'key1.2': [1]}, - [1, 2], [1, [2.1, 'nested array']], [{key1: [{}]}]) -
- -

ExecuteFunction

- - - -
<script>
-function JavascriptAlert(message) { window.alert(message); }
-</script>
-
- - - - external.ExecuteFunction('JavascriptAlert', - 'python called from js and then js called from python', 0, [1,2]) -
- -

GetSource, GetText

- - - - - - - external.GetSource() -
- - external.GetText() - - - - - - -

Javascript callbacks

- -

TestJSCallback

- - - -
<script>
-function JSCallback(arg1) {
-    window.alert(arg1)
-}
-</script>
-
- - - - external.TestJSCallback(JSCallback) - -

TestJSCallbackComplexArguments

- - - -
<script>
-function JSCallback2() {
-    window.alert(JSON.stringify(arguments))
-}
-</script>
-
- - - - external.TestJSCallbackComplexArguments({"myCallback": JSCallback2}) - - - - - - - -

Python callbacks

- -

TestPythonCallback

- - - -
<script>
-function JSCallback3(pyCallback) {
-    pyCallback(1, 2.14, "string", [1, [2, {"key": "value"}]], {"list": [1,2]});
-}
-</script>
-
- - - - - - external.TestPythonCallback(JSCallback3) - - - - - - -

Display handler

- -

OnAddressChange

- -See messages in the console during loading of a webpage. - -

OnTitleChange

- -See messages in the console during loading of a webpage. - -

OnTooltip

- -See messages in the console when hovering over a google logo: -http://www.google.com/ - -

OnStatusMessage

- -See messages in the console when hovering over links. - -

OnConsoleMessage

- -Try this: - - http://patik.com/code/console-log-polyfill/ - - - - - - -

Keyboard handler

- -

- Press F5 to reload the page.
- On Linux it is required to click anywhere in the window first - so that keyboard focus is set. See Issue 77 in the CEF Python - Issue Tracker. -

- - - - - - - - - -

Request handler

- -

OnBeforeResourceLoad

- -See messages in the console. - -

OnResourceRedirect

- -Try this: - - http://tinyurl.com/google404redirect - -

GetAuthCredentials

- -Try this: - - http://browserspy.dk/password-ok.php - -

OnQuotaRequest

- - - - -
<script>
-function DoRequestQuota() {
-    // Request Quota (only for File System API)
-    try {
-        navigator.webkitPersistentStorage.requestQuota(PERSISTENT, 1024*1024,
-                function(bytes){ window.alert("Granted bytes: "+bytes);},
-                function(error){ window.alert(error); });
-    } catch(e) {
-        navigator.webkitPersistentStorage.requestQuota(1024*1024,
-                function(bytes){ window.alert("Granted bytes: "+bytes);},
-                function(error){ window.alert(error); });
-    }
-}
-</script>
-
- -Try this: - - https://googledrive.com/host/0B1di2XiBBfacMnhRRkI1YlotUEk/requestquota.html - -

OnProtocolExecution

- - -Try magnet link to download a torrent: - - magnet:?xt=urn:btih:a4224b45b27f436374391379cc5c7e629e2e5189 - -

_OnBeforePluginLoad

- -Try OnBeforePluginLoad() with Flash: - - http://www.adobe.com/software/flash/about/ - -

_OnCertificateError

- - -The url below won't be allowed. Click twice "Back" from context menu -to return back here after visiting that url:
- - https://testssl-expire.disig.sk/index.en.html -
- -This url will be allowed:
- - https://testssl-expire.disig.sk/index.en.html?allow=1 -
-After you've clicked the second url, the first one will now be allowed, -as this is the same domain and it's cached now. - -

OnRendererProcessTerminated

- -Try to terminate the "subprocess.exe" renderer process through -task manager. - -

OnPluginCrashed

- -No test for that yet. - - - - - - -

Cookie tests

- -See messages in the console. - -

GetCookieManager

- - - -RequestHandler.GetCookieManager() - an example of having an unique -cookie manager for each browser. -
    -
  1. Visit the url below and set some cookies. Use "Back" from - context menu to get back here (you might have to click "Back" - multiple times).
    - Visit it in the current browser:
    - - http://www.html-kit.com/tools/cookietester/ -
  2. -
  3. Open cookietester in a popup - a separate cookie manager is used:
    - - javascript:external.CreateAnotherBrowser('http://www.html-kit.com/tools/cookietester/') -
  4. -
- -

-Popup browsers created javascript's window.open share -the same renderer process and request context. If you want -to have separate cookie managers for popups created using -window.open then you have to implement the -LifespanHandler.`OnBeforePopup` callback. Return True in that -callback to cancel popup creation and instead create the -window on your own and embed browser in it. The CreateAnotherBrowser() -function from the wxpython example does that. -

- -

VisitAllCookies

- -Visit all cookies: -external.VisitAllCookies() -

- -Note: visit some http:// webpage first, otherwise cookie manager is not -yet created. -
- -

VisitUrlCookies

- -Visit a subset of cookies for the given url: - - external.VisitUrlCookies("http://www.html-kit.com/tools/cookietester/") -
- -

SetCookie

- -Set a cookie on html-kit.com: -external.SetCookie() -
-Go see the cookie that was created: - - http://www.html-kit.com/tools/cookietester/ - -

DeleteCookies

- -Delete the single cookie previously created via SetCookie(): - - external.DeleteCookies() -
-Go check if cookie was deleted: - - http://www.html-kit.com/tools/cookietester/ - - - - - - -

Load Handler

- -See messages in the console during loading of a webpage. - -

OnLoadingStateChange

- - -

OnLoadStart

- - -

OnLoadEnd

- - -

OnLoadError

- -After you see the custom error message you have to hit -twice the Back from the context menu, to get back to this page. -
-Try this: - - http://www.non-existent.nono/ -
- - - - - - - - -

Javascript Dialog Handler

- -See messages in the console. - -

OnJavascriptDialog

- - - window.alert('Test js dialog handler') - - -

OnBeforeUnloadJavascriptDialog

- - - -
<script>
-function TestOnBeforeUnloadJavascriptDialog() {
-    window.onbeforeunload = function() {
-        return 'Testing the OnBeforeUnloadJavascriptDialog() callback';
-    }
-    location.href = "wxpython.html";
-}
-</script>
-
- - - TestOnBeforeUnloadJavascriptDialog() - - -

OnResetJavascriptDialogState

- - -

OnJavascriptDialogClosed

- - - - - - - -

Other tests

- -

HTTPS caching with SSL certificate errors

-Set ApplicationSettings["ignore_certificate_errors"] to True. - - - - - - - - - - - - - diff --git a/src/windows/deprecated_32bit/wxpython.py b/src/windows/deprecated_32bit/wxpython.py deleted file mode 100644 index ed6bb44bd..000000000 --- a/src/windows/deprecated_32bit/wxpython.py +++ /dev/null @@ -1,906 +0,0 @@ -# An example of embedding CEF browser in wxPython on Windows. -# Tested with wxPython 2.8.12.1 and 3.0.2.0. - -import os, sys -libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.dll') -if os.path.exists(libcef_dll): - # Import a local module - if (2,7) <= sys.version_info < (2,8): - import cefpython_py27 as cefpython - elif (3,4) <= sys.version_info < (3,4): - import cefpython_py34 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython - -import wx -import time -import re -import uuid -import platform -import inspect -import struct - -# ----------------------------------------------------------------------------- -# Globals - -g_applicationSettings = None -g_browserSettings = None -g_commandLineSwitches = None - -# Which method to use for message loop processing. -# EVT_IDLE - wx application has priority -# EVT_TIMER - cef browser has priority (default) -# It seems that Flash content behaves better when using a timer. -# Not sure if using EVT_IDLE is correct, it doesn't work on Linux, -# on Windows it works fine. See also the post by Robin Dunn: -# https://groups.google.com/d/msg/wxpython-users/hcNdMEx8u48/MD5Jgbm_k1kJ -USE_EVT_IDLE = False # If False then Timer will be used - -TEST_EMBEDDING_IN_PANEL = True - -# ----------------------------------------------------------------------------- - -def GetApplicationPath(file=None): - import re, os, platform - # On Windows after downloading file and calling Browser.GoForward(), - # current working directory is set to %UserProfile%. - # Calling os.path.dirname(os.path.realpath(__file__)) - # returns for eg. "C:\Users\user\Downloads". A solution - # is to cache path on first call. - if not hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # If file is None return current directory without trailing slash. - if file is None: - file = "" - # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = GetApplicationPath.dir + os.sep + file - if platform.system() == "Windows": - path = re.sub(r"[/\\]+", re.escape(os.sep), path) - path = re.sub(r"[/\\]+$", "", path) - return path - return str(file) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) - except: - print("[wxpython.py] WARNING: failed writing to error file: %s" % ( - errorFile)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg+"\n") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) - -class MainFrame(wx.Frame): - browser = None - mainPanel = None - - def GetHandleForBrowser(self): - if self.mainPanel: - return self.mainPanel.GetHandle() - else: - return self.GetHandle() - - def __init__(self, url=None, popup=False): - if popup: - title = "wxPython Popup" - else: - title = "wxPython CEF 3 example" - wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, - title=title) - size=(800,600) - - # This is an optional code to enable High DPI support. - if "auto_zooming" in g_applicationSettings \ - and g_applicationSettings["auto_zooming"] == "system_dpi": - # This utility function will adjust width/height using - # OS DPI settings. For 800/600 with Win7 DPI settings - # being set to "Larger 150%" will return 1200/900. - size = cefpython.DpiAware.CalculateWindowSize(size[0], size[1]) - - self.SetSize(size) - - if not url: - url = "file://"+GetApplicationPath("wxpython.html") - # Test hash in url. - # url += "#test-hash" - - self.CreateMenu() - - if TEST_EMBEDDING_IN_PANEL: - print("Embedding in a wx.Panel!") - # You also have to set the wx.WANTS_CHARS style for - # all parent panels/controls, if it's deeply embedded. - self.mainPanel = wx.Panel(self, style=wx.WANTS_CHARS) - - # Global client callbacks must be set before browser is created. - self.clientHandler = ClientHandler() - cefpython.SetGlobalClientCallback("OnCertificateError", - self.clientHandler._OnCertificateError) - cefpython.SetGlobalClientCallback("OnBeforePluginLoad", - self.clientHandler._OnBeforePluginLoad) - cefpython.SetGlobalClientCallback("OnAfterCreated", - self.clientHandler._OnAfterCreated) - - windowInfo = cefpython.WindowInfo() - windowInfo.SetAsChild(self.GetHandleForBrowser()) - self.browser = cefpython.CreateBrowserSync(windowInfo, - browserSettings=g_browserSettings, - navigateUrl=url) - - self.clientHandler.mainBrowser = self.browser - self.browser.SetClientHandler(self.clientHandler) - - jsBindings = cefpython.JavascriptBindings( - bindToFrames=False, bindToPopups=True) - jsBindings.SetFunction("PyPrint", PyPrint) - jsBindings.SetProperty("pyProperty", "This was set in Python") - jsBindings.SetProperty("pyConfig", ["This was set in Python", - {"name": "Nested dictionary", "isNested": True}, - [1,"2", None]]) - self.javascriptExternal = JavascriptExternal(self.browser) - jsBindings.SetObject("external", self.javascriptExternal) - jsBindings.SetProperty("sources", GetSources()) - self.browser.SetJavascriptBindings(jsBindings) - - if self.mainPanel: - self.mainPanel.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) - self.mainPanel.Bind(wx.EVT_SIZE, self.OnSize) - else: - self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) - self.Bind(wx.EVT_SIZE, self.OnSize) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - if USE_EVT_IDLE and not popup: - # Bind EVT_IDLE only for the main application frame. - print("Using EVT_IDLE to execute the CEF message loop work") - self.Bind(wx.EVT_IDLE, self.OnIdle) - - def CreateMenu(self): - filemenu = wx.Menu() - filemenu.Append(1, "Open") - exit = filemenu.Append(2, "Exit") - self.Bind(wx.EVT_MENU, self.OnClose, exit) - aboutmenu = wx.Menu() - aboutmenu.Append(1, "CEF Python") - menubar = wx.MenuBar() - menubar.Append(filemenu,"&File") - menubar.Append(aboutmenu, "&About") - self.SetMenuBar(menubar) - - def OnSetFocus(self, event): - cefpython.WindowUtils.OnSetFocus(self.GetHandleForBrowser(), 0, 0, 0) - - def OnSize(self, event): - cefpython.WindowUtils.OnSize(self.GetHandleForBrowser(), 0, 0, 0) - - def OnClose(self, event): - # Remove all CEF browser references so that browser is closed - # cleanly. Otherwise there may be issues for example with cookies - # not being flushed to disk when closing app immediately - # (Issue 158). - del self.javascriptExternal.mainBrowser - del self.clientHandler.mainBrowser - del self.browser - - # Destroy wx frame, this will complete the destruction of CEF browser - self.Destroy() - - # In wx.chromectrl calling browser.CloseBrowser and/or self.Destroy - # may cause crashes when embedding multiple browsers in tab - # (Issue 107). In such case instead of calling CloseBrowser/Destroy - # try this code: - # | self.browser.ParentWindowWillClose() - # | event.Skip() - - def OnIdle(self, event): - cefpython.MessageLoopWork() - -def PyPrint(message): - print("[wxpython.py] PyPrint: "+message) - -class JavascriptExternal: - mainBrowser = None - stringVisitor = None - - def __init__(self, mainBrowser): - self.mainBrowser = mainBrowser - - def GoBack(self): - self.mainBrowser.GoBack() - - def GoForward(self): - self.mainBrowser.GoForward() - - def SetZoomLevel(self, zoomLevel): - self.mainBrowser.SetZoomLevel(zoomLevel) - - def CreateAnotherBrowser(self, url=None): - frame = MainFrame(url=url) - frame.Show() - - def Print(self, message): - print("[wxpython.py] Print: "+message) - - def TestAllTypes(self, *args): - print("[wxpython.py] TestAllTypes: "+str(args)) - - def ExecuteFunction(self, *args): - self.mainBrowser.ExecuteFunction(*args) - - def TestJSCallback(self, jsCallback): - print("[wxpython.py] jsCallback.GetFunctionName() = %s"\ - % jsCallback.GetFunctionName()) - print("[wxpython.py] jsCallback.GetFrame().GetIdentifier() = %s" % \ - jsCallback.GetFrame().GetIdentifier()) - jsCallback.Call("This message was sent from python using js callback") - - def TestJSCallbackComplexArguments(self, jsObject): - jsCallback = jsObject["myCallback"]; - jsCallback.Call(1, None, 2.14, "string", ["list", ["nested list", \ - {"nested object":None}]], \ - {"nested list next":[{"deeply nested object":1}]}) - - def TestPythonCallback(self, jsCallback): - jsCallback.Call(self.PyCallback) - - def PyCallback(self, *args): - message = "PyCallback() was executed successfully! "\ - "Arguments: %s" % str(args) - print("[wxpython.py] "+message) - self.mainBrowser.GetMainFrame().ExecuteJavascript( - "window.alert(\"%s\")" % message) - - def GetSource(self): - # Must keep a strong reference to the StringVisitor object - # during the visit. - self.stringVisitor = StringVisitor() - self.mainBrowser.GetMainFrame().GetSource(self.stringVisitor) - - def GetText(self): - # Must keep a strong reference to the StringVisitor object - # during the visit. - self.stringVisitor = StringVisitor() - self.mainBrowser.GetMainFrame().GetText(self.stringVisitor) - - def ShowDevTools(self): - print("[wxpython.py] external.ShowDevTools called") - self.mainBrowser.ShowDevTools() - - # ------------------------------------------------------------------------- - # Cookies - # ------------------------------------------------------------------------- - cookieVisitor = None - - def VisitAllCookies(self): - # Need to keep the reference alive. - self.cookieVisitor = CookieVisitor() - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - " the cookietester website first and create some cookies") - return - cookieManager.VisitAllCookies(self.cookieVisitor) - - def VisitUrlCookies(self): - # Need to keep the reference alive. - self.cookieVisitor = CookieVisitor() - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - " the cookietester website first and create some cookies") - return - cookieManager.VisitUrlCookies( - "http://www.html-kit.com/tools/cookietester/", - False, self.cookieVisitor) - # .www.html-kit.com - - def SetCookie(self): - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - "the cookietester website first and create some cookies") - return - cookie = cefpython.Cookie() - cookie.SetName("Created_Via_Python") - cookie.SetValue("yeah really") - cookieManager.SetCookie("http://www.html-kit.com/tools/cookietester/", - cookie) - print("\n[wxpython.py] Cookie created! Visit html-kit cookietester to"\ - " see it") - - def DeleteCookies(self): - cookieManager = self.mainBrowser.GetUserData("cookieManager") - if not cookieManager: - print("\n[wxpython.py] Cookie manager not yet created! Visit"\ - " the cookietester website first and create some cookies") - return - cookieManager.DeleteCookies( - "http://www.html-kit.com/tools/cookietester/", - "Created_Via_Python") - print("\n[wxpython.py] Cookie deleted! Visit html-kit cookietester "\ - "to see the result") - -class StringVisitor: - def Visit(self, string): - print("\n[wxpython.py] StringVisitor.Visit(): string:") - print("--------------------------------") - print(string) - print("--------------------------------") - -class CookieVisitor: - def Visit(self, cookie, count, total, deleteCookie): - if count == 0: - print("\n[wxpython.py] CookieVisitor.Visit(): total cookies: %s"\ - % total) - print("\n[wxpython.py] CookieVisitor.Visit(): cookie:") - print(" "+str(cookie.Get())) - # True to continue visiting cookies - return True - -class ClientHandler: - mainBrowser = None # May be None for global client callbacks. - - def __init__(self): - pass - - # ------------------------------------------------------------------------- - # DisplayHandler - # ------------------------------------------------------------------------- - - def OnAddressChange(self, browser, frame, url): - print("[wxpython.py] DisplayHandler::OnAddressChange()") - print(" url = %s" % url) - - def OnTitleChange(self, browser, title): - print("[wxpython.py] DisplayHandler::OnTitleChange()") - print(" title = %s" % title) - - def OnTooltip(self, browser, textOut): - # OnTooltip not yet implemented (both Linux and Windows), - # will be fixed in next CEF release, see Issue 783: - # https://code.google.com/p/chromiumembedded/issues/detail?id=783 - print("[wxpython.py] DisplayHandler::OnTooltip()") - print(" text = %s" % textOut[0]) - - statusMessageCount = 0 - def OnStatusMessage(self, browser, value): - if not value: - # Do not notify in the console about empty statuses. - return - self.statusMessageCount += 1 - if self.statusMessageCount > 3: - # Do not spam too much. - return - print("[wxpython.py] DisplayHandler::OnStatusMessage()") - print(" value = %s" % value) - - def OnConsoleMessage(self, browser, message, source, line): - print("[wxpython.py] DisplayHandler::OnConsoleMessage()") - print(" message = %s" % message) - print(" source = %s" % source) - print(" line = %s" % line) - - # ------------------------------------------------------------------------- - # KeyboardHandler - # ------------------------------------------------------------------------- - - def OnPreKeyEvent(self, browser, event, eventHandle, - isKeyboardShortcutOut): - print("[wxpython.py] KeyboardHandler::OnPreKeyEvent()") - - def OnKeyEvent(self, browser, event, eventHandle): - if event["type"] == cefpython.KEYEVENT_KEYUP: - # OnKeyEvent is called twice for F5/Esc keys, with event - # type KEYEVENT_RAWKEYDOWN and KEYEVENT_KEYUP. - # Normal characters a-z should have KEYEVENT_CHAR. - return False - print("[wxpython.py] KeyboardHandler::OnKeyEvent()") - print(" type=%s" % event["type"]) - print(" modifiers=%s" % event["modifiers"]) - print(" windows_key_code=%s" % event["windows_key_code"]) - print(" native_key_code=%s" % event["native_key_code"]) - print(" is_system_key=%s" % event["is_system_key"]) - print(" character=%s" % event["character"]) - print(" unmodified_character=%s" % event["unmodified_character"]) - print(" focus_on_editable_field=%s" \ - % event["focus_on_editable_field"]) - linux = (platform.system() == "Linux") - windows = (platform.system() == "Windows") - # F5 - if (linux and event["native_key_code"] == 71) \ - or (windows and event["windows_key_code"] == 116): - print("[wxpython.py] F5 pressed, calling" - " browser.ReloadIgnoreCache()") - browser.ReloadIgnoreCache() - return True - # Escape - if (linux and event["native_key_code"] == 9) \ - or (windows and event["windows_key_code"] == 27): - print("[wxpython.py] Esc pressed, calling browser.StopLoad()") - browser.StopLoad() - return True - # F12 - if (linux and event["native_key_code"] == 96) \ - or (windows and event["windows_key_code"] == 123): - print("[wxpython.py] F12 pressed, calling" - " browser.ShowDevTools()") - browser.ShowDevTools() - return True - return False - - # ------------------------------------------------------------------------- - # RequestHandler - # ------------------------------------------------------------------------- - - def OnBeforeBrowse(self, browser, frame, request, isRedirect): - print("[wxpython.py] RequestHandler::OnBeforeBrowse()") - print(" url = %s" % request.GetUrl()[:100]) - # Handle "magnet:" links. - if request.GetUrl().startswith("magnet:"): - print("[wxpython.p] RequestHandler::OnBeforeBrowse(): " - "magnet link clicked, cancelling browse request") - return True - return False - - def OnBeforeResourceLoad(self, browser, frame, request): - print("[wxpython.py] RequestHandler::OnBeforeResourceLoad()") - print(" url = %s" % request.GetUrl()[:100]) - return False - - def OnResourceRedirect(self, browser, frame, oldUrl, newUrlOut, request): - print("[wxpython.py] RequestHandler::OnResourceRedirect()") - print(" old url = %s" % oldUrl[:100]) - print(" new url = %s" % newUrlOut[0][:100]) - - def GetAuthCredentials(self, browser, frame, isProxy, host, port, realm, - scheme, callback): - # This callback is called on the IO thread, thus print messages - # may not be visible. - print("[wxpython.py] RequestHandler::GetAuthCredentials()") - print(" host = %s" % host) - print(" realm = %s" % realm) - callback.Continue(username="test", password="test") - return True - - def OnQuotaRequest(self, browser, originUrl, newSize, callback): - print("[wxpython.py] RequestHandler::OnQuotaRequest()") - print(" origin url = %s" % originUrl) - print(" new size = %s" % newSize) - callback.Continue(True) - return True - - def GetCookieManager(self, browser, mainUrl): - # Create unique cookie manager for each browser. - # You must set the "unique_request_context_per_browser" - # application setting to True for the cookie manager - # to work. - # Return None to have one global cookie manager for - # all CEF browsers. - if not browser: - # The browser param may be empty in some exceptional - # case, see docs. - return None - cookieManager = browser.GetUserData("cookieManager") - if cookieManager: - return cookieManager - else: - print("[wxpython.py] RequestHandler::GetCookieManager():"\ - " created cookie manager") - cookieManager = cefpython.CookieManager.CreateManager("") - if "cache_path" in g_applicationSettings: - path = g_applicationSettings["cache_path"] - # path = os.path.join(path, "cookies_browser_{}".format( - # browser.GetIdentifier())) - cookieManager.SetStoragePath(path) - browser.SetUserData("cookieManager", cookieManager) - return cookieManager - - def OnProtocolExecution(self, browser, url, allowExecutionOut): - # There's no default implementation for OnProtocolExecution on Linux, - # you have to make OS system call on your own. You probably also need - # to use LoadHandler::OnLoadError() when implementing this on Linux. - print("[wxpython.py] RequestHandler::OnProtocolExecution()") - print(" url = %s" % url) - if url.startswith("magnet:"): - print("[wxpython.py] Magnet link allowed!") - allowExecutionOut[0] = True - - def _OnBeforePluginLoad(self, browser, mimeType, pluginUrl, topOriginUrl, - info): - # This is a global callback set using SetGlobalClientCallback(). - # Plugins are loaded on demand, only when website requires it, - # the same plugin may be called multiple times. - # This callback is called on various threads, thus print messages - # may not be visible. - print("[wxpython.py] RequestHandler::_OnBeforePluginLoad()") - print(" mimeType = %s" % mimeType) - print(" pluginUrl = %s" % pluginUrl) - print(" topOriginUrl = %s" % topOriginUrl) - print(" info.GetName() = %s" % info.GetName()) - print(" info.GetPath() = %s" % info.GetPath()) - print(" info.GetVersion() = %s" % info.GetVersion()) - print(" info.GetDescription() = %s" % info.GetDescription()) - # False to allow, True to block plugin. - return False - - def _OnCertificateError(self, certError, requestUrl, callback): - # This is a global callback set using SetGlobalClientCallback(). - print("[wxpython.py] RequestHandler::_OnCertificateError()") - print(" certError = %s" % certError) - print(" requestUrl = %s" % requestUrl) - if requestUrl == "https://testssl-expire.disig.sk/index.en.html": - print(" Not allowed!") - return False - if requestUrl \ - == "https://testssl-expire.disig.sk/index.en.html?allow=1": - print(" Allowed!") - callback.Continue(True) - return True - return False - - def OnRendererProcessTerminated(self, browser, status): - print("[wxpython.py] RequestHandler::OnRendererProcessTerminated()") - statuses = { - cefpython.TS_ABNORMAL_TERMINATION: "TS_ABNORMAL_TERMINATION", - cefpython.TS_PROCESS_WAS_KILLED: "TS_PROCESS_WAS_KILLED", - cefpython.TS_PROCESS_CRASHED: "TS_PROCESS_CRASHED" - } - statusName = "Unknown" - if status in statuses: - statusName = statuses[status] - print(" status = %s" % statusName) - - def OnPluginCrashed(self, browser, pluginPath): - print("[wxpython.py] RequestHandler::OnPluginCrashed()") - print(" plugin path = %s" % pluginPath) - - # ------------------------------------------------------------------------- - # LoadHandler - # ------------------------------------------------------------------------- - - def OnLoadingStateChange(self, browser, isLoading, canGoBack, - canGoForward): - print("[wxpython.py] LoadHandler::OnLoadingStateChange()") - print(" isLoading = %s, canGoBack = %s, canGoForward = %s" \ - % (isLoading, canGoBack, canGoForward)) - - def OnLoadStart(self, browser, frame): - print("[wxpython.py] LoadHandler::OnLoadStart()") - print(" frame url = %s" % frame.GetUrl()[:100]) - - def OnLoadEnd(self, browser, frame, httpStatusCode): - print("[wxpython.py] LoadHandler::OnLoadEnd()") - print(" frame url = %s" % frame.GetUrl()[:100]) - # For file:// urls the status code = 0 - print(" http status code = %s" % httpStatusCode) - # Tests for the Browser object methods - self._Browser_LoadUrl(browser) - - def _Browser_LoadUrl(self, browser): - if browser.GetUrl() == "data:text/html,Test#Browser.LoadUrl": - browser.LoadUrl("file://"+GetApplicationPath("wxpython.html")) - - def OnLoadError(self, browser, frame, errorCode, errorTextList, failedUrl): - print("[wxpython.py] LoadHandler::OnLoadError()") - print(" frame url = %s" % frame.GetUrl()[:100]) - print(" error code = %s" % errorCode) - print(" error text = %s" % errorTextList[0]) - print(" failed url = %s" % failedUrl) - # Handle ERR_ABORTED error code, to handle the following cases: - # 1. Esc key was pressed which calls browser.StopLoad() in OnKeyEvent - # 2. Download of a file was aborted - # 3. Certificate error - if errorCode == cefpython.ERR_ABORTED: - print("[wxpython.py] LoadHandler::OnLoadError(): Ignoring load " - "error: Esc was pressed or file download was aborted, " - "or there was certificate error") - return; - customErrorMessage = "My custom error message!" - frame.LoadUrl("data:text/html,%s" % customErrorMessage) - - # ------------------------------------------------------------------------- - # LifespanHandler - # ------------------------------------------------------------------------- - - # ** This callback is executed on the IO thread ** - # Empty place-holders: popupFeatures, client. - def OnBeforePopup(self, browser, frame, targetUrl, targetFrameName, - targetDisposition, userGesture, - popupFeatures, windowInfo, client, browserSettings, - noJavascriptAccess): - print("[wxpython.py] LifespanHandler::OnBeforePopup()") - print(" targetUrl = %s" % targetUrl) - - # Custom browser settings for popups: - # > browserSettings[0] = {"plugins_disabled": True} - - # Set WindowInfo object: - # > windowInfo[0] = cefpython.WindowInfo() - - # On Windows there are keyboard problems in popups, when popup - # is created using "window.open" or "target=blank". This issue - # occurs only in wxPython. PyGTK or PyQt do not require this fix. - # The solution is to create window explicitilly, and not depend - # on CEF to create window internally. See Issue 80 for details: - # https://github.com/cztomczak/cefpython/issues/80 - - # If you set allowPopups=True then CEF will create popup window. - # The wx.Frame cannot be created here, as this callback is - # executed on the IO thread. Window should be created on the UI - # thread. One solution is to call cefpython.CreateBrowser() - # which runs asynchronously and can be called on any thread. - # The other solution is to post a task on the UI thread, so - # that cefpython.CreateBrowserSync() can be used. - - # Note that if you return True and create the popup window yourself, - # then the popup window and parent window will not be able to script - # each other. There will be no "window.opener" property available - # in the popup window. - - cefpython.PostTask(cefpython.TID_UI, self._CreatePopup, targetUrl) - - allowPopups = False - return not allowPopups - - def _CreatePopup(self, url): - frame = MainFrame(url=url, popup=True) - frame.Show() - - def _OnAfterCreated(self, browser): - # This is a global callback set using SetGlobalClientCallback(). - print("[wxpython.py] LifespanHandler::_OnAfterCreated()") - print(" browserId=%s" % browser.GetIdentifier()) - - def DoClose(self, browser): - print("[wxpython.py] LifespanHandler::DoClose()") - print(" browserId=%s" % browser.GetIdentifier()) - - def OnBeforeClose(self, browser): - print("[wxpython.py] LifespanHandler::OnBeforeClose") - print(" browserId=%s" % browser.GetIdentifier()) - - # ------------------------------------------------------------------------- - # JavascriptDialogHandler - # ------------------------------------------------------------------------- - - def OnJavascriptDialog(self, browser, originUrl, dialogType, - messageText, defaultPromptText, callback, - suppressMessage): - print("[wxpython.py] JavascriptDialogHandler::OnJavascriptDialog()") - print(" originUrl="+originUrl) - print(" dialogType="+str(dialogType)) - print(" messageText="+messageText) - print(" defaultPromptText="+defaultPromptText) - # If you want to suppress the javascript dialog: - # suppressMessage[0] = True - return False - - def OnBeforeUnloadJavascriptDialog(self, browser, messageText, isReload, - callback): - print("[wxpython.py] OnBeforeUnloadJavascriptDialog()") - print(" messageText="+messageText) - print(" isReload="+str(isReload)) - # Return True if the application will use a custom dialog: - # callback.Continue(allow=True, userInput="") - # return True - return False - - def OnResetJavascriptDialogState(self, browser): - print("[wxpython.py] OnResetDialogState()") - - def OnJavascriptDialogClosed(self, browser): - print("[wxpython.py] OnDialogClosed()") - - -class MyApp(wx.App): - timer = None - timerID = 1 - mainFrame = None - - def OnInit(self): - if not USE_EVT_IDLE: - print("[wxpython.py] Using TIMER to run CEF message loop") - self.CreateTimer() - self.mainFrame = MainFrame() - self.SetTopWindow(self.mainFrame) - self.mainFrame.Show() - return True - - def CreateTimer(self): - # See "Making a render loop": - # http://wiki.wxwidgets.org/Making_a_render_loop - # Another approach is to use EVT_IDLE in MainFrame, - # see which one fits you better. - self.timer = wx.Timer(self, self.timerID) - self.timer.Start(10) # 10ms - wx.EVT_TIMER(self, self.timerID, self.OnTimer) - - def OnTimer(self, event): - cefpython.MessageLoopWork() - - def OnExit(self): - # When app.MainLoop() returns, MessageLoopWork() should - # not be called anymore. - print("[wxpython.py] MyApp.OnExit") - if not USE_EVT_IDLE: - self.timer.Stop() - - -def GetSources(): - # Get sources of all python functions and methods from this file. - # This is to provide sources preview to wxpython.html. - # The dictionary of functions is binded to "window.sources". - thisModule = sys.modules[__name__] - functions = inspect.getmembers(thisModule, inspect.isfunction) - classes = inspect.getmembers(thisModule, inspect.isclass) - sources = {} - for funcTuple in functions: - sources[funcTuple[0]] = inspect.getsource(funcTuple[1]) - for classTuple in classes: - className = classTuple[0] - classObject = classTuple[1] - methods = inspect.getmembers(classObject) - for methodTuple in methods: - try: - sources[methodTuple[0]] = inspect.getsource(\ - methodTuple[1]) - except: - pass - return sources - - -if __name__ == '__main__': - print('[wxpython.py] architecture=%s-bit' % (8 * struct.calcsize("P"))) - print('[wxpython.py] wx.version=%s' % wx.version()) - - # Intercept python exceptions. Exit app immediately when exception - # happens on any of the threads. - sys.excepthook = ExceptHook - - # Application settings - g_applicationSettings = { - # Disk cache - # "cache_path": "webcache/", - - # CEF Python debug messages in console and in log_file - "debug": True, - # Set it to LOGSEVERITY_VERBOSE for more details - "log_severity": cefpython.LOGSEVERITY_INFO, - # Set to "" to disable logging to a file - "log_file": GetApplicationPath("debug.log"), - - # These directories must be set on Linux - "locales_dir_path": cefpython.GetModuleDirectory()+"/locales", - "resources_dir_path": cefpython.GetModuleDirectory(), - # The "subprocess" executable that launches the Renderer - # and GPU processes among others. You may rename that - # executable if you like. - "browser_subprocess_path": "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess"), - - # This option is required for the GetCookieManager callback - # to work. It affects renderer processes, when this option - # is set to True. It will force a separate renderer process - # for each browser created using CreateBrowserSync. - "unique_request_context_per_browser": True, - # Downloads are handled automatically. A default SaveAs file - # dialog provided by OS will be displayed. - - "downloads_enabled": True, - # Remote debugging port, required for Developer Tools support. - # A value of 0 will generate a random port. To disable devtools - # support set it to -1. - "remote_debugging_port": 0, - # Mouse context menu - "context_menu": { - "enabled": True, - "navigation": True, # Back, Forward, Reload - "print": True, - "view_source": True, - "external_browser": True, # Open in external browser - "devtools": True, # Developer Tools - }, - - # See also OnCertificateError which allows you to ignore - # certificate errors for specific websites. - "ignore_certificate_errors": True, - } - - # You can comment out the code below if you do not want High - # DPI support. If you disable it text will look fuzzy on - # high DPI displays. - # - # Enabling High DPI support in app can be done by - # embedding a DPI awareness xml manifest in executable - # (see Issue 112 comment #2), or by calling SetProcessDpiAware - # function. Embedding xml manifest is the most reliable method. - # The downside of calling SetProcessDpiAware is that scrollbar - # in CEF browser is smaller than it should be. This is because - # DPI awareness was set too late, after the CEF dll was loaded. - # To fix that embed DPI awareness xml manifest in the .exe file. - # - # There is one bug when enabling High DPI support - fonts in - # javascript dialogs (alert) are tiny. However, you can implement - # custom javascript dialogs using JavascriptDialogHandler. - # - # Additionally you have to set "auto_zomming" application - # setting. High DPI support is available only on Windows. - # You may set auto_zooming to "system_dpi" and browser - # contents will be zoomed using OS DPI settings. On Win7 - # these can be set in: Control Panel > Appearance and - # Personalization > Display. - # - # Example values for auto_zooming are: - # "system_dpi", "0.0" (96 DPI), "1.0" (120 DPI), - # "2.0" (144 DPI), "-1.0" (72 DPI) - # Numeric value means a zoom level. - # Example values that can be set in Win7 DPI settings: - # Smaller 100% (Default) = 96 DPI = 0.0 zoom level - # Medium 125% = 120 DPI = 1.0 zoom level - # Larger 150% = 144 DPI = 2.0 zoom level - # Custom 75% = 72 DPI = -1.0 zoom level - g_applicationSettings["auto_zooming"] = "system_dpi" - print("[wxpython.py] Calling SetProcessDpiAware") - cefpython.DpiAware.SetProcessDpiAware() - - # Browser settings. You may have different settings for each - # browser, see the call to CreateBrowserSync. - g_browserSettings = { - # "plugins_disabled": True, - # "file_access_from_file_urls_allowed": True, - # "universal_access_from_file_urls_allowed": True, - } - - # Command line switches set programmatically - g_commandLineSwitches = { - # "proxy-server": "socks5://127.0.0.1:8888", - # "no-proxy-server": "", - # "enable-media-stream": "", - # "disable-gpu": "", - - } - - cefpython.Initialize(g_applicationSettings, g_commandLineSwitches) - - app = MyApp(False) - app.MainLoop() - - # Let wx.App destructor do the cleanup before calling - # cefpython.Shutdown(). This is to ensure reliable CEF shutdown. - del app - - cefpython.Shutdown() - diff --git a/tools/common.py b/tools/common.py index 3df0404b9..f710b4d95 100644 --- a/tools/common.py +++ b/tools/common.py @@ -320,7 +320,7 @@ def get_cefpython_binary_basename(postfix2, ignore_error=False): def get_setup_installer_basename(version, postfix2): - setup_basename = ("cefpython3-{version}-{os}-setup" + setup_basename = ("cefpython3_{version}_{os}" .format(version=version, os=postfix2)) return setup_basename From 1fa43af99f3b273989d5594b54413218590c748d Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 11:45:09 +0100 Subject: [PATCH 024/398] Fix build.py tool broken in previous commit --- tools/build.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/build.py b/tools/build.py index 7debfffcb..1d32dcf1d 100644 --- a/tools/build.py +++ b/tools/build.py @@ -806,9 +806,8 @@ def install_and_run(): os.chdir(BUILD_DIR) # Setup installer directory - setup_installer_dir = ("./cefpython3-{version}-{os}-setup/" - .format(version=VERSION, os=OS_POSTFIX2)) - setup_installer_dir = os.path.join(BUILD_DIR, setup_installer_dir) + setup_basename = get_setup_installer_basename(VERSION, OS_POSTFIX2) + setup_installer_dir = os.path.join(BUILD_DIR, setup_basename) # Delete setup installer directory if exists if os.path.exists(setup_installer_dir): From 9537590c8ff11d21a516de68464465f706b4b646 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 13:14:26 +0100 Subject: [PATCH 025/398] Fix v56.1 build on Mac (#335) --- src/client_handler/js_dialog_handler.cpp | 6 ++++++ src/compile_time_constants.pxi | 4 ++-- tools/automate.py | 2 +- tools/build_distrib.py | 14 +++++++++----- tools/common.py | 10 ++++++---- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/client_handler/js_dialog_handler.cpp b/src/client_handler/js_dialog_handler.cpp index 3d3ecd09f..15dfc848e 100644 --- a/src/client_handler/js_dialog_handler.cpp +++ b/src/client_handler/js_dialog_handler.cpp @@ -27,12 +27,14 @@ bool JSDialogHandler::OnJSDialog(CefRefPtr browser, dialog_type, message_text, default_prompt_text, callback, suppress_message); +#if defined(OS_LINUX) if (!ret) { // Default implementation return dialog_handler_->OnJSDialog(browser, origin_url, dialog_type, message_text, default_prompt_text, callback, suppress_message); } +#endif return ret; } @@ -47,11 +49,13 @@ bool JSDialogHandler::OnBeforeUnloadDialog( bool ret = JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( browser, message_text, is_reload, callback); +#if defined(OS_LINUX) if (!ret) { // Default implementation return dialog_handler_->OnBeforeUnloadDialog(browser, message_text, is_reload, callback); } +#endif return ret; } @@ -59,8 +63,10 @@ bool JSDialogHandler::OnBeforeUnloadDialog( void JSDialogHandler::OnResetDialogState(CefRefPtr browser) { REQUIRE_UI_THREAD(); +#if defined(OS_LINUX) // Default implementation dialog_handler_->OnResetDialogState(browser); +#endif // User implementation JavascriptDialogHandler_OnResetJavascriptDialogState(browser); } diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..457e4a580 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 2 +DEF UNAME_SYSNAME = "Darwin" +DEF PY_MAJOR_VERSION = 3 diff --git a/tools/automate.py b/tools/automate.py index 1a12398e5..9e192eb1c 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -598,7 +598,7 @@ def build_wrapper_library_mac(): # from cefclient. cmake_wrapper = prepare_build_command(build_lib=True) cmake_wrapper.extend(["cmake", "-G", "Ninja", - "-DPROJECT_ARCH=x86_64" + "-DPROJECT_ARCH=x86_64", "-DCMAKE_CXX_FLAGS=-stdlib=libc++", "-DCMAKE_BUILD_TYPE=" + Options.build_type, ".."]) diff --git a/tools/build_distrib.py b/tools/build_distrib.py index a88f059cc..7466caf89 100644 --- a/tools/build_distrib.py +++ b/tools/build_distrib.py @@ -6,8 +6,6 @@ Build distribution packages for all architectures and all supported python versions. -TODO: Mac support. Currently runs only on Windows/Linux. - Usage: build_distrib.py VERSION [--no-run-examples] [--no-rebuild] @@ -24,9 +22,11 @@ 1. Expects that all supported python versions are installed a) On Windows search for Pythons in the multiple default install locations - b) On Mac use system Python 2.7 and Python 3 from ~/.pyenv/versions/ + b) On Linux use only Pythons from ~/.pyenv/versions/ directory - c) On Linux use only Pythons from ~/.pyenv/versions/ directory + c) On Mac use Pythons from ~/.pyenv/versions/ and /usr/local/bin/python + For example will use Python 2.7.13 from /usr/local/bin/ only + when 2.7 was not found in ~/.pyenv/versions/. 2. Expects that all python compilers for supported python versions are installed. See docs/Build-instructions.md > Requirements. 3. Expects cef_binary*/ directories from Spotify Automated Builds @@ -83,6 +83,10 @@ LINUX=[ "%PYENV_ROOT%/versions/*/bin", ], + MAC=[ + "%PYENV_ROOT%/versions/*/bin", + "/usr/local/bin", + ], ) @@ -516,7 +520,7 @@ def test_wheel_packages(pythons): print("[build_distrib.py] Test wheel package (install, unittests) for" " {python_name}".format(python_name=python["name"])) platform_tag = get_pypi_postfix2_for_arch(python["arch"]) - whl_pattern = (r"*-{platform_tag}.whl" + whl_pattern = (r"*{platform_tag}.whl" .format(platform_tag=platform_tag)) wheels = glob.glob(os.path.join(DISTRIB_DIR, whl_pattern)) assert len(wheels) == 1, ("No wheels found in distrib dir for %s" diff --git a/tools/common.py b/tools/common.py index f710b4d95..c6b886480 100644 --- a/tools/common.py +++ b/tools/common.py @@ -47,24 +47,26 @@ # Platforms SYSTEM = platform.system().upper() +if SYSTEM == "DARWIN": + SYSTEM = "MAC" WINDOWS = SYSTEM if SYSTEM == "WINDOWS" else False LINUX = SYSTEM if SYSTEM == "LINUX" else False -MAC = SYSTEM if SYSTEM == "DARWIN" else False +MAC = SYSTEM if SYSTEM == "MAC" else False OS_POSTFIX2_ARCH = dict( WINDOWS={"32bit": "win32", "64bit": "win64"}, LINUX={"32bit": "linux32", "64bit": "linux64"}, - DARWIN={"32bit": "mac32", "64bit": "mac64"}, + MAC={"32bit": "mac32", "64bit": "mac64"}, ) CEF_POSTFIX2_ARCH = dict( WINDOWS={"32bit": "windows32", "64bit": "windows64"}, LINUX={"32bit": "linux32", "64bit": "linux64"}, - DARWIN={"64bit": "macosx64"}, + MAC={"64bit": "macosx64"}, ) PYPI_POSTFIX2_ARCH = dict( WINDOWS={"32bit": "win32", "64bit": "win_amd64"}, LINUX={"32bit": "manylinux1_i686", "64bit": "manylinux1_x86_64"}, - DARWIN={"64bit": "x86_64"}, + MAC={"64bit": "x86_64"}, ) # Python version eg. 27 From 264ce187a3bf14670f91002911f2628139cf6f28 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 20:41:22 +0100 Subject: [PATCH 026/398] Update README - add support matrixes and install/examples sections --- README.md | 55 +++++++++++++++++++++++++------------ examples/Examples-README.md | 9 +++--- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a656c62da..56654a93b 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ Table of contents: * [Introduction](#introduction) +* [Install](#install) +* [Examples](#examples) * [Support](#support) * [Releases](#releases) - * [v50+ releases](#v50-releases) + * [v56+ releases](#v56-releases) * [v31 release](#v31-release) * [Support development](#support-development) * [Thanks](#thanks) @@ -38,6 +40,21 @@ applications. You can use it for web scraping or as a web crawler, or other kind of internet bots. +## Install + +You can install with pip. On Linux pip 8.1+ is required. You can +also download packages for offline installation available on the +[GitHub Releases](../../releases) pages. + +``` +pip install cefpython3==56.1 +``` + +## Examples + +See the [Examples-README.md](examples/Examples-README.md) file. + + ## Support - Ask questions, report problems and issues on the [Forum](https://groups.google.com/group/cefpython) @@ -49,28 +66,30 @@ or other kind of internet bots. ## Releases -### v50+ releases +Information on supported platforms, python versions, architectures +and requirements. If you want to support old operating systems then +choose the v31 release. -- Can be installed on all platforms using `pip install cefpython3` command -- Downloads are available on [GitHub Releases](../../releases) pages -- Windows support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 - (requirements: Windows 7+) -- Linux support: 32-bit and 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 - (requirements: Debian 7+ / Ubuntu 12.04+) -- Mac support: 64-bit, Python 2.7 / 3.4 / 3.5 / 3.6 - (requirements: MacOS 10.9+) -- Documentation is in the [docs/](docs) directory -- API reference is in the [api/](api) directory -- Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +### v56+ releases + +OS | Py2 | Py3 | 32bit | 64bit | Requirements +--- | --- | --- | --- | --- | --- +Windows | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Windows 7+ +Linux | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Debian 7+ / Ubuntu 12.04+ +Mac | 2.7 | 3.4 / 3.5 / 3.6 | No | Yes | MacOS 10.9+ ### v31 release +OS | Py2 | Py3 | 32bit | 64bit | Requirements +--- | --- | --- | --- | --- | --- +Windows | 2.7 | No | Yes | Yes | Windows XP+ +Linux | 2.7 | No | Yes | Yes | Debian 7+ / Ubuntu 12.04+ +Mac | 2.7 | No | Yes | Yes | MacOS 10.7+ + +Additional information for v31.2 release: +- On Windows/Mac you can install with command: `pip install cefpython3==31.2` - Downloads are available on [wiki pages](../../wiki#downloads) - and on GH Releases tagged [v31.2](../../releases/tag/v31.2) -- Supports only Python 2.7 -- Windows support: 32-bit and 64-bit (requirements: Windows XP+) -- Linux support: 32-bit and 64-bit (requirements: Debian 7+ / Ubuntu 12.04+) -- Mac support: 32-bit and 64-bit (requirements: MacOS 10.7+) + and on GitHub Releases tagged [v31.2](../../releases/tag/v31.2). - Documentation is on [wiki pages](../../wiki) - API reference is available in revision [169a1b2](../../tree/169a1b20d3cd09879070d41aab28cfa195d2a7d5/docs/api) diff --git a/examples/Examples-README.md b/examples/Examples-README.md index d7bb153cb..a611d860b 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -12,15 +12,14 @@ maintained: - [gtk2.py](gtk2.py): example for [PyGTK](http://www.pygtk.org/) library (GTK 2) -- [gtk3.py](gtk3.py): example for [PyGObject/PyGI] - (https://wiki.gnome.org/Projects/PyGObject) library (GTK 3). - Currently broken on Linux/Mac, see top comments in sources. +- [gtk3.py](gtk3.py): example for [PyGObject/PyGI](https://wiki.gnome.org/Projects/PyGObject) + library (GTK 3). Currently broken on Linux/Mac, see top comments in sources. - [hello_world.py](hello_world.py): doesn't require any third party GUI framework - [qt4.py](qt4.py): example for [PyQt4](https://wiki.python.org/moin/PyQt4) and [PySide](https://wiki.qt.io/PySide) libraries (Qt 4) -- [tkinter_.py](tkinter_.py): example for [Tkinter] - (https://wiki.python.org/moin/TkInter). Currently broken on Mac. +- [tkinter_.py](tkinter_.py): example for [Tkinter](https://wiki.python.org/moin/TkInter). + Currently broken on Mac. - [wxpython.py](wxpython.py): example for [wxPython](https://wxpython.org/) If there are any issues in examples read top comments in sources From 0d5afb5dffd36cbb959b152ab69cac326e0593db Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 20:56:18 +0100 Subject: [PATCH 027/398] Update README - add info on ARM and Android --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 56654a93b..8690e61e2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Table of contents: * [Examples](#examples) * [Support](#support) * [Releases](#releases) - * [v56+ releases](#v56-releases) + * [Latest release](#latest-release) * [v31 release](#v31-release) * [Support development](#support-development) * [Thanks](#thanks) @@ -70,7 +70,7 @@ Information on supported platforms, python versions, architectures and requirements. If you want to support old operating systems then choose the v31 release. -### v56+ releases +### Latest release OS | Py2 | Py3 | 32bit | 64bit | Requirements --- | --- | --- | --- | --- | --- @@ -78,6 +78,13 @@ Windows | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Windows 7+ Linux | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Debian 7+ / Ubuntu 12.04+ Mac | 2.7 | 3.4 / 3.5 / 3.6 | No | Yes | MacOS 10.9+ +These platforms are not yet supported: + +OS | More info +--- | --- +ARM | [Issue #267](../../issues/267) +Android | [Issue #303](../../issues/303) + ### v31 release OS | Py2 | Py3 | 32bit | 64bit | Requirements From ea14b98cb4a6a71dd3d4f32a037c55848e3b8f9a Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 20 Mar 2017 20:57:32 +0100 Subject: [PATCH 028/398] Fix link in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8690e61e2..0bfae188e 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,12 @@ Windows | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Windows 7+ Linux | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Debian 7+ / Ubuntu 12.04+ Mac | 2.7 | 3.4 / 3.5 / 3.6 | No | Yes | MacOS 10.9+ -These platforms are not yet supported: +These platforms are not supported yet: OS | More info --- | --- ARM | [Issue #267](../../issues/267) -Android | [Issue #303](../../issues/303) +Android | [Issue #307](../../issues/307) ### v31 release From ee46c0f965e7f8754b349f8d01beb6a13c8d6f57 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 21 Mar 2017 12:56:43 +0100 Subject: [PATCH 029/398] Update README's - hello world example and documentation search --- README.md | 3 +++ examples/Examples-README.md | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0bfae188e..e5c2c7d7e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,9 @@ See the [Examples-README.md](examples/Examples-README.md) file. - Documentation is in the [docs/](docs) directory - API reference is in the [api/](api) directory - Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +- To search documentation use GitHub "This repository" search + at the top. To narrow results to documentation only select + "Markdown" in the left pane. - Wiki pages are deprecated and for v31 only diff --git a/examples/Examples-README.md b/examples/Examples-README.md index a611d860b..974ddd21d 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -1,26 +1,40 @@ # Examples README Table of contents: +* [Hello World!](#hello-world) * [Supported examples](#supported-examples) * [More examples](#more-examples) +## Hello World! + +Instructions to install the cefpython3 package, clone the repository +and run the hello_world.py example: + +``` +pip install cefpython3==56.1 +git clone https://github.com/cztomczak/cefpython.git +cd cefpython/ +python hello_world.py +``` + ## Supported examples Examples provided in the examples/ root directory are actively maintained: +- [hello_world.py](hello_world.py): basic example, doesn't require any + third party GUI framework to run - [gtk2.py](gtk2.py): example for [PyGTK](http://www.pygtk.org/) library (GTK 2) - [gtk3.py](gtk3.py): example for [PyGObject/PyGI](https://wiki.gnome.org/Projects/PyGObject) library (GTK 3). Currently broken on Linux/Mac, see top comments in sources. -- [hello_world.py](hello_world.py): doesn't require any third party - GUI framework - [qt4.py](qt4.py): example for [PyQt4](https://wiki.python.org/moin/PyQt4) and [PySide](https://wiki.qt.io/PySide) libraries (Qt 4) - [tkinter_.py](tkinter_.py): example for [Tkinter](https://wiki.python.org/moin/TkInter). Currently broken on Mac. - [wxpython.py](wxpython.py): example for [wxPython](https://wxpython.org/) + toolkit If there are any issues in examples read top comments in sources to see whether this is a known issue with available workarounds. From 4b2b8de61c5a91db3935da6fc723c5b4404e7026 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 21 Mar 2017 12:58:43 +0100 Subject: [PATCH 030/398] Fix typo in README --- examples/Examples-README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 974ddd21d..6e76fb4d7 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -13,7 +13,7 @@ and run the hello_world.py example: ``` pip install cefpython3==56.1 git clone https://github.com/cztomczak/cefpython.git -cd cefpython/ +cd cefpython/examples/ python hello_world.py ``` From 14342b3b9feaa3d22779ab197a5e003ad186a1a5 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Wed, 22 Mar 2017 17:35:20 +0100 Subject: [PATCH 031/398] Update README - minor fix --- README.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e5c2c7d7e..299271fd6 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,8 @@ Linux | 2.7 | 3.4 / 3.5 / 3.6 | Yes | Yes | Debian 7+ / Ubuntu 12.04+ Mac | 2.7 | 3.4 / 3.5 / 3.6 | No | Yes | MacOS 10.9+ These platforms are not supported yet: - -OS | More info ---- | --- -ARM | [Issue #267](../../issues/267) -Android | [Issue #307](../../issues/307) +- ARM - see [Issue #267](../../issues/267) +- Android - see [Issue #307](../../issues/307) ### v31 release @@ -111,12 +108,11 @@ by making a donation please click the Paypal Donate button: - +

-At this time CEF Python is unable to accept donations that sponsor the -development of specific features. However you can make a donation with -a comment that you would like to see some feature implemented and it will -give it a higher priority. +If you would like to see some feature implemented you can make +a comment about that when making a donation. It will give it +a higher priority. If you are interested in sponsorship opportunities please contact Czarek directly. From b3ad7baa853896f2593520411b889716719e70b7 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Wed, 22 Mar 2017 17:51:55 +0100 Subject: [PATCH 032/398] Update README - minor fixes --- README.md | 9 ++------- tools/toc.py | 5 +++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 299271fd6..d624fb587 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,9 @@ Table of contents: * [Examples](#examples) * [Support](#support) * [Releases](#releases) - * [Latest release](#latest-release) - * [v31 release](#v31-release) * [Support development](#support-development) * [Thanks](#thanks) * [Quick links](#quick-links) - * [Docs](#docs) - * [API categories](#api-categories) - * [API index](#api-index) ## Introduction @@ -73,7 +68,7 @@ Information on supported platforms, python versions, architectures and requirements. If you want to support old operating systems then choose the v31 release. -### Latest release +**Latest release** OS | Py2 | Py3 | 32bit | 64bit | Requirements --- | --- | --- | --- | --- | --- @@ -85,7 +80,7 @@ These platforms are not supported yet: - ARM - see [Issue #267](../../issues/267) - Android - see [Issue #307](../../issues/307) -### v31 release +**v31 release** OS | Py2 | Py3 | 32bit | 64bit | Requirements --- | --- | --- | --- | --- | --- diff --git a/tools/toc.py b/tools/toc.py index b7f64c1be..a9749d35a 100644 --- a/tools/toc.py +++ b/tools/toc.py @@ -113,6 +113,11 @@ def create_toc(contents, file_): contents += os.linesep + toc + os.linesep + os.linesep toc_inserted = True contents += line + os.linesep + # Special case for README.md - remove Quick Links toc for subheadings + re_find = (r" \* \[Docs\]\(#docs\)[\r\n]+" + r" \* \[API categories\]\(#api-categories\)[\r\n]+" + r" \* \[API index\]\(#api-index\)\r?\n?") + contents = re.sub(re_find, "", contents) return tocsize, contents, warnings From de9b0b2b37e2634b4ae589e29379322aa8a8dcef Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 23 Mar 2017 21:10:51 +0100 Subject: [PATCH 033/398] Fix CEF warnings/errors not saved to debug.log when --debug flag is specified. --- Authors | 2 +- src/cefpython.pyx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Authors b/Authors index 89576d339..28a008d75 100644 --- a/Authors +++ b/Authors @@ -1,5 +1,5 @@ Core developers: -Czarek Tomczak +Czarek Tomczak Contributors: 老农 cjjer diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 64b1fe7c9..9a492764c 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -612,6 +612,7 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): global g_debugFile if "--debug" in sys.argv: application_settings["debug"] = True + application_settings["log_file"] = "debug.log" application_settings["log_severity"] = LOGSEVERITY_WARNING sys.argv.remove("--debug") if "debug" in application_settings: From 4301258755182d53b7d5fadacc64bed17b7deb0d Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 24 Mar 2017 19:17:49 +0100 Subject: [PATCH 034/398] Complete Migration Guide document (#293) --- README.md | 2 +- docs/Examples.md | 2 +- docs/Migration-guide.md | 215 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 211 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d624fb587..2dda89340 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ See the [Examples-README.md](examples/Examples-README.md) file. - Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) - To search documentation use GitHub "This repository" search at the top. To narrow results to documentation only select - "Markdown" in the left pane. + "Markdown" in the right pane. - Wiki pages are deprecated and for v31 only diff --git a/docs/Examples.md b/docs/Examples.md index aa2af612e..9aa065e59 100644 --- a/docs/Examples.md +++ b/docs/Examples.md @@ -1,4 +1,4 @@ # Examples Examples are available in the examples/ root directory. See -[Examples-README.md](../examples/Examples-README.md). +the [Examples-README.md](../examples/Examples-README.md) file. diff --git a/docs/Migration-guide.md b/docs/Migration-guide.md index 9a1ceeea7..0ee6ca991 100644 --- a/docs/Migration-guide.md +++ b/docs/Migration-guide.md @@ -1,12 +1,44 @@ -# Migration guide from v31 to latest v55+ (STILL UNDER WORKS.. #293) +# Migration guide + +This migration guide will get you through to make your code work +with latest CEF Python. This document includes notable changes +that were introduced to cefpython and each topic is prefixed +with version number in which a change was introduced. +This migration guide doesn't cover all changes required for your +software to run smoothly. Some changes depend on the GUI framework +you are using and this guide doesn't cover these. You will have +to go to the examples/ root directory and see the example for your +GUI framework. The new examples are very straightforward and include +many useful comments explaining whys. You will have to get through +its code and see if anything changed that also requires changes +in your application. Table of contents: -* [Distribution packages](#distribution-packages) -* [Handlers' callbacks and other interfaces](#handlers-callbacks-and-other-interfaces) +* [v50+ Distribution packages](#v50-distribution-packages) +* [v50+ Importing the cefpython3 package on Linux](#v50-importing-the-cefpython3-package-on-linux) +* [v50+ Install X11 error handlers on Linux](#v50-install-x11-error-handlers-on-linux) +* [v50+ Set window bounds on Linux](#v50-set-window-bounds-on-linux) +* [v50+ Notify CEF on move or resize events](#v50-notify-cef-on-move-or-resize-events) +* [v50+ Keyboard focus issues on Linux](#v50-keyboard-focus-issues-on-linux) +* [v50+ Windows XP and Vista are no more supported](#v50-windows-xp-and-vista-are-no-more-supported) +* [v50+ Mac 32-bit is no more supported](#v50-mac-32-bit-is-no-more-supported) +* [v50+ cefbuilds.com is deprected, use Spotify Automated CEF Builds](#v50-cefbuildscom-is-deprected-use-spotify-automated-cef-builds) +* [v50+ Build instructions and build tools](#v50-build-instructions-and-build-tools) +* [v51+ Off-screen-rendering: new option "windowless_rendering_enabled"](#v51-off-screen-rendering-new-option-windowless_rendering_enabled) +* [v51+ Remove LifespanHandler.RunModal](#v51-remove-lifespanhandlerrunmodal) +* [v51+: BrowserSettings options removed](#v51-browsersettings-options-removed) +* [v51+ cef.Request.Flags changed](#v51-cefrequestflags-changed) +* [v51+ Request.GetHeaderMap and SetHeaderMap change](#v51-requestgetheadermap-and-setheadermap-change) +* [v54+ GTK 3 example doesn't work anymore on Linux](#v54-gtk-3-example-doesnt-work-anymore-on-linux) +* [v54+ libcef.so library is stripped from symbols on Linux](#v54-libcefso-library-is-stripped-from-symbols-on-linux) +* [v55+ HTTPS cache problems on pages with certificate errors](#v55-https-cache-problems-on-pages-with-certificate-errors) +* [v55.3+ Handlers' callbacks and other interfaces](#v553-handlers-callbacks-and-other-interfaces) +* [v56+ MacOS 10.9+ required to run](#v56-macos-109-required-to-run) + -## Distribution packages +## v50+ Distribution packages In latest CEF Python there are only two distribution packages available. The first one is a wheel package distributed through @@ -15,10 +47,14 @@ on Linux). The second one is a setup package available for download on the GitHub Releases pages, instructions on how to install it are provided in README.txt. +**Windows** + On Windows many of the distribution packages such as MSI, EXE, ZIP and InnoSetup files, are no more available. It is too much hassle to support these. +**Linux debian package** + On Linux the debian package is not supported anymore. Since pip 8.1+ added support for manylinux1 wheel packages, you can now easily install cefpython on Linux using the pip tool. @@ -31,7 +67,173 @@ in an automated manner, it might be reconsidered in the future to provide debian packages again. -## Handlers' callbacks and other interfaces +## v50+ Importing the cefpython3 package on Linux + +In the past on Linux it was required for the cefpython3 package +to be imported before any other packages due to tcmalloc global +hook being loaded. This is not required anymore, tcmalloc is +disabled by default. + + +## v50+ Install X11 error handlers on Linux + +It is required to install X11 error handlers on Linux, otherwise +you will see 'BadWindow' errors happening - sometimes randomly - +which will cause application to terminate. Since v56+ x11 error +handlers are installed automatically by default during the call +to cef.Initialize(). However sometimes that is not enough like +for example in the wxpython.py example which requires the x11 +error handlers to be installed manually after wx was initialized, +and that is because wx initialization had reset x11 error handlers +that were installed earlier during cef initialization (Issue [#334](../../../issues/334)). + +You can install X11 error handlers by calling: +``` +WindowUtils = cef.WindowUtils() +WindowUtils.InstallX11ErrorHandlers() +``` + +API ref: WindowUtils.[InstallX11ErrorHandlers()](../api/WindowUtils.md#installx11errorhandlers-linux) + + +## v50+ Set window bounds on Linux + +It is now required to set window bounds during window "resize", +"move" and "configure" events on Linux. You can do so by calling: + +``` +browser.SetBounds(x, y, width, height) +``` + +API ref: Browser.[SetBounds()](../api/Browser.md#setbounds) + + +## v50+ Notify CEF on move or resize events + +It is required to notify the browser on move or resize events +so that popup widgets (e.g. \) are displayed in the correct +location and dismissed when the window moves. Also so that +drag & drop areas are updated accordingly. + +``` +browser.NotifyMoveOrResizeStarted() +``` + +API ref: Browser.[NotifyMoveOrResizeStarted()](../api/Browser.md#notifymoveorresizestarted) + + +## v50+ Keyboard focus issues on Linux + +There several keyboard focus issues on Linux since CEF library +replaced GTK library with X11 library. Most of these issues are +fixed in examples by calling SetFocus in LoadHandler.OnLoadStart +during initial app loading and/or by calling SetFocus in +FocusHandler.OnGotFocus. This keyboard focus issues need to be +fixed in usptream CEF. For more details see Issue [#284](../../../issues/284). + + +## v50+ Windows XP and Vista are no more supported + +CEF Python v31.2 was the last version to support Windows XP. +This is due to Chromium/CEF dropping XP support, last version +that supported XP was CEF v49. + + +## v50+ Mac 32-bit is no more supported + +CEF Python v31.2 was the last version to support Mac 32-bit. +This is due to CEF/Chromium dropping 32-bit support, last version +that supported 32-bit was CEF v38. + + +## v50+ cefbuilds.com is deprected, use Spotify Automated CEF Builds + +The cefbuilds.com site with CEF prebuilt binaries is now deprecated. +From now on download prebuilt CEF binaries from the Spotify Automated +CEF Builds: + +http://opensource.spotify.com/cefbuilds/index.html + + +## v50+ Build instructions and build tools + +Many changes in regards to building CEF and CEF Python has changed. +There are now new tools in the tools/ root directory that fully +automate building CEF and CEF Python. CEF Python now provides +upstream CEF prebuilt binaries and libraries on GitHub Releases +tagged eg. "v56-upstream". With these binaries you can build +CEF Python from sources in less than 10 minutes. See the new +[Build instructions](Build-instructions.md) document. + + +## v51+ Off-screen-rendering: new option "windowless_rendering_enabled" + +When using off-screen-rendering you must set the ApplicationSettings +"windowless_rendering_enabled" option to True. This applies to +examples such as: Kivy, Panda3D and screenshot example. + +API ref: ApplicationSettings.[windowless_rendering_enabled](../api/ApplicationSettings.md#windowless_rendering_enabled) + + +## v51+ Remove LifespanHandler.RunModal + +LifespanHandler.RunModal callback is no more available. + + +## v51+: BrowserSettings options removed + +The following options were removed from BrowserSettings: +- user_style_sheet_location +- java_disabled +- accelerated_compositing +- author_and_user_styles_disabled + + +## v51+ cef.Request.Flags changed + +The following flags were removed from cef.Request.Flags: +- AllowCookies +- ReportLoadTiming +- ReportRawHeaders + +API ref: Request.[GetFlags](../api/Request.md#getflags) + + +## v51+ Request.GetHeaderMap and SetHeaderMap change + +GetHeaderMap() will not include the Referer value if any +and SetHeaderMap() will ignore the Referer value. + +API ref: Request.[GetHeaderMap](../api/Request.md#getheadermap) + + +## v54+ GTK 3 example doesn't work anymore on Linux + +Currently GTK 3 example is broken on Linux. You can either +downgrade to an old cefpython v53 (available on GitHub release +page) or use GTK 2 example. For more details on the problem see +Issue [#261](../../../issues/261). + + +## v54+ libcef.so library is stripped from symbols on Linux + +Symbols useful for debugging are no more available in libcef.so +shipped with distribution packages on Linux. This is explained +in details in Issue [#262](../../../issues/262). + + +## v55+ HTTPS cache problems on pages with certificate errors + +The fix for HTTPS cache problems on pages with certificate errors +is no more applied on Windows. + +Soon this will fix also won't be applied on Linux anymore when +cefpython starts using CEF prebuilt binaries from Spotify. + +See Issue [#125](../../../issues/125) for more details. + + +## v55.3+ Handlers' callbacks and other interfaces Since v55.3 all handlers' callbacks and other interfaces such as CookieVisitor, StringVisitor and WebRequestClient, are now called @@ -77,5 +279,6 @@ your code will definitely break, unless you've also used "http_code" for argument name. +## v56+ MacOS 10.9+ required to run - +CEF v55 was the last version to support MacOS 10.7. From 77c506b7c337a83aabda51f00df9f6a17c47d75e Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 25 Mar 2017 13:18:47 +0100 Subject: [PATCH 035/398] Fix Printing in the hello_world.py and tkinter_.py examples. Minor fixes. Added some code for testing CEF views. --- docs/Migration-guide.md | 6 +- examples/gtk2.py | 10 +-- src/browser.pyx | 1 - src/cefpython.pyx | 62 ++++++++++++++++- src/client_handler/dialog_handler_gtk.cpp | 4 ++ src/common/cefpython_public_api.h | 12 ++-- src/compile_time_constants.pxi | 4 +- src/extern/cef/cef_types.pxd | 9 +++ src/extern/cef/cef_views.pxd | 84 +++++++++++++++++++++++ src/frame.pyx | 1 - src/handlers/request_handler.pyx | 9 +++ src/linux/binaries_64bit/kivy_.py | 6 +- src/settings.pyx | 5 +- src/subprocess/cefpython_app.cpp | 13 ++++ src/subprocess/cefpython_app.h | 4 -- src/window_info.pyx | 7 +- 16 files changed, 208 insertions(+), 29 deletions(-) create mode 100644 src/extern/cef/cef_views.pxd diff --git a/docs/Migration-guide.md b/docs/Migration-guide.md index 0ee6ca991..3bab17bc4 100644 --- a/docs/Migration-guide.md +++ b/docs/Migration-guide.md @@ -27,7 +27,7 @@ Table of contents: * [v50+ Build instructions and build tools](#v50-build-instructions-and-build-tools) * [v51+ Off-screen-rendering: new option "windowless_rendering_enabled"](#v51-off-screen-rendering-new-option-windowless_rendering_enabled) * [v51+ Remove LifespanHandler.RunModal](#v51-remove-lifespanhandlerrunmodal) -* [v51+: BrowserSettings options removed](#v51-browsersettings-options-removed) +* [v51+ BrowserSettings options removed](#v51-browsersettings-options-removed) * [v51+ cef.Request.Flags changed](#v51-cefrequestflags-changed) * [v51+ Request.GetHeaderMap and SetHeaderMap change](#v51-requestgetheadermap-and-setheadermap-change) * [v54+ GTK 3 example doesn't work anymore on Linux](#v54-gtk-3-example-doesnt-work-anymore-on-linux) @@ -157,7 +157,7 @@ http://opensource.spotify.com/cefbuilds/index.html ## v50+ Build instructions and build tools -Many changes in regards to building CEF and CEF Python has changed. +There were many changes in regards to building CEF and CEF Python. There are now new tools in the tools/ root directory that fully automate building CEF and CEF Python. CEF Python now provides upstream CEF prebuilt binaries and libraries on GitHub Releases @@ -180,7 +180,7 @@ API ref: ApplicationSettings.[windowless_rendering_enabled](../api/ApplicationSe LifespanHandler.RunModal callback is no more available. -## v51+: BrowserSettings options removed +## v51+ BrowserSettings options removed The following options were removed from BrowserSettings: - user_style_sheet_location diff --git a/examples/gtk2.py b/examples/gtk2.py index 2f8821819..99d7cc140 100644 --- a/examples/gtk2.py +++ b/examples/gtk2.py @@ -49,10 +49,10 @@ def main(): def check_versions(): - print("[gkt2.py] CEF Python {ver}".format(ver=cef.__version__)) - print("[gkt2.py] Python {ver} {arch}".format( + print("[gtk2.py] CEF Python {ver}".format(ver=cef.__version__)) + print("[gtk2.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) - print("[gkt2.py] GTK {ver}".format(ver='.'.join( + print("[gtk2.py] GTK {ver}".format(ver='.'.join( map(str, list(gtk.gtk_version))))) assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" pygtk.require('2.0') @@ -64,11 +64,11 @@ def configure_message_loop(): print("[gtk2.py] Force --message-loop-cef flag on Mac") sys.argv.append("--message-loop-cef") if "--message-loop-cef" in sys.argv: - print("[gkt2.py] Message loop mode: CEF (best performance)") + print("[gtk2.py] Message loop mode: CEF (best performance)") g_message_loop = MESSAGE_LOOP_CEF sys.argv.remove("--message-loop-cef") else: - print("[gkt2.py] Message loop mode: TIMER") + print("[gtk2.py] Message loop mode: TIMER") g_message_loop = MESSAGE_LOOP_TIMER diff --git a/src/browser.pyx b/src/browser.pyx index 59c1aaf1e..edb3071a9 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -42,7 +42,6 @@ cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser, global g_pyBrowsers - # This probably ain't needed, but just to be sure. if cefBrowser == NULL or not cefBrowser.get(): raise Exception("{caller}: CefBrowser reference is NULL" .format(caller=callerIdStr)) diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 9a492764c..7e5283cfd 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -384,7 +384,7 @@ cdef extern from *: # cannot cimport *, that would cause name conflicts with constants # noinspection PyUnresolvedReferences from cef_types cimport ( - CefSettings, CefBrowserSettings, CefRect, CefPoint, + CefSettings, CefBrowserSettings, CefRect, CefSize, CefPoint, CefKeyEvent, CefMouseEvent, CefScreenInfo, PathKey, PK_DIR_EXE, PK_DIR_MODULE, int32, uint32, int64, uint64, @@ -433,6 +433,8 @@ from cef_path_util cimport * from cef_drag_data cimport * from cef_image cimport * from main_message_loop cimport * +# noinspection PyUnresolvedReferences +from cef_views cimport * # ----------------------------------------------------------------------------- # GLOBAL VARIABLES @@ -775,6 +777,46 @@ def CreateBrowserSync(windowInfo=None, assert IsThread(TID_UI), ( "cefpython.CreateBrowserSync() may only be called on the UI thread") + """ + # CEF views + # noinspection PyUnresolvedReferences + cdef CefRefPtr[CefWindow] cef_window + # noinspection PyUnresolvedReferences + cdef CefRefPtr[CefBoxLayout] cef_box_layout + cdef CefBoxLayoutSettings cef_box_layout_settings + cdef CefRefPtr[CefPanel] cef_panel + if not windowInfo and browserSettings \ + and "window_title" in browserSettings: + # noinspection PyUnresolvedReferences + cef_window = CefWindow.CreateTopLevelWindow( + NULL) + Debug("CefWindow.GetChildViewCount = " + +str(cef_window.get().GetChildViewCount())) + + cef_window.get().CenterWindow(CefSize(800, 600)) + cef_window.get().SetBounds(CefRect(0, 0, 800, 600)) + # noinspection PyUnresolvedReferences + #cef_box_layout = cef_window.get().SetToBoxLayout( + # cef_box_layout_settings) + #cef_box_layout.get().SetFlexForView(cef_window, 1) + cef_window.get().SetToFillLayout() + # noinspection PyUnresolvedReferences + cef_panel = CefPanel.CreatePanel(NULL) + cef_window.get().AddChildView(cef_panel) + cef_window.get().Layout() + cef_window.get().SetVisible(True) + cef_window.get().Show() + cef_window.get().RequestFocus() + windowInfo = WindowInfo() + windowInfo.SetAsChild(cef_window.get().GetWindowHandle()) + Debug("CefWindow handle = "+str(cef_window.get().GetWindowHandle())) + """ + + # Only title was set in hello_world.py example + if windowInfo and not windowInfo.windowType: + windowInfo.SetAsChild(0) + + # No window info provided if not windowInfo: windowInfo = WindowInfo() windowInfo.SetAsChild(0) @@ -831,6 +873,9 @@ def CreateBrowserSync(windowInfo=None, else: Debug("CefBrowser::CreateBrowserSync() succeeded") + Debug("CefBrowser window handle = " + +str(cefBrowser.get().GetHost().get().GetWindowHandle())) + # Request context - part 2/2. if g_applicationSettings["unique_request_context_per_browser"]: requestContextHandler.get().SetBrowser(cefBrowser) @@ -842,6 +887,21 @@ def CreateBrowserSync(windowInfo=None, pyBrowser.SetUserData("__outerWindowHandle", int(windowInfo.parentWindowHandle)) + """ + if cef_window.get(): + cef_window.get().ReorderChildView(cef_panel, -1) + cef_window.get().Layout() + cef_window.get().Show() + cef_window.get().RequestFocus() + """ + + if windowInfo.parentWindowHandle == 0 and windowInfo.windowType == "child": + # Set window title in hello_world.py example + IF UNAME_SYSNAME == "Linux": + pass + ELIF UNAME_SYSNAME == "Darwin": + pass + return pyBrowser def MessageLoop(): diff --git a/src/client_handler/dialog_handler_gtk.cpp b/src/client_handler/dialog_handler_gtk.cpp index 1db132907..76bf18513 100644 --- a/src/client_handler/dialog_handler_gtk.cpp +++ b/src/client_handler/dialog_handler_gtk.cpp @@ -11,12 +11,15 @@ #include #include #include +#include #include #include "include/cef_browser.h" #include "include/cef_parser.h" #include "include/wrapper/cef_helpers.h" +#include "LOG_DEBUG.h" + #include "dialog_handler_gtk.h" #include "x11.h" @@ -156,6 +159,7 @@ GtkWindow* GetWindow(CefRefPtr browser) { // internally, so GTK wasn't yet initialized and must do it // now, so that display is available. Also must install X11 // error handlers to avoid 'BadWindow' errors. + LOG_DEBUG << "Initialize GTK"; gtk_init(0, NULL); InstallX11ErrorHandlers(); // Now the display is available diff --git a/src/common/cefpython_public_api.h b/src/common/cefpython_public_api.h index 7de519527..0fa115d41 100644 --- a/src/common/cefpython_public_api.h +++ b/src/common/cefpython_public_api.h @@ -9,6 +9,12 @@ #ifndef CEFPYTHON_PUBLIC_API_H #define CEFPYTHON_PUBLIC_API_H +// Includes required by "cefpython_fixed.h". +#include "include/cef_client.h" +#include "include/cef_urlrequest.h" +#include "include/cef_command_line.h" +#include "util.h" + #if defined(OS_WIN) #pragma warning(disable:4190) // cefpython API extern C-linkage warnings #endif @@ -24,12 +30,6 @@ #define DL_EXPORT(RTYPE) RTYPE #endif -// Includes required by "cefpython_fixed.h". -#include "include/cef_client.h" -#include "include/cef_urlrequest.h" -#include "include/cef_command_line.h" -#include "util.h" - #if PY_MAJOR_VERSION == 2 #if PY_MINOR_VERSION == 7 #include "../../build/build_cefpython/cefpython_py27_fixed.h" diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 457e4a580..35f850027 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Darwin" -DEF PY_MAJOR_VERSION = 3 +DEF UNAME_SYSNAME = "Linux" +DEF PY_MAJOR_VERSION = 2 diff --git a/src/extern/cef/cef_types.pxd b/src/extern/cef/cef_types.pxd index 78484f6e0..60ace4f39 100644 --- a/src/extern/cef/cef_types.pxd +++ b/src/extern/cef/cef_types.pxd @@ -15,9 +15,13 @@ from libc.limits cimport UINT_MAX cdef extern from "include/internal/cef_types.h": + # noinspection PyUnresolvedReferences ctypedef int32_t int32 + # noinspection PyUnresolvedReferences ctypedef uint32_t uint32 + # noinspection PyUnresolvedReferences ctypedef int64_t int64 + # noinspection PyUnresolvedReferences ctypedef uint64_t uint64 IF UNAME_SYSNAME == "Windows": @@ -97,6 +101,11 @@ cdef extern from "include/internal/cef_types.h": CefRect() CefRect(int x, int y, int width, int height) + cdef cppclass CefSize: + int width, height + CefSize() + CefSize(int width, int height) + cdef cppclass CefPoint: pass diff --git a/src/extern/cef/cef_views.pxd b/src/extern/cef/cef_views.pxd new file mode 100644 index 000000000..09763a071 --- /dev/null +++ b/src/extern/cef/cef_views.pxd @@ -0,0 +1,84 @@ +# Copyright (c) 2017 CEF Python, see the Authors file. +# All rights reserved. Licensed under BSD 3-clause license. +# Project website: https://github.com/cztomczak/cefpython + +from cef_ptr cimport CefRefPtr +from cef_browser cimport CefWindowHandle +from cef_types cimport CefRect, CefSize +from libcpp cimport bool as cpp_bool + + +cdef extern from "include/internal/cef_types.h": + ctypedef struct cef_insets_t: + int top; + int left; + int bottom; + int right; + + ctypedef enum cef_main_axis_alignment_t: + CEF_MAIN_AXIS_ALIGNMENT_START, + CEF_MAIN_AXIS_ALIGNMENT_CENTER, + CEF_MAIN_AXIS_ALIGNMENT_END, + + ctypedef enum cef_cross_axis_alignment_t: + CEF_CROSS_AXIS_ALIGNMENT_STRETCH, + CEF_CROSS_AXIS_ALIGNMENT_START, + CEF_CROSS_AXIS_ALIGNMENT_CENTER, + CEF_CROSS_AXIS_ALIGNMENT_END, + + ctypedef struct CefBoxLayoutSettings: + int horizontal; + int inside_border_horizontal_spacing; + int inside_border_vertical_spacing; + cef_insets_t inside_border_insets; + int between_child_spacing; + cef_main_axis_alignment_t main_axis_alignment; + cef_cross_axis_alignment_t cross_axis_alignment; + int minimum_cross_axis_size; + int default_flex; + +cdef extern from "include/views/cef_box_layout.h": + cdef cppclass CefBoxLayout: + void SetFlexForView(CefRefPtr[CefWindow] view, int flex) + +cdef extern from "include/views/cef_window_delegate.h": + cdef cppclass CefWindowDelegate: + pass + +cdef extern from "include/views/cef_view.h": + cdef cppclass CefView: + pass + +cdef extern from "include/views/cef_panel_delegate.h": + cdef cppclass CefPanelDelegate: + pass + +cdef extern from "include/views/cef_panel.h": + cdef cppclass CefPanel: + @staticmethod + CefRefPtr[CefPanel] CreatePanel(CefRefPtr[CefPanelDelegate] delegate) + + +cdef extern from "include/views/cef_fill_layout.h": + cdef cppclass CefFillLayout: + pass + +cdef extern from "include/views/cef_window.h": + cdef cppclass CefWindow: + @staticmethod + CefRefPtr[CefWindow] CreateTopLevelWindow( + CefRefPtr[CefWindowDelegate] delegate) + void Show() + CefWindowHandle GetWindowHandle() + void SetBounds(const CefRect& bounds) + void CenterWindow(const CefSize& size) + void SetVisible(cpp_bool visible) + void Layout() + CefRefPtr[CefFillLayout] SetToFillLayout() + CefRefPtr[CefBoxLayout] SetToBoxLayout( + const CefBoxLayoutSettings& settings) + void RequestFocus() + void RemoveAllChildViews() + size_t GetChildViewCount() + void AddChildView(CefRefPtr[CefPanel] view) + void ReorderChildView(CefRefPtr[CefPanel] view, int index) diff --git a/src/frame.pyx b/src/frame.pyx index 9342719c6..6535791dc 100644 --- a/src/frame.pyx +++ b/src/frame.pyx @@ -19,7 +19,6 @@ cdef PyFrame GetPyFrameById(int browserId, object frameId): cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): global g_pyFrames - # This code probably ain't needed, but just to be sure. if cefFrame == NULL or not cefFrame.get(): raise Exception("GetPyFrame(): CefFrame reference is NULL") diff --git a/src/handlers/request_handler.pyx b/src/handlers/request_handler.pyx index b2cadbe7d..0df1d8670 100644 --- a/src/handlers/request_handler.pyx +++ b/src/handlers/request_handler.pyx @@ -358,6 +358,15 @@ cdef public cpp_bool RequestHandler_OnBeforePluginLoad( cdef py_bool returnValue cdef object clientCallback try: + # OnBeforePluginLoad is called from RequestContexthandler. + # The Browser object might not be available, because it is + # being set synchronously during CreateBrowserSync, after + # Browser is created. From testing it always works, however + # better be safe. + if not browser.get(): + Debug("WARNING: RequestHandler_OnBeforePluginLoad() failed," + " Browser object is not available") + return False py_browser = GetPyBrowser(browser, "OnBeforePluginLoad") py_plugin_info = CreatePyWebPluginInfo(plugin_info) clientCallback = GetGlobalClientCallback("OnBeforePluginLoad") diff --git a/src/linux/binaries_64bit/kivy_.py b/src/linux/binaries_64bit/kivy_.py index 477c4975f..7d7ef4cc0 100644 --- a/src/linux/binaries_64bit/kivy_.py +++ b/src/linux/binaries_64bit/kivy_.py @@ -187,9 +187,9 @@ def start_cef(self): # Start idle - CEF message loop work. Clock.schedule_once(self._message_loop_work, 0) - # CEF needs a valid window handle passed to SetAsOffscreen() - # for Printing to work. There is no API to get Kivy's window - # handle so creating a dummy hidden Window using GTK. + # TODO: For printing to work in off-screen-rendering mode + # it is enough to call gtk_init(). It is not required + # to provide window handle when calling SetAsOffscreen(). gtkwin = gtk.Window() gtkwin.realize() diff --git a/src/settings.pyx b/src/settings.pyx index 79efe6799..aeb663531 100644 --- a/src/settings.pyx +++ b/src/settings.pyx @@ -133,7 +133,10 @@ cdef void SetBrowserSettings( cdef CefString* cefString for key in browserSettings: - if key == "accept_language_list": + if key == "window_title": + # CEF Python only options. These are not to be found in CEF. + continue + elif key == "accept_language_list": cefString = new CefString(&cefBrowserSettings.accept_language_list) PyToCefStringPointer(browserSettings[key], cefString) del cefString diff --git a/src/subprocess/cefpython_app.cpp b/src/subprocess/cefpython_app.cpp index 9dd456a0d..63f9ad3ec 100644 --- a/src/subprocess/cefpython_app.cpp +++ b/src/subprocess/cefpython_app.cpp @@ -9,6 +9,12 @@ #include "common/cefpython_public_api.h" #endif +#if defined(OS_LINUX) +#include +#include +#include "print_handler_gtk.h" +#endif + #include "cefpython_app.h" #include "util.h" #include "include/wrapper/cef_closure_task.h" @@ -95,6 +101,13 @@ void CefPythonApp::OnContextInitialized() { #ifdef BROWSER_PROCESS REQUIRE_UI_THREAD(); #if defined(OS_LINUX) + // For print handler to work GTK must be initialized. This is + // required for hello_world.py example to work. + GdkDisplay* gdk_display = gdk_display_get_default(); + if (!gdk_display) { + LOG_DEBUG << "Initialize GTK"; + gtk_init(0, NULL); + } print_handler_ = new ClientPrintHandlerGtk(); #endif // OS_LINUX #endif // BROWSER_PROCESS diff --git a/src/subprocess/cefpython_app.h b/src/subprocess/cefpython_app.h index 63e30225c..e76b2691c 100644 --- a/src/subprocess/cefpython_app.h +++ b/src/subprocess/cefpython_app.h @@ -6,10 +6,6 @@ #include "include/cef_app.h" #include "include/cef_print_handler.h" -#if defined(OS_LINUX) -#include "print_handler_gtk.h" -#endif - #include // CefPythonApp class is instantiated in subprocess and in diff --git a/src/window_info.pyx b/src/window_info.pyx index 9cfe17fbd..2ac24df76 100644 --- a/src/window_info.pyx +++ b/src/window_info.pyx @@ -76,8 +76,10 @@ cdef class WindowInfo: cdef public py_string windowName cdef public py_bool transparentPainting - def __init__(self): + def __init__(self, title=""): self.transparentPainting = False + if title: + self.windowName = title cpdef py_void SetAsChild(self, WindowHandle parentWindowHandle, list windowRect=None): @@ -117,7 +119,8 @@ cdef class WindowInfo: % parentWindowHandle) self.parentWindowHandle = parentWindowHandle self.windowType = "popup" - self.windowName = str(windowName) + if windowName: + self.windowName = str(windowName) cpdef py_void SetAsOffscreen(self, WindowHandle parentWindowHandle): From 8315fb0ce7c42b68db8f1b53436fae13ada0d062 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 25 Mar 2017 16:29:25 +0100 Subject: [PATCH 036/398] Support for setting window title in hello_world.py example on Linux. Initialize GTK only when Print dialog is launched. --- api/cefpython.md | 20 ++++++++---------- examples/hello_world.py | 5 +++-- src/cefpython.pyx | 14 +++++++++++-- src/client_handler/util_mac.mm | 6 ++++++ src/client_handler/x11.cpp | 36 ++++++++++++++++---------------- src/client_handler/x11.h | 6 ++++++ src/extern/x11.pxd | 1 + src/subprocess/cefpython_app.cpp | 16 +++++++------- src/window_utils_linux.pyx | 3 --- 9 files changed, 64 insertions(+), 43 deletions(-) diff --git a/api/cefpython.md b/api/cefpython.md index d994c9443..3af53001f 100644 --- a/api/cefpython.md +++ b/api/cefpython.md @@ -48,22 +48,20 @@ synchronously. The async call to CefCreateBrowser is yet TODO. | window_info | [WindowInfo](WindowInfo.md) | | [settings](BrowserSettings.md) | [BrowserSettings](BrowserSettings.md) | | url | string | -| request_context | void | +| window_title | string | | __Return__ | [Browser](Browser.md) | -This function should only be called on the UI thread. The 'request_context' parameter is not yet implemented. You must first create a window and initialize 'window_info' by calling WindowInfo.SetAsChild(). +All parameters are optional. -After the call to CreateBrowserSync() the page is not yet loaded, if you want your next lines of code to do some stuff on the webpage you will have to implement [LoadHandler](LoadHandler.md).OnLoadEnd() callback, see example below: +This function can only be called on the UI thread. -```python -def OnLoadEnd(browser, frame, httpCode): - if frame == browser.GetMainFrame(): - print("Finished loading main frame: %s (http code = %d)" - % (frame.GetUrl(), httpCode)) +The "window_title" parameter will be used only +when parent window provided in window_info was set to 0. -browser = cefpython.CreateBrowserSync(windowInfo, settings, url) -browser.SetClientCallback("OnLoadEnd", OnLoadEnd) -``` +After the call to CreateBrowserSync() the page is not yet loaded, +if you want your next lines of code to do some stuff on the webpage +you will have to implement LoadHandler.[OnLoadEnd]((LoadHandler.md#onloadend)) +callback. ### ExceptHook diff --git a/examples/hello_world.py b/examples/hello_world.py index c29a2669f..bcacf379f 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -10,7 +10,8 @@ def main(): check_versions() sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error cef.Initialize() - cef.CreateBrowserSync(url="https://www.google.com/") + cef.CreateBrowserSync(window_title="Hello World!", + url="https://www.google.com/") cef.MessageLoop() cef.Shutdown() @@ -18,7 +19,7 @@ def main(): def check_versions(): print("[hello_world.py] CEF Python {ver}".format(ver=cef.__version__)) print("[hello_world.py] Python {ver} {arch}".format( - ver=platform.python_version(), arch=platform.architecture()[0])) + ver=platform.python_version(), arch=platform.architecture()[0])) assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 7e5283cfd..3863f0941 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -376,6 +376,9 @@ ELIF UNAME_SYSNAME == "Darwin": from cpp_utils cimport * from task cimport * +IF UNAME_SYSNAME == "Linux": + cimport x11 + from cef_string cimport * cdef extern from *: # noinspection PyUnresolvedReferences @@ -764,6 +767,7 @@ def CreateBrowser(**kwargs): def CreateBrowserSync(windowInfo=None, browserSettings=None, navigateUrl="", + window_title="", **kwargs): # Alternative names for existing parameters if "window_info" in kwargs: @@ -823,6 +827,9 @@ def CreateBrowserSync(windowInfo=None, elif not isinstance(windowInfo, WindowInfo): raise Exception("CreateBrowserSync() failed: windowInfo: invalid object") + if window_title and windowInfo.parentWindowHandle == 0: + windowInfo.windowName = window_title + if not browserSettings: browserSettings = {} @@ -895,10 +902,13 @@ def CreateBrowserSync(windowInfo=None, cef_window.get().RequestFocus() """ - if windowInfo.parentWindowHandle == 0 and windowInfo.windowType == "child": + if windowInfo.parentWindowHandle == 0\ + and windowInfo.windowType == "child"\ + and windowInfo.windowName: # Set window title in hello_world.py example IF UNAME_SYSNAME == "Linux": - pass + x11.SetX11WindowTitle(cefBrowser, + PyStringToChar(windowInfo.windowName)) ELIF UNAME_SYSNAME == "Darwin": pass diff --git a/src/client_handler/util_mac.mm b/src/client_handler/util_mac.mm index c0adfa09b..b1afa8f99 100644 --- a/src/client_handler/util_mac.mm +++ b/src/client_handler/util_mac.mm @@ -7,6 +7,7 @@ #include #include "include/cef_app.h" #include "include/cef_application_mac.h" +#include "include/cef_browser.h" namespace { @@ -64,3 +65,8 @@ - (void)_swizzled_terminate:(id)sender { void MacInitialize() { [NSApplication sharedApplication]; } + +void MacSetTitle(CefRefPtr browser, char* title) { + NSView* view = browser->GetHost()->GetWindowHandle(); + view.window!.title = title; +} diff --git a/src/client_handler/x11.cpp b/src/client_handler/x11.cpp index 9199f4c95..0c37bff40 100644 --- a/src/client_handler/x11.cpp +++ b/src/client_handler/x11.cpp @@ -2,23 +2,22 @@ // All rights reserved. Licensed under BSD 3-clause license. // Project website: https://github.com/cztomczak/cefpython -#include +#include "x11.h" #include "LOG_DEBUG.h" -#include "include/cef_browser.h" int XErrorHandlerImpl(Display *display, XErrorEvent *event) { - LOG_DEBUG + LOG_DEBUG << "X error received: " << "type " << event->type << ", " << "serial " << event->serial << ", " << "error_code " << static_cast(event->error_code) << ", " << "request_code " << static_cast(event->request_code) << ", " << "minor_code " << static_cast(event->minor_code); - return 0; + return 0; } int XIOErrorHandlerImpl(Display *display) { - return 0; + return 0; } void InstallX11ErrorHandlers() { @@ -29,20 +28,21 @@ void InstallX11ErrorHandlers() { XSetIOErrorHandler(XIOErrorHandlerImpl); } -void SetXWindowBounds(::Window xwindow, - int x, int y, size_t width, size_t height) { - ::Display* xdisplay = cef_get_xdisplay(); - XWindowChanges changes = {0}; - changes.x = x; - changes.y = y; - changes.width = static_cast(width); - changes.height = static_cast(height); - XConfigureWindow(xdisplay, xwindow, - CWX | CWY | CWHeight | CWWidth, &changes); -} - void SetX11WindowBounds(CefRefPtr browser, int x, int y, int width, int height) { ::Window xwindow = browser->GetHost()->GetWindowHandle(); - SetXWindowBounds(xwindow, x, y, width, height); + ::Display* xdisplay = cef_get_xdisplay(); + XWindowChanges changes = {0}; + changes.x = x; + changes.y = y; + changes.width = static_cast(width); + changes.height = static_cast(height); + XConfigureWindow(xdisplay, xwindow, + CWX | CWY | CWHeight | CWWidth, &changes); +} + +void SetX11WindowTitle(CefRefPtr browser, char* title) { + ::Window xwindow = browser->GetHost()->GetWindowHandle(); + ::Display* xdisplay = cef_get_xdisplay(); + XStoreName(xdisplay, xwindow, title); } diff --git a/src/client_handler/x11.h b/src/client_handler/x11.h index 863296fed..cd5931875 100644 --- a/src/client_handler/x11.h +++ b/src/client_handler/x11.h @@ -2,6 +2,12 @@ // All rights reserved. Licensed under BSD 3-clause license. // Project website: https://github.com/cztomczak/cefpython +#pragma once + +#include +#include "include/cef_browser.h" + void InstallX11ErrorHandlers(); void SetX11WindowBounds(CefRefPtr browser, int x, int y, int width, int height); +void SetX11WindowTitle(CefRefPtr browser, char* title); diff --git a/src/extern/x11.pxd b/src/extern/x11.pxd index 5963a1317..4e481f246 100644 --- a/src/extern/x11.pxd +++ b/src/extern/x11.pxd @@ -10,3 +10,4 @@ cdef extern from "client_handler/x11.h" nogil: void InstallX11ErrorHandlers() void SetX11WindowBounds(CefRefPtr[CefBrowser] browser, int x, int y, int width, int height) + void SetX11WindowTitle(CefRefPtr[CefBrowser] browser, char* title) diff --git a/src/subprocess/cefpython_app.cpp b/src/subprocess/cefpython_app.cpp index 63f9ad3ec..738728d86 100644 --- a/src/subprocess/cefpython_app.cpp +++ b/src/subprocess/cefpython_app.cpp @@ -101,13 +101,6 @@ void CefPythonApp::OnContextInitialized() { #ifdef BROWSER_PROCESS REQUIRE_UI_THREAD(); #if defined(OS_LINUX) - // For print handler to work GTK must be initialized. This is - // required for hello_world.py example to work. - GdkDisplay* gdk_display = gdk_display_get_default(); - if (!gdk_display) { - LOG_DEBUG << "Initialize GTK"; - gtk_init(0, NULL); - } print_handler_ = new ClientPrintHandlerGtk(); #endif // OS_LINUX #endif // BROWSER_PROCESS @@ -140,6 +133,15 @@ void CefPythonApp::OnRenderProcessThreadCreated( } CefRefPtr CefPythonApp::GetPrintHandler() { +#if defined(OS_LINUX) + // For print handler to work GTK must be initialized. This is + // required for some of the examples. + GdkDisplay* gdk_display = gdk_display_get_default(); + if (!gdk_display) { + LOG_DEBUG << "Initialize GTK"; + gtk_init(0, NULL); + } +#endif return print_handler_; } diff --git a/src/window_utils_linux.pyx b/src/window_utils_linux.pyx index ccd628e1c..63dda9c86 100644 --- a/src/window_utils_linux.pyx +++ b/src/window_utils_linux.pyx @@ -4,9 +4,6 @@ include "cefpython.pyx" -IF UNAME_SYSNAME == "Linux": - cimport x11 - class WindowUtils: # You have to overwrite this class and provide implementations # for these methods. From 90452e39b76fa3061d2f590248587d446c2d6081 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 25 Mar 2017 16:57:24 +0100 Subject: [PATCH 037/398] Support window title in hello_world.py example on Mac (#339) --- src/cefpython.pyx | 8 +++++--- src/client_handler/util_mac.h | 2 ++ src/client_handler/util_mac.mm | 5 +++-- src/compile_time_constants.pxi | 2 +- src/extern/mac.pxd | 6 +++++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 3863f0941..f50919a8b 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -813,7 +813,8 @@ def CreateBrowserSync(windowInfo=None, cef_window.get().RequestFocus() windowInfo = WindowInfo() windowInfo.SetAsChild(cef_window.get().GetWindowHandle()) - Debug("CefWindow handle = "+str(cef_window.get().GetWindowHandle())) + Debug("CefWindow handle = " + +str(cef_window.get().GetWindowHandle())) """ # Only title was set in hello_world.py example @@ -881,7 +882,7 @@ def CreateBrowserSync(windowInfo=None, Debug("CefBrowser::CreateBrowserSync() succeeded") Debug("CefBrowser window handle = " - +str(cefBrowser.get().GetHost().get().GetWindowHandle())) + +str(cefBrowser.get().GetHost().get().GetWindowHandle())) # Request context - part 2/2. if g_applicationSettings["unique_request_context_per_browser"]: @@ -910,7 +911,8 @@ def CreateBrowserSync(windowInfo=None, x11.SetX11WindowTitle(cefBrowser, PyStringToChar(windowInfo.windowName)) ELIF UNAME_SYSNAME == "Darwin": - pass + MacSetWindowTitle(cefBrowser, + PyStringToChar(windowInfo.windowName)) return pyBrowser diff --git a/src/client_handler/util_mac.h b/src/client_handler/util_mac.h index 5641a1206..37ec7266b 100644 --- a/src/client_handler/util_mac.h +++ b/src/client_handler/util_mac.h @@ -8,7 +8,9 @@ #include #include "include/cef_base.h" #include "include/cef_app.h" +#include "include/cef_browser.h" void MacInitialize(); +void MacSetWindowTitle(CefRefPtr browser, char* title); #endif // CEFPYTHON_UTIL_MAC_H_ diff --git a/src/client_handler/util_mac.mm b/src/client_handler/util_mac.mm index b1afa8f99..f2e14cd1b 100644 --- a/src/client_handler/util_mac.mm +++ b/src/client_handler/util_mac.mm @@ -66,7 +66,8 @@ void MacInitialize() { [NSApplication sharedApplication]; } -void MacSetTitle(CefRefPtr browser, char* title) { +void MacSetWindowTitle(CefRefPtr browser, char* title) { NSView* view = browser->GetHost()->GetWindowHandle(); - view.window!.title = title; + NSString* nstitle = [NSString stringWithFormat:@"%s" , title]; + view.window.title = nstitle; } diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..05306be6d 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Linux" +DEF UNAME_SYSNAME = "Darwin" DEF PY_MAJOR_VERSION = 2 diff --git a/src/extern/mac.pxd b/src/extern/mac.pxd index 6d6b73a85..cca038b98 100644 --- a/src/extern/mac.pxd +++ b/src/extern/mac.pxd @@ -2,5 +2,9 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython +from cef_ptr cimport CefRefPtr +from cef_browser cimport CefBrowser + cdef extern from "client_handler/util_mac.h": - cdef void MacInitialize() + void MacInitialize() + void MacSetWindowTitle(CefRefPtr[CefBrowser] browser, char* title) From b1156d389b613950109c3e24cbcc21fd9951263c Mon Sep 17 00:00:00 2001 From: cztomczak Date: Mon, 27 Mar 2017 11:11:48 +0200 Subject: [PATCH 038/398] Update docs for gtk functions in WindowUtils. --- api/WindowUtils.md | 7 ++++--- src/compile_time_constants.pxi | 2 +- src/settings.pyx | 5 +---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/api/WindowUtils.md b/api/WindowUtils.md index 4d292132e..a21d05b90 100644 --- a/api/WindowUtils.md +++ b/api/WindowUtils.md @@ -115,7 +115,7 @@ On Linux and Mac this method always returns True. @TODO. | long GdkNativeWindow | long | | __Return__ | void | -Linux-only. This method is utilized in the PyQt example. +Linux-only. ### gtk_widget_show (Linux) @@ -125,7 +125,7 @@ Linux-only. This method is utilized in the PyQt example. | long GtkWidget* | long | | __Return__ | void | -Linux-only. This method is utilized in the PyQt example. +Linux-only. ### InstallX11ErrorHandlers (Linux) @@ -139,4 +139,5 @@ won't be terminated on non-fatal errors. Must be done after initializing GTK. CEF Python calls this function automatically during a call to -Initialize, so there is no need to call it manually anymore. +Initialize, so there is no more need to call it manually in +most cases. diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 05306be6d..35f850027 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Darwin" +DEF UNAME_SYSNAME = "Linux" DEF PY_MAJOR_VERSION = 2 diff --git a/src/settings.pyx b/src/settings.pyx index aeb663531..79efe6799 100644 --- a/src/settings.pyx +++ b/src/settings.pyx @@ -133,10 +133,7 @@ cdef void SetBrowserSettings( cdef CefString* cefString for key in browserSettings: - if key == "window_title": - # CEF Python only options. These are not to be found in CEF. - continue - elif key == "accept_language_list": + if key == "accept_language_list": cefString = new CefString(&cefBrowserSettings.accept_language_list) PyToCefStringPointer(browserSettings[key], cefString) del cefString From 9853a1edc5ed67dca17abc68395849bd9e117fa9 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Wed, 5 Apr 2017 07:12:02 +0200 Subject: [PATCH 039/398] Fix build on Linux --- docs/Build-instructions.md | 9 +++------ src/subprocess/cefpython_app.cpp | 7 ++++++- tools/automate.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index 3281810eb..c35ac91ab 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -183,7 +183,7 @@ requirements common for all platforms. ### Linux -* Install packages: `sudo apt-get install python-dev cmake g++ libgtk2.0-dev libgtkglext1-dev` +* Install packages: `sudo apt-get install cmake g++ libgtk2.0-dev libgtkglext1-dev` * If building CEF from sources: * Official binaries are built on Ubuntu 14.04 (cmake 2.8.12, g++ 4.8.4) * Download [ninja](http://martine.github.io/ninja/) 1.7.1 or later @@ -200,11 +200,8 @@ requirements common for all platforms. [cef/BuildingOnDebian7.md](https://bitbucket.org/chromiumembedded/cef/wiki/BuildingOnDebian7.md) and [cef/#1575](https://bitbucket.org/chromiumembedded/cef/issues/1575), and [cef/#1697](https://bitbucket.org/chromiumembedded/cef/issues/1697) -* If building CEF from sources, 32-bit on 64-bit machine: - * Follow the configuration [here](https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md#markdown-header-linux-configuration) - * To perform a 32-bit Linux build on a 64-bit Linux system see - Linux configuration in upstream cef/AutomatedBuildSetup.md. See also - [cef/#1804](https://bitbucket.org/chromiumembedded/cef/issues/1804). +* Building CEF 32-bit is only possible using cross-compiling on + 64-bit machine. See [Issue #328](https://github.com/cztomczak/cefpython/issues/328). * Sometimes it is also required to install these packages (eg. chroot): `sudo apt-get install libnss3 libnspr4 libxss1 libgconf-2-4` diff --git a/src/subprocess/cefpython_app.cpp b/src/subprocess/cefpython_app.cpp index 738728d86..1f51ba270 100644 --- a/src/subprocess/cefpython_app.cpp +++ b/src/subprocess/cefpython_app.cpp @@ -9,11 +9,14 @@ #include "common/cefpython_public_api.h" #endif -#if defined(OS_LINUX) +#ifdef BROWSER_PROCESS +#ifdef OS_LINUX #include +#include #include #include "print_handler_gtk.h" #endif +#endif #include "cefpython_app.h" #include "util.h" @@ -133,6 +136,7 @@ void CefPythonApp::OnRenderProcessThreadCreated( } CefRefPtr CefPythonApp::GetPrintHandler() { +#ifdef BROWSER_PROCESS #if defined(OS_LINUX) // For print handler to work GTK must be initialized. This is // required for some of the examples. @@ -141,6 +145,7 @@ CefRefPtr CefPythonApp::GetPrintHandler() { LOG_DEBUG << "Initialize GTK"; gtk_init(0, NULL); } +#endif #endif return print_handler_; } diff --git a/tools/automate.py b/tools/automate.py index 9e192eb1c..cb97d8d2f 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -30,7 +30,7 @@ [--force-chromium-update FORCE_CHROMIUM_UPDATE] [--no-cef-update NO_CEF_UPDATE] [--cef-branch BRANCH] [--cef-commit COMMIT] - [--build-dir BUILD_DIR] [--cef-build-dir CEF_BUIL_DDIR] + [--build-dir BUILD_DIR] [--cef-build-dir CEF_BUILD_DIR] [--ninja-jobs JOBS] [--gyp-generators GENERATORS] [--gyp-msvs-version MSVS] automate.py (-h | --help) [type -h to show full description for options] @@ -900,7 +900,7 @@ def run_command(command, working_dir, env=None): env = getenv() # When passing list of args shell cannot be True on eg. Linux, read # notes in build.py - shell=(platform.system() == "Windows") + shell = (platform.system() == "Windows") return subprocess.check_call(args, cwd=working_dir, env=env, shell=shell) From 850ac552d58b02038d80e4ca921105c823414ae0 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 6 Apr 2017 22:16:06 +0200 Subject: [PATCH 040/398] Tutorial is almost complete (#256) Detect invalid arguments passed to cef.Initialize and cef.CreateBrowserSync. --- .gitignore | 1 + README.md | 16 +- api/API-categories.md | 2 +- api/ApplicationSettings.md | 6 +- api/JavascriptBindings.md | 6 - api/cefpython.md | 47 ++-- docs/Tutorial.md | 483 ++++++++++++++++++++++++++++-------- examples/Examples-README.md | 5 + examples/hello_world.py | 4 +- examples/tutorial.py | 104 ++++++++ src/cefpython.pyx | 9 + src/helpers.pyx | 55 ++-- 12 files changed, 574 insertions(+), 164 deletions(-) create mode 100644 examples/tutorial.py diff --git a/.gitignore b/.gitignore index 43cf72378..74815a1c6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build/ __pycache__/ *.pyc cefclient +webcache/ diff --git a/README.md b/README.md index 2dda89340..29ab84797 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,11 @@ also download packages for offline installation available on the pip install cefpython3==56.1 ``` +## Tutorial + +See the [Tutorial.md](docs/Tutorial.md) file. + + ## Examples See the [Examples-README.md](examples/Examples-README.md) file. @@ -53,8 +58,15 @@ See the [Examples-README.md](examples/Examples-README.md) file. ## Support - Ask questions, report problems and issues on the [Forum](https://groups.google.com/group/cefpython) -- Documentation is in the [docs/](docs) directory -- API reference is in the [api/](api) directory +- Supported examples are listed in the [Examples-README.md](examples/Examples-README.md) file +- Documentation is in the [docs/](docs) directory: + - [Build instructions](docs/Build-instructions.md) + - [Knowledge base](docs/Knowledge-Base.md) + - [Migration guide](docs/Migration-guide.md) + - [Tutorial](docs/Tutorial.md) +- API reference is in the [api/](api) directory: + - [API categories](api/API-categories.md#api-categories) + - [API index](api/API-index.md#api-index) - Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) - To search documentation use GitHub "This repository" search at the top. To narrow results to documentation only select diff --git a/api/API-categories.md b/api/API-categories.md index cceb66fe7..1f4fc9922 100644 --- a/api/API-categories.md +++ b/api/API-categories.md @@ -36,7 +36,7 @@ * [WindowUtils](WindowUtils.md#windowutils-class) class -### Handlers (interfaces) +### Client handlers (interfaces) * [DisplayHandler](DisplayHandler.md#displayhandler-interface) * [DownloadHandler](DownloadHandler.md#downloadhandler) diff --git a/api/ApplicationSettings.md b/api/ApplicationSettings.md index f651941b8..390b3905a 100644 --- a/api/ApplicationSettings.md +++ b/api/ApplicationSettings.md @@ -447,9 +447,9 @@ default User-Agent string will be used. Also configurable using the (string) The location where user data such as spell checking dictionary files will be stored on disk. If empty then the default platform-specific user data -directory will be used ("~/.cef_user_data" directory on Linux, -"~/Library/Application Support/CEF/User Data" directory on Mac OS X, -"Local Settings\Application Data\CEF\User Data" directory under the user +directory will be used (`"~/.cef_user_data"` directory on Linux, +`"~/Library/Application Support/CEF/User Data"` directory on Mac OS X, +`"Local Settings\Application Data\CEF\User Data"` directory under the user profile directory on Windows). diff --git a/api/JavascriptBindings.md b/api/JavascriptBindings.md index b00aa9527..7083cd880 100644 --- a/api/JavascriptBindings.md +++ b/api/JavascriptBindings.md @@ -29,12 +29,6 @@ In CEF 3 communication between javascript and python can only be asynchronous. I There are plans to support binding data by reference (a list, dict or object's properties). This would be possible with the use of CefRegisterExtension(). -## Example usage - -See the [wxpython.py](../src/windows/binaries_32bit/wxpython.py) example for an example usage of javascript bindings, javascript callbacks and python callbacks. - - - ## Methods diff --git a/api/cefpython.md b/api/cefpython.md index 3af53001f..3409d8b48 100644 --- a/api/cefpython.md +++ b/api/cefpython.md @@ -55,12 +55,14 @@ All parameters are optional. This function can only be called on the UI thread. -The "window_title" parameter will be used only -when parent window provided in window_info was set to 0. +The "window_title" parameter will be used only when parent +window provided in window_info was set to 0. This is for use +with hello_world.py and tutorial.py examples which don't use +any third party GUI framework for creation of top-level window. After the call to CreateBrowserSync() the page is not yet loaded, -if you want your next lines of code to do some stuff on the webpage -you will have to implement LoadHandler.[OnLoadEnd]((LoadHandler.md#onloadend)) +if you want your next lines of code to do some stuff on the +webpage you will have to implement LoadHandler.[OnLoadingStateChange]((LoadHandler.md#onloadingstatechange)) callback. @@ -68,10 +70,10 @@ callback. | Parameter | Type | | --- | --- | -| excType | - | -| excValue | - | -| traceObject | - | -| __Return__ | string | +| exc_type | - | +| exc_value | - | +| exc_trace | - | +| __Return__ | void | Global except hook to exit app cleanly on error. CEF has a multiprocess architecture and when exiting you need to close all processes (main Browser @@ -99,6 +101,7 @@ to Initialize(). Returns None if key is not found. | | | | --- | --- | +| file_ (optional) | string | | __Return__ | string | Get path to where application resides. @@ -181,18 +184,7 @@ Returns true if called on the specified thread. CEF maintains multiple internal threads that are used for handling different types of tasks. The UI thread creates the browser window and is used for all interaction with the webkit rendering engine and V8 Javascript engine. The UI thread will be the same as the main application thread if CefInitialize() is called with an [ApplicationSettings](ApplicationSettings.md) 'multi_threaded_message_loop' option set to false. The IO thread is used for handling schema and network requests. The FILE thread is used for the application cache and other miscellaneous activities. -List of threads in the Browser process. These are constants defined in the cefpython module: - -* TID_UI: The main thread in the browser. This will be the same as the main application thread if cefpython.Initialize() is called with a ApplicationSettings.multi_threaded_message_loop value of false. -* TID_DB: Used to interact with the database. -* TID_FILE: Used to interact with the file system. -* TID_FILE_USER_BLOCKING: Used for file system operations that block user interactions. Responsiveness of this thread affects users. -* TID_PROCESS_LAUNCHER: Used to launch and terminate browser processes. -* TID_CACHE: Used to handle slow HTTP cache operations. -* TID_IO: Used to process IPC and network messages. - -List of threads in the Renderer process: -* TID_RENDERER: The main thread in the renderer. Used for all webkit and V8 interaction. +See PostTask() for a list of threads. ### MessageLoop @@ -244,10 +236,23 @@ Description from upstream CEF: | ... | *args | | __Return__ | void | -Post a task for execution on the thread associated with this task runner. Execution will occur asynchronously. Only Browser process threads are allowed, see IsThread() for a list of available threads and their descriptions. +Post a task for execution on the thread associated with this task runner. Execution will occur asynchronously. Only Browser process threads are allowed. An example usage is in the wxpython.py example on Windows, in implementation of LifespanHandler.OnBeforePopup(). +List of threads in the Browser process: +* cef.TID_UI: The main thread in the browser. This will be the same as the main application thread if cefpython.Initialize() is called with a ApplicationSettings.multi_threaded_message_loop value of false. +* cef.TID_DB: Used to interact with the database. +* cef.TID_FILE: Used to interact with the file system. +* cef.TID_FILE_USER_BLOCKING: Used for file system operations that block user interactions. Responsiveness of this thread affects users. +* cef.TID_PROCESS_LAUNCHER: Used to launch and terminate browser processes. +* cef.TID_CACHE: Used to handle slow HTTP cache operations. +* cef.TID_IO: Used to process IPC and network messages. + +List of threads in the Renderer process: +* cef.TID_RENDERER: The main thread in the renderer. Used for all webkit and V8 interaction. + + ### QuitMessageLoop diff --git a/docs/Tutorial.md b/docs/Tutorial.md index e659b2632..adc50a1a8 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -1,189 +1,472 @@ -# Tutorial (STILL A WORK IN PROGRESS.. #256) +# Tutorial -This tutorial is for v50+ versions of CEF Python, which are currently -available only for Linux. +With CEF Python you can embed a web browser control based +on Chromium in a Python application. You can also use it to +create a HTML 5 based GUI in an application that can act as +a replacement for standard GUI toolkits such as wxWidgets, +Qt or GTK. With this tutorial you will learn CEF Python +basics. This tutorial will discuss the two basic examples: +[hello_world.py](../examples/hello_world.py) +and [tutorial.py](../examples/tutorial.py). There are more +many more examples that you can find in the [Examples-README.md](../examples/Examples-README.md) +file, but these examples are out of scope for this tutorial. Table of contents: -* [Install and download examples](#install-and-download-examples) +* [Install and run example](#install-and-run-example) * [Hello world](#hello-world) -* [CEF's multiprocess architecture](#cefs-multiprocess-architecture) +* [Architecture](#architecture) * [Handling Python exceptions](#handling-python-exceptions) * [Message loop](#message-loop) * [Settings](#settings) -* [Handlers](#handlers) +* [Client handlers](#client-handlers) * [Javascript integration](#javascript-integration) -* [Plugins](#plugins) -* [Helper functions](#helper-functions) +* [Javascript exceptions and Python exceptions](#javascript-exceptions-and-python-exceptions) +* [Plugins and Flash support](#plugins-and-flash-support) * [Build executable](#build-executable) -* [What's next?](#whats-next) +* [Support and documentation](#support-and-documentation) -## Install and download examples +## Install and run example -The easy way to install CEF Python is through PyPI, using the pip tool, -which is bundled with all recent versions of Python. On Linux pip 8.1+ -is required. To check version and install CEF Python type: +You can install with pip. On Linux pip 8.1+ is required. Alternatively +you can download packages for offline installation from [GitHub Releases](../../../releases). -``` -pip --version -pip install cefpython3 -``` - -Alternatively you can download the setup package from -[GitHub Releases](../../../releases) and install it by following -the instructions in README.txt. - -Now let's download examples by cloning the GitHub repository. After -that, enter the "cefpython/examples/" directory. In that directory -you will find all the examples from this Tutorial, their names -start with a "tutorial_" prefix, except for the hello world example -which is just named "hello_world.py". +Run the commands below to install the cefpython3 package, clone +the github repository, enter the examples/ directory and run the +Hello World example: ``` +pip install cefpython3==56.1 git clone https://github.com/cztomczak/cefpython.git cd cefpython/examples/ +python hello_world.py ``` +The hello_world.py example's source code will be analyzed line +by line in the next section of this Tutorial. + +This tutorial in its further sections will also reference the +tutorial.py example which will show how to use more advanced +CEF Python features. The tutorial.py example is also available +in the examples/ directory. + ## Hello world The [hello_world.py](../examples/hello_world.py) example is the -most basic example. It doesn't depend on any third party GUI frameworks. +most basic example. It doesn't depend on any third party GUI framework. It creates a browser widget without providing any window information -(parent window not specified), thus CEF automatically takes care of creating -a top-level window for us, and in that window a Chromium widget is embedded. -When creating the browser, an "url" parameter is specified, which causes the -browser to initially navigate to the Google website. Let's explain the code -from this example: +(parent window is not specified), thus CEF automatically takes care of +creating a top-level window, and in that window a Chromium widget +is being embedded. When creating the browser, the "url" parameter is +specified, which causes the browser to initially navigate to +Google website. Let's analyze the code from that example: 1. `from cefpython3 import cefpython as cef` - Import the cefpython - module and bind it to a shorter name "cef". + module and make a short "cef" alias 2. `sys.excepthook = cef.ExceptHook` - Overwrite Python's default - exception handler so that all CEF processes are terminated when - Python exception occurs. To understand this better read the - "CEF's multiprocess architecture" and "Handling Python exceptions" - sections further down in this Tutorial. + exception handler so that all CEF sub-processes are terminated + when Python exception occurs. To understand this better read the + "Architecture" and "Handling Python exceptions" sections + further down in this Tutorial. 3. `cef.Initialize()` - Initialize CEF. This function must be called somewhere in the beginning of your code. It must be called before - any app window is created. It must be called only once during app's - lifetime and must have a corresponding Shutdown() call. -4. `cef.CreateBrowserSync(url="https://www.google.com/")` - Create - a browser synchronously, this function returns the Browser object. + any application window is created. It must be called only once + during app's lifetime and must have a corresponding Shutdown() + call. +4. `cef.CreateBrowserSync(url="https://www.google.com/", ...)` - Create + a browser synchronously, this function returns a Browser object. 5. `cef.MessageLoop()` - Run CEF message loop. All desktop GUI programs run some message loop that waits and dispatches events or messages. + Read more on message loop in the "Message loop" section further + down in this tutorial. 6. `cef.Shutdown()` - Shut down CEF. This function must be called for - CEF to shut down cleanly. It will free CEF system resources, it - will terminate all subprocesses, and it will flush to disk any + CEF to shut down cleanly. It will free system resources acquired + by CEF and terminate all sub-processes, and it will flush to disk any yet unsaved data like for example cookies and/or local storage. Call this function at the very end of your program execution. When using third party GUI frameworks such as Qt/wxWidgets, CEF should be shut down after these frameworks' shutdown procedures were called. For example in Qt, shut down CEF only after QApplication object was destroyed. -Documentation for the functions from this example can be found in -API docs (the api/ directory in GitHub's repository): +Documentation for the functions referenced in this example can +be found in API reference - the [api/](../api) root directory in GitHub's +repository: * [ExceptHook](../api/cefpython.md#excepthook) -* [Initialize()](../api/cefpython.md#initialize) -* [CreateBrowserSync()](../api/cefpython.md#createbrowsersync) +* [Initialize](../api/cefpython.md#initialize) +* [CreateBrowserSync](../api/cefpython.md#createbrowsersync) * [Browser](../api/Browser.md) object -* [MessageLoop()](../api/cefpython.md#messageloop) -* [Shutdown()](../api/cefpython.md#shutdown) +* [MessageLoop](../api/cefpython.md#messageloop) +* [Shutdown](../api/cefpython.md#shutdown) + + +## Architecture + +- CEF uses multi-process architecture + - The main application process is called the “Browser” + process. In CEF Python this is the same process in + which Python is running. + - CEF Python uses a separate executable called "subprocess" + for running sub-processes. Sub-processes will be created + for renderers, plugins, GPU, etc. + - In future CEF Python might allow to run Python also in + sub-processes, for example in the Renderer process which + would allow to access more CEF API ([Issue #320](../../../issues/320)) +- Most processes in CEF have multiple threads + - Handlers' callbacks and other interfaces callbacks may + be called on various threads, this is stated in API reference + - Some functions may only be used on particular threads, + this is stated in API reference + - CEF Python provides cef.[PostTask](../api/cefpython.md#posttask) + function for posting tasks between these various threads + - The "UI" thread is application main thread unless you + use ApplicationSettings.[multi_threaded_message_loop](../api/ApplicationSettings.md#multi_threaded_messge_loop) + option on Windows in which case the UI thread will no more + be application main thread + - Do not perform blocking operations on any CEF thread other + than the Browser process FILE thread. Otherwise this can + lead to serious performance issues. -## CEF's multiprocess architecture +## Handling Python exceptions -... +Due to CEF multi-process architecture Python exceptions need +special handling. When Python exception occurs then main process +is terminated. For CEF this means that the Browser process is +terminated, however there may still be running CEF sub-processes +like Renderer process, GPU process, etc. To terminate these +sub-processes cef.[Shutdown](../api/cefpython.md#shutdown) +must be called and if running CEF message loop then it must be +stopped first. In all CEF Python examples you can find such +a line that overwrites the default exception handler in Python: +``` +sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error +``` -## Handling Python exceptions -... +See Python docs for [sys.excepthook](https://docs.python.org/2/library/sys.html#sys.excepthook). + +The cef.ExceptHook helper function does the following: +1. Calls cef.[QuitMessageLoop](../api/cefpython.md#quitmessageloop) +2. Calls cef.[Shutdown](../api/cefpython.md#shutdown) +3. Writes exception to "error.log" file +4. Prints exception +5. Calls [os._exit(1)](https://docs.python.org/2/library/os.html#os._exit) - + which exits the process with status 1, without calling + cleanup handlers, flushing stdio buffers, etc. + +See CEF Python's ExceptHook source code [here](../../../search?utf8=%E2%9C%93&q=%22def+excepthook%28exc_type%22&type=). ## Message loop Message loop is a programming construct that waits for and dispatches events or messages in a program. All desktop GUI -programs must run some kind of message loop. -The hello_world.py example doesn't depend on any third party GUI -framework and thus can run CEF message loop directly by calling call -cef.MessageLoop(). However in most of other examples that show how -to embed CEF Python browser inside GUI frameworks such as -Qt/wxPython/Tkinter, you can't call cef.MessageLoop(), because these -frameworks run a message loop of its own. For such cases CEF provides -cef.MessageLoopWork() which is for integrating CEF message loop into -existing application message loop. Usually cef.MessageLoopWork() is -called in a 10ms timer. +programs must run some kind of message loop. The hello_world.py +example doesn't depend on any third party GUI framework and thus +can run CEF message loop directly by calling cef.MessageLoop(). +However in other examples that embed CEF browser with GUI frameworks +such as Qt/wxPython/Tkinter you can't call cef.MessageLoop(), because +these frameworks run a message loop of its own. For such cases CEF +provides cef.MessageLoopWork() which is for integrating CEF message +loop into existing application message loop. Usually +cef.MessageLoopWork() is called in a 10ms timer. + +**Performance** Calling cef.MessageLoopWork() in a timer is not the best performant way to run CEF message loop, also there are known bugs on some -platforms when calling message loop work in a timer. CEF provides -ApplicationSettings.[external_message_pump](../api/ApplicationSettings.md#external_message_pump) -option for running an external message pump that you should use for -best performance and to get rid of some bugs. However this option is -still experimental, as during testing on Linux it actually made app -x2 slower - it's a bug in upstream CEF that was reported. See -[Issue #246](../../../issues/246) for more details. On Windows/Mac -external message pump should work good, but it wasn't yet tested -with CEF Python. +platforms when calling message loop work in a timer. There are two +options to increase performance depending on platform. On Windows +use a multi-threaded message loop for best performance. On Mac use +an external message pump for best performance. + +**Windows: multi-threaded message loop** On Windows for best performance a multi-threaded message loop should -be used, instead of cef.MessageLoopWork / external message pump. To do -so, set -ApplicationSettings.[multi_threaded_message_loop](../ApplicationSettings.md#multi_threaded_message_loop) +be used instead of cef.MessageLoopWork() or external message pump. To do +so, set ApplicationSettings.[multi_threaded_message_loop](../ApplicationSettings.md#multi_threaded_message_loop) to True and run a native message loop in your app. Don't call CEF's -message loop. Create browser using -cef.PostTask(cef.TID_UI, cef.CreateBrowserSync, ...). +message loop. Create browser using `cef.PostTask(cef.TID_UI, cef.CreateBrowserSync, ...)`. Note that when using multi-threaded message loop, CEF's UI thread is no more application's main thread, and that makes it a bit harder to correctly use CEF API. API docs explain on which threads a function may be called and in case of handlers' callbacks (and other interfaces) -it is stated on which thread a callback will be called. -See also [Issue #133](../../../issues/133). +it is stated on which thread a callback will be called. See also +[Issue #133](../../../issues/133). + +**Mac: external message pump** + +CEF provides ApplicationSettings.[external_message_pump](../api/ApplicationSettings.md#external_message_pump) +option for running an external message pump that you should use for +best performance and to get rid of some bugs that appear when using +cef.MessageLoopWork() in a timer. + +This option is currently marked experimental as it wasn't yet fully +tested. This option should work good on Mac - in upstream CEF it was +tested mainly on Mac. If you've successfully used this option on Mac +please let us know on the Forum. + +**Linux** + +External message pump option is not recommended to use on Linux, +as during testing it actually made app x2 slower - it's a bug in +upstream CEF. See [Issue #246](../../../issues/246) for more details. ## Settings -ApplicationSettings, BrowserSettings, CommandLineSwitches, Chromium -Preferences (not implemented yet) ... +CEF settings are provided in multiple ways. There are global +[application settings](../api/ApplicationSettings.md#application-settings) +and [command line switches](../api/CommandLineSwitches.md#command-line-switches) +that can be passed to cef.[Initialize](../api/cefpython.md#initialize). +There are also [browser settings](../api/BrowserSettings.md#browser-settings) +that can be passed to cef.[CreateBrowserSync](../api/cefpython.md#createbrowsersync). +Finally there are Chromium preferences, but these are not yet +implemented. See below for details on each of these settings. + +**Application settings** + +A dict of [application settings](../api/ApplicationSettings.md#application-settings) +can be passed to cef.[Initialize](../api/cefpython.md#initialize). +Here are some settings worth noting: +- [cache_path](../api/ApplicationSettings.md#cache_path) - set + a directory path so that web cache data is persisted, otherwise + an in-memory cache is used. Cookies and HTML 5 databases such + as local storage will only persist if this option is set. +- [context_menu](../api/ApplicationSettings.md#context_menu) - + customize context menu +- [locale](../api/ApplicationSettings.md#locale) - set language + for localized resources + +To enable debugging set these settings: +``` +settings = { + "debug": True, + "log_severity": cef.LOGSEVERITY_WARNING, + "log_file": "debug.log", +} +cef.Initialize(settings=settings) +``` + +Alternatively you can pass `--debug` flag on the command line +and these settings will be set automatically. + +**Browser settings** + +A dict of [browser settings](../api/BrowserSettings.md#browser-settings) +can be passed to cef.[CreateBrowserSync](../api/cefpython.md#createbrowsersync). + +**Command line switches** +A dict of [command line switches](../api/CommandLineSwitches.md) +can be passed to cef.[Initialize](../api/cefpython.md#initialize). +Examples switches: +- "enable-media-stream" - to enable media (WebRTC audio/video) streaming +- "proxy-server" - to set proxy server +- "disable-gpu" - use only CPU software rendering + +Example code: + +``` +switches = { + "enable-media-stream": "", + "proxy-server": "socks5://127.0.0.1:8888", + "disable-gpu": "", +} +cef.Initialize(switches=switches) +``` -## Handlers +Note that when setting switch that doesn't accept value then +must pass an empty string as value. + +**Chromium preferences** + +There are lots of more settings that can be set using Chromium +preferences (and even changed during runtime), however this API +wasn't yet exposed to CEF Python, see [Issue #244](../../../issues/244) +for details. + + +## Client handlers + +In CEF [client handlers](../api/API-categories.md#client-handlers-interfaces) +provide a way to be notified of Chromium events. There are client +handlers like DisplayHandler, LoadHandler, RequestHandler, etc. +These handlers are class interfaces for which you provide +implementation. We will refer to the methods of these objects +as "callbacks". You can set a client handler by calling +Browser.[SetClientHandler](../api/Browser.md#setclienthandler). +You are not required to implement whole interface, you can implement +only some callbacks. Some handlers due to cefpython limitations +have callbacks that can only be set globally by calling +cef.[SetGlobalClientCallback](../api/cefpython.md#setglobalclientcallback). +In API reference such global client callbacks are marked with an +underscore in its name. + +The [tutorial.py](../examples/tutorial.py) example shows how to +implement client handlers like [DisplayHandler](../api/DisplayHandler.md) +and [LoadHandler](../api/LoadHandler.md). It also shows how to +implement a global client callback LifespanHandler.[_OnAfterCreated](../api/LifespanHandler.md#_onaftercreated). Here is some source code: + +``` +set_client_handlers(browser) ... +def set_client_handlers(browser): + client_handlers = [LoadHandler(), DisplayHandler()] + for handler in client_handlers: + browser.SetClientHandler(handler) +... +class LoadHandler(object): + def OnLoadingStateChange(self, browser, is_loading, **_): + if not is_loading: + # Loading is complete + js_print(browser, "Python: LoadHandler.OnLoadingStateChange:" + "loading is complete") +``` ## Javascript integration +Python code is running in the main process (the Browser process), +while Javascript is running in the Renderer sub-process. Communication +between Python and Javascript is possible either using inter-process +asynchronous messaging or through http requests (both sync and +async possible). + +**Asynchronous inter-process messaging** + +Python and Javascript can communicate using inter-process +messaging: + - Use the [JavascriptBindings](../api/JavascriptBindings.md) + class methods to to expose Python functions, objects and properties + to Javascript: [SetFunction](../api/JavascriptBindings.md#setfunctions), + [SetObject](../api/JavascriptBindings.md#setobject) + and [SetProperty](../api/JavascriptBindings.md#setproperty) + - To initiate communication from the Python side call + Browser object methods: [ExecuteJavascript](../api/Browser.md#executejavascript) + or [ExecuteFunction](../api/Browser.md#executefunction). + Frame object also has the same methods. + - To initiate communication from the Javascript side first + you have to bind Python functions/objects using the + JavascriptBindings class mentioned earlier. Then you call + these functions/objects. + - You can pass Javascript callbacks to Python. Just pass a + javascript function as an argument when calling Python + function/object. On the Python side that javascript function + will be converted to [JavascriptCallback](../api/JavascriptCallback.md) + object. Execute the [Call](../api/JavascriptCallback.md#call) + method on it to call the javascript function asynchronously. + - You can pass Python callbacks to Javascript, however you + can do so only after the communication was initiated from + the Javascript side and a javascript callback was passed. + When executing JavascriptCallback.[Call](../api/JavascriptCallback.md#call) + method you can pass Python callbacks to Javascript. In + javascript these Python callbacks will act as native + javascript functions, so call them as usual. + - Note that when executing Browser.[ExecuteFunction](../api/Browser.md#executefunction) method you cannot pass Python functions + nor objects here. Such feature is not yet supported. You can + however pass Python functions when executing javascript + callbacks mentioned earlier. + +In tutorial.py example you will find example code that uses +javascript bindings and other APIs mentioned above. + +... ... -## Plugins +**Communication using http requests** -... +Python and Javascript can also communicate using http requests +by running an internal web-server. See for example [SimpleHTTPServer](https://docs.python.org/2/library/simplehttpserver.html) +in Python docs. + +With http requests it is possible for synchronous +communication from Javascript to Python by performing +synchronous AJAX requests. + +To initiate communication from the Python side call +Browser object methods: [ExecuteJavascript](../api/Browser.md#executejavascript) +or [ExecuteFunction](../api/Browser.md#executefunction). +Frame object also has the same methods. + +You can also serve requests directly in CEF using for example +[ResourceHandler](../api/ResourceHandler.md) object. You can find +an example usage of this object in one of examples listed in +the [Examples-README.md](../examples/Examples-README.md) file. +On a side note, upstream CEF also supports custom scheme handlers, +however these APIs were not yet exposed to CEF Python. -## Helper functions -GetApplicationPath... -GetModulePath... -others... +## Javascript exceptions and Python exceptions + +There are cases when executing Javascript code may end up with +Python exception being thrown: + +1. When a Python function is invoked from Javascript and it fails, + a Python exception will be thrown +2. When Python executes a Javascript callback and it fails, + a Python exception will be thrown + +In other cases to see Javascript exceptions open Developer Tools +window using mouse context menu and switch to Console tab. + +There are multiple ways to catch/intercept javascript exceptions: + +1. In Javascript you can register "window.onerror" event to + catch all Javascript exceptions +2. In Python you can intercept Javascript exceptions using + DisplayHandler.[OnConsoleMessage](../api/DisplayHandler.md#onconsolemessage) +3. In upstream CEF there is also CefRenderProcessHandler::OnUncaughtException + callback for catching Javascript exceptions, however this + wasn't yet exposed to CEF Python + + +## Plugins and Flash support + +Latest CEF supports only [PPAPI plugins](https://www.chromium.org/developers/design-documents/pepper-plugin-implementation). +NPAPI plugins are no more supported. + +Instructions for enabling Flash support are available in [Issue #235](../../../issues/235) ("Flash support in CEF 51+"). + +For the old CEF Python v31 release instructions for enabling Flash +support are available on Wiki pages. + ## Build executable -Examples for building an executable are yet to be created: +There are no official examples for building executable using +python packaging tools, however some resources and tips are +available, see these issues in the tracker: + +* Pyinstaller - see [Issue #135](../../../issues/135) +* py2exe - see [Issue #35](../../../issues/35) +* py2app - see [Issue #337](../../../issues/337) +* cx_Freeze - see [Issue #338](../../../issues/338) + +If you have any problems building executable ask on the [Forum](https://groups.google.com/group/cefpython). + +**Files in the cefpython3 package** + +The cefpython3 package has the following components: +1. The CEF Python modules (cefpython_pyxx.pyd on Windows, + cefpython_pyxx.so on Linux/Mac) +2. The CEF dynamic library (libcef.dll on Windows, libcef.so on Linux, + “Chromium Embedded Framework.framework” on OS X). +3. Other dynamic libraries CEF depends on (libEGL, libGLES, + d3dcompiler, etc.) and some optional (widevinecdmadapter, etc.) +4. Support files (*.pak, *.dat, *.bin, etc). -* On Windows use py2exe ([#35](../../../issues/35)) - or pyinstaller ([#135](../../../issues/135)) -* On Mac use py2app or pyinstaller -* On Linux use pyinstaller or cx_freeze +See README.txt in the cefpython3 package which provides +extended details about all CEF binary files. -## What's next? +## Support and documentation -See more examples in the examples/ directory -See API docs in the api/ directory -Example usage of most of API is available in the unittests/ directory -See the Knowledge base document -Ask questions and report problems on the Forum +For support and documentation see the [Support](../README.md#support) +section in README. diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 6e76fb4d7..3b93066cb 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -39,6 +39,11 @@ maintained: If there are any issues in examples read top comments in sources to see whether this is a known issue with available workarounds. +**Unit tests** + +There are also available unit tests and its usage of the API can +be of some use. See [main_test.py](../unittests/main_test.py). + ## More examples diff --git a/examples/hello_world.py b/examples/hello_world.py index bcacf379f..0157267f5 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -10,8 +10,8 @@ def main(): check_versions() sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error cef.Initialize() - cef.CreateBrowserSync(window_title="Hello World!", - url="https://www.google.com/") + cef.CreateBrowserSync(url="https://www.google.com/", + window_title="Hello World!") cef.MessageLoop() cef.Shutdown() diff --git a/examples/tutorial.py b/examples/tutorial.py new file mode 100644 index 000000000..be85b3d4d --- /dev/null +++ b/examples/tutorial.py @@ -0,0 +1,104 @@ +# Tutorial example. +# Tested with CEF Python v56.1+ + +from cefpython3 import cefpython as cef +import base64 +import platform +import sys + +# HTML code. Browser will navigate to a Data uri created +# from this html code. +HTML_code = """ +test +""" + + +def main(): + check_versions() + sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error + settings = {"cache_path": "webcache"} + cef.Initialize(settings=settings) + set_global_handler() + browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code), + window_title="Hello World!") + set_client_handlers(browser) + set_javascript_bindings(browser) + cef.MessageLoop() + cef.Shutdown() + + +def check_versions(): + print("[tutorial.py] CEF Python {ver}".format(ver=cef.__version__)) + print("[tutorial.py] Python {ver} {arch}".format( + ver=platform.python_version(), arch=platform.architecture()[0])) + assert cef.__version__ >= "56.1", "CEF Python v56.1+ required to run this" + + +def html_to_data_uri(html): + html = html.encode("utf-8", "replace") + b64 = base64.b64encode(html).decode("utf-8", "replace") + return "data:text/html;base64,{data}".format(data=b64) + + +def set_global_handler(): + # A global handler is a special handler for callbacks that + # must be set before Browser is created using + # SetGlobalClientCallback() method. + global_handler = GlobalHandler() + cef.SetGlobalClientCallback("OnAfterCreated", + global_handler.OnAfterCreated) + + +def set_client_handlers(browser): + client_handlers = [LoadHandler(), DisplayHandler()] + for handler in client_handlers: + browser.SetClientHandler(handler) + + +def set_javascript_bindings(browser): + external = External(browser) + bindings = cef.JavascriptBindings( + bindToFrames=False, bindToPopups=False) + bindings.SetFunction("html_to_data_uri", html_to_data_uri) + bindings.SetProperty("test_property", "This property was set in Python") + bindings.SetObject("external", external) + browser.SetJavascriptBindings(bindings) + + +def js_print(browser, msg): + browser.ExecuteFunction("js_print", msg) + + +class GlobalHandler(object): + def OnAfterCreated(self, browser, **_): + js_print(browser, + "Python: GlobalHandler._OnAfterCreated: browser id={id}" + .format(id=browser.GetIdentifier())) + + +class LoadHandler(object): + def OnLoadingStateChange(self, browser, is_loading, **_): + if not is_loading: + # Loading is complete + js_print(browser, "Python: LoadHandler.OnLoadingStateChange:" + "loading is complete") + + +class DisplayHandler(object): + def OnConsoleMessage(self, browser, message, **_): + if "error" in message.lower() or "uncaught" in message.lower(): + js_print(browser, "Python: LoadHandler.OnConsoleMessage: " + "intercepted Javascript error: {error}" + .format(error=message)) + + +class External(object): + def __init__(self, browser): + self.browser = browser + + def test_function(self): + pass + + +if __name__ == '__main__': + main() diff --git a/src/cefpython.pyx b/src/cefpython.pyx index f50919a8b..dc7a672e1 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -593,9 +593,13 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): if "settings" in kwargs: assert not applicationSettings, "Bad arguments" application_settings = kwargs["settings"] + del kwargs["settings"] if "switches" in kwargs: assert not command_line_switches, "Bad arguments" command_line_switches = kwargs["switches"] + del kwargs["switches"] + for kwarg in kwargs: + raise Exception("Invalid argument: "+kwarg) IF UNAME_SYSNAME == "Linux": # Fix Issue #231 - Discovery of the "icudtl.dat" file fails on Linux. @@ -772,10 +776,15 @@ def CreateBrowserSync(windowInfo=None, # Alternative names for existing parameters if "window_info" in kwargs: windowInfo = kwargs["window_info"] + del kwargs["window_info"] if "settings" in kwargs: browserSettings = kwargs["settings"] + del kwargs["settings"] if "url" in kwargs: navigateUrl = kwargs["url"] + del kwargs["url"] + for kwarg in kwargs: + raise Exception("Invalid argument: "+kwarg) Debug("CreateBrowserSync() called") assert IsThread(TID_UI), ( diff --git a/src/helpers.pyx b/src/helpers.pyx index 595d2cd09..dbfb22e8b 100644 --- a/src/helpers.pyx +++ b/src/helpers.pyx @@ -32,7 +32,7 @@ cpdef str GetModuleDirectory(): g_GetAppPath_dir = None -cpdef str GetAppPath(file=None): +cpdef str GetAppPath(file_=None): """Get application path.""" # On Windows after downloading file and calling Browser.GoForward(), # current working directory is set to %UserProfile%. @@ -47,49 +47,46 @@ cpdef str GetAppPath(file=None): global g_GetAppPath_dir g_GetAppPath_dir = adir # If file is None return current directory without trailing slash. - if file is None: - file = "" + if file_ is None: + file_ = "" # Only when relative path. - if not file.startswith("/") and not file.startswith("\\") and ( - not re.search(r"^[\w-]+:", file)): - path = g_GetAppPath_dir + os.sep + file + if not file_.startswith("/") and not file_.startswith("\\") and ( + not re.search(r"^[\w-]+:", file_)): + path = g_GetAppPath_dir + os.sep + file_ if platform.system() == "Windows": path = re.sub(r"[/\\]+", re.escape(os.sep), path) path = re.sub(r"[/\\]+$", "", path) return path - return str(file) + return str(file_) -cpdef py_void ExceptHook(excType, excValue, traceObject): - """Global except hook to exit app cleanly on error.""" - # This hook does the following: in case of exception write it to - # the "error.log" file, display it to the console, shutdown CEF - # and exit application immediately by ignoring "finally" (_exit()). - print("[CEF Python] ExceptHook: catched exception, will shutdown CEF now") +def ExceptHook(exc_type, exc_value, exc_trace): + """Global except hook to exit app cleanly on error. + This hook does the following: in case of exception write it to + the "error.log" file, display it to the console, shutdown CEF + and exit application immediately by ignoring "finally" (_exit()). + """ + print("[CEF Python] ExceptHook: catched exception, will shutdown CEF") QuitMessageLoop() Shutdown() - print("[CEF Python] ExceptHook: see the catched exception below:") - errorMsg = "".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetAppPath("error.log") + msg = "".join(traceback.format_exception(exc_type, exc_value, + exc_trace)) + error_file = GetAppPath("error.log") + encoding = GetAppSetting("string_encoding") or "utf-8" + if type(msg) == bytes: + msg = msg.decode(encoding=encoding, errors="replace") try: - appEncoding = g_applicationSettings["string_encoding"] - except: - appEncoding = "utf-8" - if type(errorMsg) == bytes: - errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace") - try: - with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp: + with codecs.open(error_file, mode="a", encoding=encoding) as fp: fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg)) + time.strftime("%Y-%m-%d %H:%M:%S"), msg)) except: print("[CEF Python] WARNING: failed writing to error file: %s" % ( - errorFile)) + error_file)) # Convert error message to ascii before printing, otherwise # you may get error like this: # | UnicodeEncodeError: 'charmap' codec can't encode characters - errorMsg = errorMsg.encode("ascii", errors="replace") - errorMsg = errorMsg.decode("ascii", errors="replace") - print("\n"+errorMsg) + msg = msg.encode("ascii", errors="replace") + msg = msg.decode("ascii", errors="replace") + print("\n"+msg) # noinspection PyProtectedMember os._exit(1) From bc4e03893c00efab22e46f993ec681154e092f39 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 7 Apr 2017 10:56:55 +0200 Subject: [PATCH 041/398] Complete Tutorial (#256) and fix #344. Do not inherit client handlers nor javascript bindings in DevTools windows (#344). --- README.md | 1 + api/JavascriptBindings.md | 1 - docs/Tutorial.md | 52 +++++++++++++-- examples/tutorial.py | 132 ++++++++++++++++++++++++++++++++------ src/browser.pyx | 8 +-- src/helpers.pyx | 64 +++++++++--------- 6 files changed, 194 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 29ab84797..bcf0ec45d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Table of contents: * [Introduction](#introduction) * [Install](#install) +* [Tutorial](#tutorial) * [Examples](#examples) * [Support](#support) * [Releases](#releases) diff --git a/api/JavascriptBindings.md b/api/JavascriptBindings.md index 7083cd880..ccfc51e2f 100644 --- a/api/JavascriptBindings.md +++ b/api/JavascriptBindings.md @@ -6,7 +6,6 @@ Table of contents: * [Introduction](#introduction) -* [Example usage](#example-usage) * [Methods](#methods) * [\_\_init\_\_()](#__init__) * [IsValueAllowed](#isvalueallowed) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index adc50a1a8..21869177b 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -157,7 +157,7 @@ The cef.ExceptHook helper function does the following: which exits the process with status 1, without calling cleanup handlers, flushing stdio buffers, etc. -See CEF Python's ExceptHook source code [here](../../../search?utf8=%E2%9C%93&q=%22def+excepthook%28exc_type%22&type=). +See CEF Python's ExceptHook source code in src/[helpers.pyx](../src/helpers.pyx). ## Message loop @@ -322,10 +322,17 @@ def set_client_handlers(browser): ... class LoadHandler(object): def OnLoadingStateChange(self, browser, is_loading, **_): + # Issue #344 will fix this in next release, so that client + # handlers are not called for Developer Tools windows. + if browser.GetUrl().startswith("chrome-devtools://"): + return + # This callback is called twice, once when loading starts + # (is_loading=True) and second time when loading ends + # (is_loading=False). if not is_loading: - # Loading is complete - js_print(browser, "Python: LoadHandler.OnLoadingStateChange:" - "loading is complete") + # Loading is complete. DOM is ready. + js_print(browser, "Python", "OnLoadingStateChange", + "Loading is complete") ``` @@ -372,12 +379,43 @@ messaging: however pass Python functions when executing javascript callbacks mentioned earlier. -In tutorial.py example you will find example code that uses -javascript bindings and other APIs mentioned above. +In [tutorial.py](../examples/tutorial.py) example you will find +example usage of javascript bindings, javascript callbacks +and python callbacks. Here is some source code: +``` +set_javascript_bindings(browser) ... +def set_javascript_bindings(browser): + bindings = cef.JavascriptBindings( + bindToFrames=False, bindToPopups=False) + bindings.SetFunction("html_to_data_uri", html_to_data_uri) + browser.SetJavascriptBindings(bindings) ... - +def html_to_data_uri(html, js_callback=None): + # This function is called in two ways: + # 1. From Python: in this case value is returned + # 2. From Javascript: in this case value cannot be returned because + # inter-process messaging is asynchronous, so must return value + # by calling js_callback. + html = html.encode("utf-8", "replace") + b64 = base64.b64encode(html).decode("utf-8", "replace") + ret = "data:text/html;base64,{data}".format(data=b64) + if js_callback: + js_print(js_callback.GetFrame().GetBrowser(), + "Python", "html_to_data_uri", + "Called from Javascript. Will call Javascript callback now.") + js_callback.Call(ret) + else: + return ret +... + +``` **Communication using http requests** diff --git a/examples/tutorial.py b/examples/tutorial.py index be85b3d4d..88304abf3 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -5,19 +5,60 @@ import base64 import platform import sys +import threading # HTML code. Browser will navigate to a Data uri created # from this html code. HTML_code = """ -test + + + + + + + + +

Tutorial example

+
+ + """ def main(): check_versions() sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error - settings = {"cache_path": "webcache"} - cef.Initialize(settings=settings) + cef.Initialize() set_global_handler() browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code), window_title="Hello World!") @@ -34,10 +75,22 @@ def check_versions(): assert cef.__version__ >= "56.1", "CEF Python v56.1+ required to run this" -def html_to_data_uri(html): +def html_to_data_uri(html, js_callback=None): + # This function is called in two ways: + # 1. From Python: in this case value is returned + # 2. From Javascript: in this case value cannot be returned because + # inter-process messaging is asynchronous, so must return value + # by calling js_callback. html = html.encode("utf-8", "replace") b64 = base64.b64encode(html).decode("utf-8", "replace") - return "data:text/html;base64,{data}".format(data=b64) + ret = "data:text/html;base64,{data}".format(data=b64) + if js_callback: + js_print(js_callback.GetFrame().GetBrowser(), + "Python", "html_to_data_uri", + "Called from Javascript. Will call Javascript callback now.") + js_callback.Call(ret) + else: + return ret def set_global_handler(): @@ -59,45 +112,86 @@ def set_javascript_bindings(browser): external = External(browser) bindings = cef.JavascriptBindings( bindToFrames=False, bindToPopups=False) + bindings.SetProperty("python_property", "This property was set in Python") bindings.SetFunction("html_to_data_uri", html_to_data_uri) - bindings.SetProperty("test_property", "This property was set in Python") bindings.SetObject("external", external) browser.SetJavascriptBindings(bindings) -def js_print(browser, msg): - browser.ExecuteFunction("js_print", msg) +def js_print(browser, lang, event, msg): + # Execute Javascript function "js_print" + browser.ExecuteFunction("js_print", lang, event, msg) class GlobalHandler(object): def OnAfterCreated(self, browser, **_): - js_print(browser, - "Python: GlobalHandler._OnAfterCreated: browser id={id}" - .format(id=browser.GetIdentifier())) + # Issue #344 will fix this in next release, so that client + # handlers are not called for Developer Tools windows. + if browser.GetUrl().startswith("chrome-devtools://"): + return + # DOM is not yet loaded. Using js_print at this moment will + # throw an error: "Uncaught ReferenceError: js_print is not defined". + # We make this error on purpose. This error will be intercepted + # in DisplayHandler.OnConsoleMessage. + js_print(browser, "Python", "OnAfterCreated", + "This will probably never display as DOM is not yet loaded") + # Delay print by 0.5 sec, because js_print is not available yet + args = [browser, "Python", "OnAfterCreated", + "(Delayed) Browser id="+str(browser.GetIdentifier())] + threading.Timer(0.5, js_print, args).start() class LoadHandler(object): def OnLoadingStateChange(self, browser, is_loading, **_): + # Issue #344 will fix this in next release, so that client + # handlers are not called for Developer Tools windows. + if browser.GetUrl().startswith("chrome-devtools://"): + return + # This callback is called twice, once when loading starts + # (is_loading=True) and second time when loading ends + # (is_loading=False). if not is_loading: - # Loading is complete - js_print(browser, "Python: LoadHandler.OnLoadingStateChange:" - "loading is complete") + # Loading is complete. DOM is ready. + js_print(browser, "Python", "OnLoadingStateChange", + "Loading is complete") class DisplayHandler(object): def OnConsoleMessage(self, browser, message, **_): + # Issue #344 will fix this in next release, so that client + # handlers are not called for Developer Tools windows. + if browser.GetUrl().startswith("chrome-devtools://"): + return + # This will intercept js errors, see comments in OnAfterCreated if "error" in message.lower() or "uncaught" in message.lower(): - js_print(browser, "Python: LoadHandler.OnConsoleMessage: " - "intercepted Javascript error: {error}" - .format(error=message)) + # Prevent infinite recurrence in case something went wrong + if "js_print is not defined" in message.lower(): + if hasattr(self, "js_print_is_not_defined"): + print("Python: OnConsoleMessage: " + "Intercepted Javascript error: "+message) + return + else: + self.js_print_is_not_defined = True + # Delay print by 0.5 sec, because js_print may not be + # available yet due to DOM not ready. + args = [browser, "Python", "OnConsoleMessage", + "(Delayed) Intercepted Javascript error: {error}" + .format(error=message)] + threading.Timer(0.5, js_print, args).start() class External(object): def __init__(self, browser): self.browser = browser - def test_function(self): - pass + def test_multiple_callbacks(self, js_callback): + """Test both javascript and python callbacks.""" + js_print(self.browser, "Python", "test_multiple_callbacks", + "Called from Javascript. Will call Javascript callback now.") + + def py_callback(msg_from_js): + js_print(self.browser, "Python", "py_callback", msg_from_js) + js_callback.Call("String sent from Python", py_callback) if __name__ == '__main__': diff --git a/src/browser.pyx b/src/browser.pyx index edb3071a9..406a01c90 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -101,8 +101,8 @@ cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser, # - Popups inherit javascript bindings only when "bindToPopups" # constructor param was set to True. - if pyBrowser.IsPopup() and \ - not pyBrowser.GetUserData("__outerWindowHandle"): + if pyBrowser.IsPopup()\ + and not pyBrowser.GetUserData("__outerWindowHandle"): openerHandle = pyBrowser.GetOpenerWindowHandle() for identifier, tempPyBrowser in g_pyBrowsers.items(): if tempPyBrowser.GetWindowHandle() == openerHandle: @@ -457,12 +457,10 @@ cdef class PyBrowser: window_info.SetAsPopup( self.GetOpenerWindowHandle(), PyToCefStringValue("DevTools")) - cdef CefRefPtr[ClientHandler] client_handler =\ - new ClientHandler() cdef CefBrowserSettings settings cdef CefPoint inspect_element_at self.GetCefBrowserHost().get().ShowDevTools( - window_info, client_handler, settings, + window_info, NULL, settings, inspect_element_at) cpdef py_void StopLoad(self): diff --git a/src/helpers.pyx b/src/helpers.pyx index dbfb22e8b..ea8d5e8b8 100644 --- a/src/helpers.pyx +++ b/src/helpers.pyx @@ -12,6 +12,38 @@ import time import codecs +def ExceptHook(exc_type, exc_value, exc_trace): + """Global except hook to exit app cleanly on error. + This hook does the following: in case of exception write it to + the "error.log" file, display it to the console, shutdown CEF + and exit application immediately by ignoring "finally" (_exit()). + """ + print("[CEF Python] ExceptHook: catched exception, will shutdown CEF") + QuitMessageLoop() + Shutdown() + msg = "".join(traceback.format_exception(exc_type, exc_value, + exc_trace)) + error_file = GetAppPath("error.log") + encoding = GetAppSetting("string_encoding") or "utf-8" + if type(msg) == bytes: + msg = msg.decode(encoding=encoding, errors="replace") + try: + with codecs.open(error_file, mode="a", encoding=encoding) as fp: + fp.write("\n[%s] %s\n" % ( + time.strftime("%Y-%m-%d %H:%M:%S"), msg)) + except: + print("[CEF Python] WARNING: failed writing to error file: %s" % ( + error_file)) + # Convert error message to ascii before printing, otherwise + # you may get error like this: + # | UnicodeEncodeError: 'charmap' codec can't encode characters + msg = msg.encode("ascii", errors="replace") + msg = msg.decode("ascii", errors="replace") + print("\n"+msg) + # noinspection PyProtectedMember + os._exit(1) + + cpdef str GetModuleDirectory(): """Get path to the cefpython module (so/pyd).""" if platform.system() == "Linux" and os.getenv("CEFPYTHON3_PATH"): @@ -58,35 +90,3 @@ cpdef str GetAppPath(file_=None): path = re.sub(r"[/\\]+$", "", path) return path return str(file_) - - -def ExceptHook(exc_type, exc_value, exc_trace): - """Global except hook to exit app cleanly on error. - This hook does the following: in case of exception write it to - the "error.log" file, display it to the console, shutdown CEF - and exit application immediately by ignoring "finally" (_exit()). - """ - print("[CEF Python] ExceptHook: catched exception, will shutdown CEF") - QuitMessageLoop() - Shutdown() - msg = "".join(traceback.format_exception(exc_type, exc_value, - exc_trace)) - error_file = GetAppPath("error.log") - encoding = GetAppSetting("string_encoding") or "utf-8" - if type(msg) == bytes: - msg = msg.decode(encoding=encoding, errors="replace") - try: - with codecs.open(error_file, mode="a", encoding=encoding) as fp: - fp.write("\n[%s] %s\n" % ( - time.strftime("%Y-%m-%d %H:%M:%S"), msg)) - except: - print("[CEF Python] WARNING: failed writing to error file: %s" % ( - error_file)) - # Convert error message to ascii before printing, otherwise - # you may get error like this: - # | UnicodeEncodeError: 'charmap' codec can't encode characters - msg = msg.encode("ascii", errors="replace") - msg = msg.decode("ascii", errors="replace") - print("\n"+msg) - # noinspection PyProtectedMember - os._exit(1) From bacb63587edcaed65fc8a859692d618d8acd1600 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 7 Apr 2017 11:08:33 +0200 Subject: [PATCH 042/398] Fix links --- docs/Tutorial.md | 21 +++++++++++---------- examples/Examples-README.md | 1 + examples/tutorial.py | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 21869177b..9abd7c13a 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -36,7 +36,7 @@ Run the commands below to install the cefpython3 package, clone the github repository, enter the examples/ directory and run the Hello World example: -``` +```commandline pip install cefpython3==56.1 git clone https://github.com/cztomczak/cefpython.git cd cefpython/examples/ @@ -142,7 +142,7 @@ must be called and if running CEF message loop then it must be stopped first. In all CEF Python examples you can find such a line that overwrites the default exception handler in Python: -``` +```python sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error ``` @@ -187,7 +187,7 @@ an external message pump for best performance. On Windows for best performance a multi-threaded message loop should be used instead of cef.MessageLoopWork() or external message pump. To do -so, set ApplicationSettings.[multi_threaded_message_loop](../ApplicationSettings.md#multi_threaded_message_loop) +so, set ApplicationSettings.[multi_threaded_message_loop](../api/ApplicationSettings.md#multi_threaded_message_loop) to True and run a native message loop in your app. Don't call CEF's message loop. Create browser using `cef.PostTask(cef.TID_UI, cef.CreateBrowserSync, ...)`. Note that when using multi-threaded message loop, CEF's UI thread @@ -242,7 +242,7 @@ Here are some settings worth noting: for localized resources To enable debugging set these settings: -``` +```python settings = { "debug": True, "log_severity": cef.LOGSEVERITY_WARNING, @@ -270,7 +270,7 @@ Examples switches: Example code: -``` +```python switches = { "enable-media-stream": "", "proxy-server": "socks5://127.0.0.1:8888", @@ -310,9 +310,10 @@ underscore in its name. The [tutorial.py](../examples/tutorial.py) example shows how to implement client handlers like [DisplayHandler](../api/DisplayHandler.md) and [LoadHandler](../api/LoadHandler.md). It also shows how to -implement a global client callback LifespanHandler.[_OnAfterCreated](../api/LifespanHandler.md#_onaftercreated). Here is some source code: +implement a global client callback LifespanHandler.[_OnAfterCreated](../api/LifespanHandler.md#_onaftercreated). Here is part of its +source code: -``` +```python set_client_handlers(browser) ... def set_client_handlers(browser): @@ -350,7 +351,7 @@ Python and Javascript can communicate using inter-process messaging: - Use the [JavascriptBindings](../api/JavascriptBindings.md) class methods to to expose Python functions, objects and properties - to Javascript: [SetFunction](../api/JavascriptBindings.md#setfunctions), + to Javascript: [SetFunction](../api/JavascriptBindings.md#setfunction), [SetObject](../api/JavascriptBindings.md#setobject) and [SetProperty](../api/JavascriptBindings.md#setproperty) - To initiate communication from the Python side call @@ -381,9 +382,9 @@ messaging: In [tutorial.py](../examples/tutorial.py) example you will find example usage of javascript bindings, javascript callbacks -and python callbacks. Here is some source code: +and python callbacks. Here is part of its source code: -``` +```python set_javascript_bindings(browser) ... def set_javascript_bindings(browser): diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 3b93066cb..2fae3e14a 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -25,6 +25,7 @@ maintained: - [hello_world.py](hello_world.py): basic example, doesn't require any third party GUI framework to run +- [tutorial.py](tutorial.py): example from [Tutorial](../docs/Tutorial.md) - [gtk2.py](gtk2.py): example for [PyGTK](http://www.pygtk.org/) library (GTK 2) - [gtk3.py](gtk3.py): example for [PyGObject/PyGI](https://wiki.gnome.org/Projects/PyGObject) diff --git a/examples/tutorial.py b/examples/tutorial.py index 88304abf3..28d69ddb7 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -1,4 +1,4 @@ -# Tutorial example. +# Tutorial example. Doesn't depend on any third party GUI framework. # Tested with CEF Python v56.1+ from cefpython3 import cefpython as cef From 73c84b20106699db2a15bcc2b46e4b7b51d9131c Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 11 Apr 2017 20:35:40 +0200 Subject: [PATCH 043/398] Update docs --- README.md | 2 +- api/Browser.md | 22 +++++++++++----------- api/JavascriptCallback.md | 6 +++--- docs/Build-instructions.md | 3 +-- docs/Tutorial.md | 6 ++---- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index bcf0ec45d..626e25b1b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ There are many use cases for CEF. You can embed a web browser control based on Chromium with great HTML 5 support. You can use it to create a HTML 5 based GUI in an application, this can act as a replacement for standard GUI toolkits such as wxWidgets, Qt or GTK. In such case to -communicate between Python<>Javascript use [javascript bindings](api/JavascriptBindings.md) +communicate between Python<>Javascript use [javascript bindings](docs/Tutorial.md#javascript-integration) or embed an internal web server and talk using http requests. You can render web content off-screen in applications that use custom drawing frameworks. You can use it for automated testing of existing diff --git a/api/Browser.md b/api/Browser.md index 58e566980..623e5c53b 100644 --- a/api/Browser.md +++ b/api/Browser.md @@ -3,20 +3,14 @@ # Browser (object) -Remember to free all browser references for the browser to shut down cleanly. +Remember to free all browser references when closing app +for the browser to shut down cleanly. Otherwise data such as cookies or other storage might not be flushed to disk -when closing app, and other issues might occur as well. If you store -a reference to Frame somewhere in your code then to free it just assign -a None value to the variable. +when closing app, and other issues might occur as well. To free a reference +just assign a None value to a browser variable. To compare browser objects always use [GetIdentifier()](#getidentifier) -method. Do not compare two Browser objects variables directly. There -are some edge cases when after the OnBeforeClose event browser objects -are no more globally referenced thus a new instance is created that -wraps upstream CefBrowser object. Browser objects that were globally -unreferenced do not have properties of the original Browser object, -for example they do not have client callbacks, javascript bindings -or user data set. +method. Do not compare two Browser objects variables directly. Table of contents: @@ -107,6 +101,12 @@ Methods available in upstream CEF which were not yet exposed in CEF Python * ImeFinishComposingText * ImeCancelComposition +There are some edge cases when after the OnBeforeClose event browser objects +are no more globally referenced thus a new instance is created that +wraps upstream CefBrowser object. Browser objects that were globally +unreferenced do not have properties of the original Browser object, +for example they do not have client callbacks, javascript bindings +or user data set. ## Methods diff --git a/api/JavascriptCallback.md b/api/JavascriptCallback.md index 97b3bc063..9f98f2726 100644 --- a/api/JavascriptCallback.md +++ b/api/JavascriptCallback.md @@ -27,10 +27,10 @@ Table of contents: | Parameter | Type | | --- | --- | -| [params..] | mixed | -| __Return__ | mixed | +| [params..] (optional) | mixed | +| __Return__ | void | -Call the javascript callback function. +Call the javascript callback function. Pass arguments optionally. For a list of allowed types for `mixed` see JavascriptBindings.[IsValueAllowed()](JavascriptBindings.md#isvalueallowed). diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index c35ac91ab..1b1811f21 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -22,8 +22,7 @@ Table of contents: ## Preface These instructions are for the new releases of CEF Python v50+. -For the old v31 release that is supported on all platforms, see -the build instructions on the wiki pages. +For the old v31 release see the build instructions on Wiki pages. If you would like to quickly build cefpython then see the [Quick build instructions for Windows](#quick-build-instructions-for-windows) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 9abd7c13a..f78757826 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -268,7 +268,8 @@ Examples switches: - "proxy-server" - to set proxy server - "disable-gpu" - use only CPU software rendering -Example code: +Note that when setting switch that doesn't accept value then +must pass an empty string as value. Example code: ```python switches = { @@ -279,9 +280,6 @@ switches = { cef.Initialize(switches=switches) ``` -Note that when setting switch that doesn't accept value then -must pass an empty string as value. - **Chromium preferences** From 8afe4c2ecb8cd3c0d1ae5ad09d61ba503d943ece Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 14 Apr 2017 08:37:29 +0200 Subject: [PATCH 044/398] Update docs --- README.md | 18 ++++++++++-------- api/API-index.md | 4 +++- api/Browser.md | 8 ++++---- api/cefpython.md | 14 ++++++-------- docs/Knowledge-Base.md | 6 ++++-- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 626e25b1b..be05b9e2f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ See the [Examples-README.md](examples/Examples-README.md) file. - API reference is in the [api/](api) directory: - [API categories](api/API-categories.md#api-categories) - [API index](api/API-index.md#api-index) -- Additional documentation is in issues labelled [Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) +- Additional documentation is available in [Issues labelled Knowledge Base](../../issues?q=is%3Aissue+is%3Aopen+label%3A%22Knowledge+Base%22) - To search documentation use GitHub "This repository" search at the top. To narrow results to documentation only select "Markdown" in the right pane. @@ -155,8 +155,8 @@ directly. - [Tutorial](docs/Tutorial.md) -### API categories - +### API categories + #### Modules * [cefpython](api/cefpython.md#cefpython) module @@ -190,7 +190,7 @@ directly. * [WindowUtils](api/WindowUtils.md#windowutils-class) class -#### Handlers (interfaces) +#### Client handlers (interfaces) * [DisplayHandler](api/DisplayHandler.md#displayhandler-interface) * [DownloadHandler](api/DownloadHandler.md#downloadhandler) @@ -211,9 +211,9 @@ directly. * [StringVisitor](api/StringVisitor.md#stringvisitor-interface) interface * [WebRequestClient](api/WebRequestClient.md#webrequestclient-interface) interface - -### API index - + +### API index + * [Application settings](api/ApplicationSettings.md#application-settings) * [accept_language_list](api/ApplicationSettings.md#accept_language_list) * [auto_zooming](api/ApplicationSettings.md#auto_zooming) @@ -475,7 +475,9 @@ directly. * [SetProperty](api/JavascriptBindings.md#setproperty) * [JavascriptCallback (object)](api/JavascriptCallback.md#javascriptcallback-object) * [Call](api/JavascriptCallback.md#call) - * [GetName](api/JavascriptCallback.md#getname) + * [GetFrame](api/JavascriptCallback.md#getframe) + * [GetId](api/JavascriptCallback.md#getid) + * [GetFunctionName](api/JavascriptCallback.md#getfunctionname) * [JavascriptDialogHandler (interface)](api/JavascriptDialogHandler.md#javascriptdialoghandler-interface) * [Continue](api/JavascriptDialogHandler.md#continue) * [OnJavascriptDialog](api/JavascriptDialogHandler.md#onjavascriptdialog) diff --git a/api/API-index.md b/api/API-index.md index df2dc4bd2..a1ab99c2c 100644 --- a/api/API-index.md +++ b/api/API-index.md @@ -263,7 +263,9 @@ * [SetProperty](JavascriptBindings.md#setproperty) * [JavascriptCallback (object)](JavascriptCallback.md#javascriptcallback-object) * [Call](JavascriptCallback.md#call) - * [GetName](JavascriptCallback.md#getname) + * [GetFrame](JavascriptCallback.md#getframe) + * [GetId](JavascriptCallback.md#getid) + * [GetFunctionName](JavascriptCallback.md#getfunctionname) * [JavascriptDialogHandler (interface)](JavascriptDialogHandler.md#javascriptdialoghandler-interface) * [Continue](JavascriptDialogHandler.md#continue) * [OnJavascriptDialog](JavascriptDialogHandler.md#onjavascriptdialog) diff --git a/api/Browser.md b/api/Browser.md index 623e5c53b..807b0cd1f 100644 --- a/api/Browser.md +++ b/api/Browser.md @@ -169,7 +169,7 @@ Explicitly close the associated DevTools browser, if any. ### DragTargetDragEnter -| | | +| Parameter | Type | | --- | --- | | drag_data | [DragData](DragData.md) | | x | int | @@ -189,7 +189,7 @@ Description from upstream CEF: ### DragTargetDragOver -| | | +| Parameter | Type | | --- | --- | | x | int | | y | int | @@ -217,7 +217,7 @@ Description from upstream CEF: ### DragTargetDrop -| | | +| Parameter | Type | | --- | --- | | x | int | | y | int | @@ -233,7 +233,7 @@ Description from upstream CEF: ### DragSourceEndedAt -| | | +| Parameter | Type | | --- | --- | | x | int | | y | int | diff --git a/api/cefpython.md b/api/cefpython.md index 3409d8b48..d93a16e38 100644 --- a/api/cefpython.md +++ b/api/cefpython.md @@ -34,11 +34,10 @@ Table of contents: ### CreateBrowser -Create browser asynchronously (does not return Browser object). -See `CreateBrowserSync()` for params list. - -NOTE: currently this is just an alias and actually creates browser -synchronously. The async call to CefCreateBrowser is yet TODO. +Not yet implemented - currently this method just calls [CreateBrowserSync](#createbrowsersync). +In upstream CEF this method creates browser asynchronously. Currently +CEF Python depends on browser being created synchronously in a few parts +of code. ### CreateBrowserSync @@ -82,8 +81,7 @@ This hook does the following: in case of exception write it to the "error.log" file, display it to the console, shutdown CEF and exit application immediately by ignoring "finally" (_exit()). -If you would like to implement a custom hook take a look at the -source code of ExceptHook in the cefpython/src/helpers.pyx file. +See also Tutorial: [Handling Python exceptions](../docs/Tutorial.md#handling-python-exceptions). ### GetAppSetting @@ -233,7 +231,7 @@ Description from upstream CEF: | --- | --- | | threadId | int | | func | object | -| ... | *args | +| [args..] | mixed | | __Return__ | void | Post a task for execution on the thread associated with this task runner. Execution will occur asynchronously. Only Browser process threads are allowed. diff --git a/docs/Knowledge-Base.md b/docs/Knowledge-Base.md index fb9ea8658..83d4b6798 100644 --- a/docs/Knowledge-Base.md +++ b/docs/Knowledge-Base.md @@ -1,7 +1,7 @@ # Knowledge Base Table of contents: -* [Notifications on new releases](#notifications-on-new-releases) +* [Notifications about new releases / commits](#notifications-about-new-releases-commits) * [Changes in API after CEF updates](#changes-in-api-after-cef-updates) * [Differences between Python 2 and Python 3](#differences-between-python-2-and-python-3) * [Location of CEF framework in Mac apps](#location-of-cef-framework-in-mac-apps) @@ -17,7 +17,7 @@ Table of contents: * [Security](#security) -## Notifications on new releases +## Notifications about new releases / commits To be notified of new releases subscribe to this [RSS/Atom feed](../../../releases.atom). @@ -25,6 +25,8 @@ Announcements are also made on the [Forum](https://groups.google.com/d/forum/cef To be notified of these via email set your Membership and Email settings and change delivery preference to Daily summaries. +To be notified on new commits subscribe to this [RSS/Atom feed](../../commits/master.atom). + ## Changes in API after CEF updates From 7a418c4b3f1f2560c87bac1b97ec1a4b12941bf9 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 14 Apr 2017 08:41:34 +0200 Subject: [PATCH 045/398] Update toc.py --- docs/Knowledge-Base.md | 2 +- tools/toc.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Knowledge-Base.md b/docs/Knowledge-Base.md index 83d4b6798..93733cd3f 100644 --- a/docs/Knowledge-Base.md +++ b/docs/Knowledge-Base.md @@ -1,7 +1,7 @@ # Knowledge Base Table of contents: -* [Notifications about new releases / commits](#notifications-about-new-releases-commits) +* [Notifications about new releases / commits](#notifications-about-new-releases--commits) * [Changes in API after CEF updates](#changes-in-api-after-cef-updates) * [Differences between Python 2 and Python 3](#differences-between-python-2-and-python-3) * [Location of CEF framework in Mac apps](#location-of-cef-framework-in-mac-apps) diff --git a/tools/toc.py b/tools/toc.py index a9749d35a..e00754c1b 100644 --- a/tools/toc.py +++ b/tools/toc.py @@ -168,11 +168,13 @@ def headinghash(title): """Get a link hash for a heading H1,H2,H3.""" hash_ = title.lower() hash_ = hash_.replace(" - ", "specialcase1") + hash_ = hash_.replace(" / ", "specialcase2") hash_ = re.sub(r"[^a-z0-9_\- ]+", r"", hash_) hash_ = hash_.replace(" ", "-") hash_ = re.sub(r"[-]+", r"-", hash_) hash_ = re.sub(r"-$", r"", hash_) hash_ = hash_.replace("specialcase1", "---") + hash_ = hash_.replace("specialcase2", "--") return hash_ From e96ae89e1091f5326a121d7bc2c1825686ca67eb Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 14 Apr 2017 08:42:47 +0200 Subject: [PATCH 046/398] Fix link --- docs/Knowledge-Base.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Knowledge-Base.md b/docs/Knowledge-Base.md index 93733cd3f..5361122dc 100644 --- a/docs/Knowledge-Base.md +++ b/docs/Knowledge-Base.md @@ -25,7 +25,7 @@ Announcements are also made on the [Forum](https://groups.google.com/d/forum/cef To be notified of these via email set your Membership and Email settings and change delivery preference to Daily summaries. -To be notified on new commits subscribe to this [RSS/Atom feed](../../commits/master.atom). +To be notified on new commits subscribe to this [RSS/Atom feed](../../../commits/master.atom). ## Changes in API after CEF updates From a529208618eb2e6eaae2bb39ec4f383712192c83 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 14 Apr 2017 11:14:06 +0100 Subject: [PATCH 047/398] Update comment regarding msvc9_query_vcvarsall (pypa/setuptools/issues/992) --- tools/automate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/automate.py b/tools/automate.py index cb97d8d2f..741242b8a 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -492,8 +492,9 @@ def build_wrapper_library_windows(runtime_library, msvs, vcvars): # When Using WinSDK 7.1 vcvarsall.bat doesn't work. Use # setuptools.msvc.msvc9_query_vcvarsall to query env vars. env.update(msvc9_query_vcvarsall(10.0, arch=VS_PLATFORM_ARG)) - # On Python 2.7 64-bit env values returned by setuptools - # are unicode. On 32-bit they are strings. + # On Python 2.7 env values returned by both distutils + # and setuptools are unicode, but Python expects env + # dict values as strings. for env_key in env: env_value = env[env_key] if type(env_value) != str: From f7398c2c51d74c211a174db07dce174846e5812a Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 14 Apr 2017 11:26:41 +0100 Subject: [PATCH 048/398] Fix error WindowInfo.windowName == NoneType when running unit tests on Windows --- src/window_info.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window_info.pyx b/src/window_info.pyx index 2ac24df76..a2e0f251b 100644 --- a/src/window_info.pyx +++ b/src/window_info.pyx @@ -78,6 +78,7 @@ cdef class WindowInfo: def __init__(self, title=""): self.transparentPainting = False + self.windowName = "" if title: self.windowName = title From 5caa06123764d3f18ee85c3443430c961de4d3f2 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 14 Apr 2017 13:14:30 +0100 Subject: [PATCH 049/398] Use VS2008 to build subprocess.exe to avoid false positives by AVs (#342) --- README.md | 12 +++---- src/compile_time_constants.pxi | 4 +-- tools/build.py | 7 +++- tools/build_distrib.py | 62 ++++++++++++++++++++++++++++++---- tools/run_examples.py | 1 + 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index be05b9e2f..114d2d358 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ See the [Examples-README.md](examples/Examples-README.md) file. - Supported examples are listed in the [Examples-README.md](examples/Examples-README.md) file - Documentation is in the [docs/](docs) directory: - [Build instructions](docs/Build-instructions.md) - - [Knowledge base](docs/Knowledge-Base.md) + - [Knowledge Base](docs/Knowledge-Base.md) - [Migration guide](docs/Migration-guide.md) - [Tutorial](docs/Tutorial.md) - API reference is in the [api/](api) directory: @@ -155,8 +155,8 @@ directly. - [Tutorial](docs/Tutorial.md) -### API categories - +### API categories + #### Modules * [cefpython](api/cefpython.md#cefpython) module @@ -211,9 +211,9 @@ directly. * [StringVisitor](api/StringVisitor.md#stringvisitor-interface) interface * [WebRequestClient](api/WebRequestClient.md#webrequestclient-interface) interface - -### API index - + +### API index + * [Application settings](api/ApplicationSettings.md#application-settings) * [accept_language_list](api/ApplicationSettings.md#accept_language_list) * [auto_zooming](api/ApplicationSettings.md#auto_zooming) diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..632aab59c 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 2 +DEF UNAME_SYSNAME = "Windows" +DEF PY_MAJOR_VERSION = 3 diff --git a/tools/build.py b/tools/build.py index 1d32dcf1d..3e4f0f8ba 100644 --- a/tools/build.py +++ b/tools/build.py @@ -879,7 +879,12 @@ def delete_directory_reliably(adir): print("[build.py] Delete directory: {dir}" .format(dir=adir.replace(ROOT_DIR, ""))) if WINDOWS: - shutil.rmtree(adir) + # rmtree is vulnerable to race conditions. Sometimes + # deleting directory fails with error: + # >> OSError: [WinError 145] The directory is not empty: + # >> 'C:\\github\\cefpython\\build\\cefpython3_56.2_win64\\build\\ + # >> lib\\cefpython3' + shutil.rmtree(adir, ignore_errors=True) else: # On Linux sudo might be required to delete directory, as this # might be a setup installer directory with package installed diff --git a/tools/build_distrib.py b/tools/build_distrib.py index 7466caf89..f2635df2b 100644 --- a/tools/build_distrib.py +++ b/tools/build_distrib.py @@ -44,7 +44,8 @@ 7. Reduce packages size (Issue #321). After packing prebuilt binaries, reduce its size so that packages will use the reduced prebuilt binaries. 8. Build cefpython modules for all supported Python versions on both - 32-bit and 64-bit + 32-bit and 64-bit. Backup and restore subprocess executable on Windows + built with Python 2.7 (Issue #342). 9. Make setup installers and pack them to zip (Win/Mac) or .tar.gz (Linux) 10. Make wheel packages 11. Move setup and wheel packages to the build/distrib/ directory @@ -129,7 +130,8 @@ def main(): reduce_package_size_issue_262("64bit") remove_unnecessary_package_files("64bit") if not NO_REBUILD: - build_cefpython_modules(pythons_32bit + pythons_64bit) + build_cefpython_modules(pythons_32bit, "32bit") + build_cefpython_modules(pythons_64bit, "64bit") if pythons_32bit: make_packages(pythons_32bit[0], "32bit") if pythons_64bit: @@ -439,7 +441,7 @@ def remove_unnecessary_package_files(arch): delete_cef_sample_apps(caller_script=__file__, bin_dir=bin_dir) -def build_cefpython_modules(pythons): +def build_cefpython_modules(pythons, arch): for python in pythons: print("[build_distrib.py] Build cefpython module for {python_name}" .format(python_name=python["name"])) @@ -461,14 +463,62 @@ def build_cefpython_modules(pythons): sys.exit(1) print("[build_distrib.py] Built successfully cefpython module for" " {python_name}".format(python_name=python["name"])) - print("[build_distrib.py] Successfully built cefpython modules for" - " all Python versions") + # Issue #342 + backup_subprocess_executable_issue342(python) + + # Issue #342 + restore_subprocess_executable_issue342(arch) + + print("[build_distrib.py] Successfully built cefpython modules for {arch}" + .format(arch=arch)) + + +def backup_subprocess_executable_issue342(python): + """Use subprocess executable build by Python 2.7 to avoid + false-positives by AVs when building subprocess with Python 3. + Windows-only issue.""" + if not WINDOWS: + return + if python["version2"] == (2, 7): + print("[build_distrib.py] Backup subprocess executable built" + " with Python 2.7 (Issue #342)") + cefpython_binary_basename = get_cefpython_binary_basename( + get_os_postfix2_for_arch(python["arch"])) + cefpython_binary = os.path.join(BUILD_DIR, cefpython_binary_basename) + assert os.path.isdir(cefpython_binary) + src = os.path.join(cefpython_binary, "subprocess.exe") + dst = os.path.join(BUILD_CEFPYTHON, + "subprocess_py27_{arch}_issue342.exe" + .format(arch=python["arch"])) + shutil.copy(src, dst) + + +def restore_subprocess_executable_issue342(arch): + """Use subprocess executable build by Python 2.7 to avoid + false-positives by AVs when building subprocess with Python 3. + Windows-only issue.""" + if not WINDOWS: + return + print("[build_distrib.py] Restore subprocess executable built" + " with Python 2.7 (Issue #342)") + cefpython_binary_basename = get_cefpython_binary_basename( + get_os_postfix2_for_arch(arch)) + cefpython_binary = os.path.join(BUILD_DIR, cefpython_binary_basename) + assert os.path.isdir(cefpython_binary) + src = os.path.join(BUILD_CEFPYTHON, + "subprocess_py27_{arch}_issue342.exe" + .format(arch=arch)) + assert os.path.isfile(src) + dst = os.path.join(cefpython_binary, "subprocess.exe") + shutil.copy(src, dst) def make_packages(python, arch): - # Make setup package + """Make setup and wheel packages.""" print("[build_distrib.py] Make setup package for {arch}..." .format(arch=arch)) + + # Call make_installer.py make_installer_py = os.path.join(TOOLS_DIR, "make_installer.py") installer_command = ("\"{python}\" {make_installer_py} {version}" .format(python=python["executable"], diff --git a/tools/run_examples.py b/tools/run_examples.py index a4a1f0bbb..241a94173 100644 --- a/tools/run_examples.py +++ b/tools/run_examples.py @@ -36,6 +36,7 @@ def main(): packages = check_installed_packages() examples = list() examples.append("hello_world.py") + examples.append("tutorial.py") succeeded = list() failed = list() passed = list() From 15231195385407305d9517563b21809fd5912fc5 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 18 Apr 2017 07:41:19 +0200 Subject: [PATCH 050/398] Update wxpython.py example for wxPython 4.0 (#348). Thanks to Andrew Leech for the patch. --- docs/Tutorial.md | 7 +++---- examples/wxpython.py | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index f78757826..bbd3054b3 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -7,8 +7,8 @@ a replacement for standard GUI toolkits such as wxWidgets, Qt or GTK. With this tutorial you will learn CEF Python basics. This tutorial will discuss the two basic examples: [hello_world.py](../examples/hello_world.py) -and [tutorial.py](../examples/tutorial.py). There are more -many more examples that you can find in the [Examples-README.md](../examples/Examples-README.md) +and [tutorial.py](../examples/tutorial.py). There are many +more examples that you can find in the [Examples-README.md](../examples/Examples-README.md) file, but these examples are out of scope for this tutorial. @@ -33,8 +33,7 @@ You can install with pip. On Linux pip 8.1+ is required. Alternatively you can download packages for offline installation from [GitHub Releases](../../../releases). Run the commands below to install the cefpython3 package, clone -the github repository, enter the examples/ directory and run the -Hello World example: +the repository and run the Hello World example: ```commandline pip install cefpython3==56.1 diff --git a/examples/wxpython.py b/examples/wxpython.py index bc5ccf495..4dc733b43 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -109,7 +109,7 @@ def create_menu(self): def embed_browser(self): window_info = cef.WindowInfo() - (width, height) = self.browser_panel.GetClientSizeTuple() + (width, height) = self.browser_panel.GetClientSize().Get() window_info.SetAsChild(self.browser_panel.GetHandle(), [0, 0, width, height]) self.browser = cef.CreateBrowserSync(window_info, @@ -197,14 +197,15 @@ def create_timer(self): # http://wiki.wxwidgets.org/Making_a_render_loop # Another way would be to use EVT_IDLE in MainFrame. self.timer = wx.Timer(self, self.timer_id) + self.Bind(wx.EVT_TIMER, self.on_timer, self.timer) self.timer.Start(10) # 10ms timer - wx.EVT_TIMER(self, self.timer_id, self.on_timer) def on_timer(self, _): cef.MessageLoopWork() def OnExit(self): self.timer.Stop() + return 0 if __name__ == '__main__': From e74777a6b2b6c9f98dc70a7d1a58dc5027129c32 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 18 Apr 2017 08:14:48 +0200 Subject: [PATCH 051/398] On Linux it is required to show window before embedding browser (#347) --- examples/gtk2.py | 6 ++++-- examples/wxpython.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/gtk2.py b/examples/gtk2.py index 99d7cc140..981c422ea 100644 --- a/examples/gtk2.py +++ b/examples/gtk2.py @@ -96,10 +96,12 @@ def __init__(self): self.vbox.pack_start(self.menubar, False, False, 0) self.main_window.add(self.vbox) - self.embed_browser() - + # On Linux must show window first before embedding browser + # (Issue #347). self.vbox.show() self.main_window.show() + self.embed_browser() + self.vbox.get_window().focus() self.main_window.get_window().focus() if g_message_loop == MESSAGE_LOOP_TIMER: diff --git a/examples/wxpython.py b/examples/wxpython.py index 4dc733b43..069a312b1 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -84,7 +84,7 @@ def __init__(self): self.browser_panel.Bind(wx.EVT_SIZE, self.OnSize) # On Linux must show before embedding browser, so that handle - # is available. + # is available (Issue #347). if LINUX: self.Show() self.embed_browser() From b3591290715dd87606c1dfc12400bc2d492de91d Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 18 Apr 2017 09:00:20 +0200 Subject: [PATCH 052/398] Fix wxpython.py example on Linux for wxPython 3.0/4.0 (#349) wx.IconFromBitmap is not available on Linux. --- examples/wxpython.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/wxpython.py b/examples/wxpython.py index 069a312b1..416805cea 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -87,7 +87,14 @@ def __init__(self): # is available (Issue #347). if LINUX: self.Show() - self.embed_browser() + # In wxPython 3.0 and wxPython 4.0 handle is still + # not yet available, must delay embedding browser + # (Issue #349). + if wx.version().startswith("3.") or wx.version().startswith("4."): + wx.CallLater(20, self.embed_browser) + else: + # This works fine in wxPython 2.8 + self.embed_browser() else: self.embed_browser() self.Show() @@ -95,7 +102,8 @@ def __init__(self): def setup_icon(self): icon_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), "resources", "wxpython.png") - if os.path.exists(icon_file): + # wx.IconFromBitmap is not available on Linux in wxPython 3.0/4.0 + if os.path.exists(icon_file) and hasattr(wx, "IconFromBitmap"): icon = wx.IconFromBitmap(wx.Bitmap(icon_file, wx.BITMAP_TYPE_PNG)) self.SetIcon(icon) @@ -132,7 +140,7 @@ def OnSize(self, _): 0, 0, 0) elif LINUX: (x, y) = (0, 0) - (width, height) = self.browser_panel.GetSizeTuple() + (width, height) = self.browser_panel.GetSize().Get() self.browser.SetBounds(x, y, width, height) self.browser.NotifyMoveOrResizeStarted() From 7c3ffb5dbf59c3c3b9e92cbcc31759075d19b2dd Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 18 Apr 2017 15:11:11 +0200 Subject: [PATCH 053/398] Fix wxpython.py example running wxPython 4.0 on Mac (#350). Fix searching for Python.h include file when building with system Python 3 on Mac. --- examples/wxpython.py | 19 ++++++++++++++++++- src/compile_time_constants.pxi | 2 +- tools/common.py | 2 ++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/wxpython.py b/examples/wxpython.py index 416805cea..2a5fbafe9 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -191,14 +191,31 @@ class CefApp(wx.App): def __init__(self, redirect): self.timer = None self.timer_id = 1 + self.is_initialized = False super(CefApp, self).__init__(redirect=redirect) + def OnPreInit(self): + super(CefApp, self).OnPreInit() + # On Mac with wxPython 4.0 the OnInit() event never gets + # called. Doing wx window creation in OnPreInit() seems to + # resolve the problem (Issue #350). + if MAC and wx.version().startswith("4."): + print("[wxpython.py] OnPreInit: initialize here" + " (wxPython 4.0 fix)") + self.initialize() + def OnInit(self): + self.initialize() + return True + + def initialize(self): + if self.is_initialized: + return self.create_timer() frame = MainFrame() self.SetTopWindow(frame) frame.Show() - return True + self.initialized = True def create_timer(self): # See also "Making a render loop": diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 632aab59c..457e4a580 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Windows" +DEF UNAME_SYSNAME = "Darwin" DEF PY_MAJOR_VERSION = 3 diff --git a/tools/common.py b/tools/common.py index c6b886480..386979c98 100644 --- a/tools/common.py +++ b/tools/common.py @@ -242,6 +242,8 @@ def get_python_include_path(): try_dirs = ["{base_dir}/include", "{base_dir}/../include/python{ver}", "{base_dir}/../include/python{ver}*", + ("{base_dir}/../Frameworks/Python.framework/Versions/{ver}" + "/include/python{ver}*"), "/usr/include/python{ver}"] ver_tuple = sys.version_info[:2] ver = "{major}.{minor}".format(major=ver_tuple[0], minor=ver_tuple[1]) From b438b3edeb6a069e022bcc217cefdb6e0c9b85b8 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 18 Apr 2017 15:14:03 +0200 Subject: [PATCH 054/398] Add comment on new tested configurations in wxpython.py example --- examples/wxpython.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/wxpython.py b/examples/wxpython.py index 2a5fbafe9..01536a87d 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -4,6 +4,7 @@ # To install wxPython on Linux type "sudo apt-get install python-wxtools". # Tested configurations: +# - wxPython 4.0 on Windows/Mac/Linux # - wxPython 3.0 on Windows/Mac # - wxPython 2.8 on Linux # - CEF Python v55.4+ From 8c5904a1c7ee0b78e074572c2cf0fff2b43a766c Mon Sep 17 00:00:00 2001 From: cztomczak Date: Tue, 18 Apr 2017 15:26:30 +0200 Subject: [PATCH 055/398] Fix typo in wxpython.py example --- examples/wxpython.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wxpython.py b/examples/wxpython.py index 01536a87d..f03da5e09 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -212,11 +212,11 @@ def OnInit(self): def initialize(self): if self.is_initialized: return + self.is_initialized = True self.create_timer() frame = MainFrame() self.SetTopWindow(frame) frame.Show() - self.initialized = True def create_timer(self): # See also "Making a render loop": From 896b0dfdf2e99dde094221828976d1f8d43325f8 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Wed, 19 Apr 2017 15:20:10 +0200 Subject: [PATCH 056/398] Use CEF logging functions (#352) and others (#351, #277)... Fix logging command line string for sub-processes (#351). Remove CefExecuteProcess call in cef.Initialize. This call should happen only in subprocess main.cpp. --- api/ApplicationSettings.md | 4 +- docs/Knowledge-Base.md | 8 +- docs/Tutorial.md | 2 +- examples/wxpython.py | 6 +- src/cefpython.pyx | 30 +-- src/client_handler/Makefile | 1 + src/client_handler/cef_log.cpp | 17 ++ src/client_handler/cef_log.h | 9 + src/client_handler/client_handler.cpp | 29 +-- src/client_handler/context_menu_handler.cpp | 9 +- ...eprecated_client_handler_py27_64bit.vcproj | 0 ...precated_client_handler_py27_win32.vcproj} | 0 ...eprecated_client_handler_py34_32bit.vcproj | 0 src/client_handler/dialog_handler_gtk.cpp | 4 +- src/client_handler/download_handler.cpp | 15 +- src/client_handler/dpi_aware.cpp | 49 ++-- src/client_handler/lifespan_handler.cpp | 6 +- .../request_context_handler.cpp | 1 - src/client_handler/request_handler.cpp | 4 +- src/client_handler/x11.cpp | 4 +- src/common/DebugLog.h | 24 -- src/common/LOG_DEBUG.h | 211 ------------------ src/compile_time_constants.pxi | 4 +- src/extern/cef_log.pxd | 8 + src/handlers/browser_process_handler.pyx | 2 - src/linux/binaries_64bit/kivy_.py | 3 - src/subprocess/cefpython_app.cpp | 160 ++++++------- src/subprocess/cefpython_app.h | 7 +- ...precated_libcefpythonapp_py27_64bit.vcproj | 0 ...recated_libcefpythonapp_py27_win32.vcproj} | 0 ...precated_libcefpythonapp_py34_32bit.vcproj | 0 .../deprecated_subprocess_64bit.vcproj | 0 .../deprecated_subprocess_win32.vcproj} | 0 src/subprocess/javascript_callback.cpp | 22 +- src/subprocess/v8function_handler.cpp | 12 +- src/subprocess/v8utils.cpp | 85 +++---- src/utils.pyx | 28 +-- tools/cython_setup.py | 18 +- 38 files changed, 293 insertions(+), 489 deletions(-) create mode 100644 src/client_handler/cef_log.cpp create mode 100644 src/client_handler/cef_log.h rename src/client_handler/{ => deprecated}/deprecated_client_handler_py27_64bit.vcproj (100%) rename src/client_handler/{client_handler_py27_win32.vcproj => deprecated/deprecated_client_handler_py27_win32.vcproj} (100%) rename src/client_handler/{ => deprecated}/deprecated_client_handler_py34_32bit.vcproj (100%) delete mode 100644 src/common/DebugLog.h delete mode 100644 src/common/LOG_DEBUG.h create mode 100644 src/extern/cef_log.pxd rename src/subprocess/{ => deprecated}/deprecated_libcefpythonapp_py27_64bit.vcproj (100%) rename src/subprocess/{libcefpythonapp_py27_win32.vcproj => deprecated/deprecated_libcefpythonapp_py27_win32.vcproj} (100%) rename src/subprocess/{ => deprecated}/deprecated_libcefpythonapp_py34_32bit.vcproj (100%) rename src/subprocess/{ => deprecated}/deprecated_subprocess_64bit.vcproj (100%) rename src/subprocess/{subprocess_win32.vcproj => deprecated/deprecated_subprocess_win32.vcproj} (100%) diff --git a/api/ApplicationSettings.md b/api/ApplicationSettings.md index 390b3905a..4dc0ff113 100644 --- a/api/ApplicationSettings.md +++ b/api/ApplicationSettings.md @@ -287,9 +287,9 @@ a value of "verbose", "info", "warning", "error", "error-report" or Accepted values - constants available in the cefpython module: * LOGSEVERITY_VERBOSE -* LOGSEVERITY_INFO (default) +* LOGSEVERITY_INFO * LOGSEVERITY_WARNING -* LOGSEVERITY_ERROR +* LOGSEVERITY_ERROR (default) * LOGSEVERITY_ERROR_REPORT * LOGSEVERITY_DISABLE diff --git a/docs/Knowledge-Base.md b/docs/Knowledge-Base.md index 5361122dc..af38bd945 100644 --- a/docs/Knowledge-Base.md +++ b/docs/Knowledge-Base.md @@ -167,8 +167,12 @@ You can pass "--debug" command line flag to any of CEF Python examples and unit tests. It will also work with your app, as this feature is enabled in CEF Python's core. When this flag is passed the following settings will be set: -``` -settings = {"debug": True, "log_severity": cef.LOGSEVERITY_WARNING} +```python +settings = { + "debug": True, + "log_severity": cef.LOGSEVERITY_INFO, + "log_file": "debug.log", +} cef.Initialize(settings=settings) ``` diff --git a/docs/Tutorial.md b/docs/Tutorial.md index bbd3054b3..91cc029a1 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -244,7 +244,7 @@ To enable debugging set these settings: ```python settings = { "debug": True, - "log_severity": cef.LOGSEVERITY_WARNING, + "log_severity": cef.LOGSEVERITY_INFO, "log_file": "debug.log", } cef.Initialize(settings=settings) diff --git a/examples/wxpython.py b/examples/wxpython.py index 416805cea..b3b5f0f69 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -1,9 +1,8 @@ # Example of embedding CEF Python browser using wxPython library. # This example has a top menu and a browser widget without navigation bar. -# To install wxPython on Linux type "sudo apt-get install python-wxtools". - # Tested configurations: +# - wxPython 4.0 on Windows/Linux # - wxPython 3.0 on Windows/Mac # - wxPython 2.8 on Linux # - CEF Python v55.4+ @@ -35,8 +34,7 @@ def main(): sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error settings = {} if WINDOWS: - # High DPI support - settings["auto_zooming"] = "system_dpi" + settings["auto_zooming"] = "system_dpi" # High DPI support # noinspection PyUnresolvedReferences, PyArgumentList cef.DpiAware.SetProcessDpiAware() # Alternative is to embed manifest cef.Initialize(settings=settings) diff --git a/src/cefpython.pyx b/src/cefpython.pyx index dc7a672e1..7b5755eb1 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -438,12 +438,12 @@ from cef_image cimport * from main_message_loop cimport * # noinspection PyUnresolvedReferences from cef_views cimport * +from cef_log cimport * # ----------------------------------------------------------------------------- # GLOBAL VARIABLES g_debug = False -g_debugFile = "debug.log" # When put None here and assigned a local dictionary in Initialize(), later # while running app this global variable was garbage collected, see topic: @@ -462,6 +462,7 @@ cdef scoped_ptr[MainMessageLoopExternalPump] g_external_message_pump cdef py_bool g_MessageLoop_called = False cdef py_bool g_MessageLoopWork_called = False +cdef py_bool g_cef_initialized = False cdef dict g_globalClientCallbacks = {} @@ -530,14 +531,11 @@ include "handlers/v8function_handler.pyx" # Utility functions to provide settings to the C++ browser process code. cdef public void cefpython_GetDebugOptions( - cpp_bool* debug, - cpp_string* debugFile + cpp_bool* debug ) except * with gil: # Called from subprocess/cefpython_app.cpp -> CefPythonApp constructor. - cdef cpp_string cppString = PyStringToChar(g_debugFile) try: debug[0] = bool(g_debug) - debugFile.assign(cppString) except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) @@ -618,16 +616,17 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): # Debug settings need to be set before Debug() is called # and before the CefPythonApp class is instantiated. global g_debug - global g_debugFile if "--debug" in sys.argv: application_settings["debug"] = True - application_settings["log_file"] = "debug.log" - application_settings["log_severity"] = LOGSEVERITY_WARNING + application_settings["log_file"] = os.path.join(os.getcwd(), + "debug.log") + application_settings["log_severity"] = LOGSEVERITY_INFO sys.argv.remove("--debug") if "debug" in application_settings: g_debug = bool(application_settings["debug"]) - if "log_file" in application_settings: - g_debugFile = application_settings["log_file"] + if "log_severity" in application_settings: + if application_settings["log_severity"] <= LOGSEVERITY_INFO: + g_debug = True Debug("Initialize() called") @@ -719,11 +718,9 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): # TODO: use the CefMainArgs(int argc, char** argv) constructor. cdef CefMainArgs cefMainArgs cdef int exitCode = 1 - with nogil: - exitCode = CefExecuteProcess(cefMainArgs, cefApp, NULL) - Debug("CefExecuteProcess(): exitCode = %s" % exitCode) - if exitCode >= 0: - sys.exit(exitCode) + + # NOTE: CefExecuteProcess shall not be called here. It should + # be called only in the subprocess main.cpp. # Make a copy as applicationSettings is a reference only # that might get destroyed later. @@ -755,6 +752,9 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): with nogil: ret = CefInitialize(cefMainArgs, cefApplicationSettings, cefApp, NULL) + global g_cef_initialized + g_cef_initialized = True + if not ret: Debug("CefInitialize() failed") diff --git a/src/client_handler/Makefile b/src/client_handler/Makefile index 13586c8ed..bdb2ee728 100644 --- a/src/client_handler/Makefile +++ b/src/client_handler/Makefile @@ -23,6 +23,7 @@ SRC = client_handler.cpp cookie_visitor.cpp resource_handler.cpp \ download_handler.cpp focus_handler.cpp js_dialog_handler.cpp \ keyboard_handler.cpp lifespan_handler.cpp load_handler.cpp \ render_handler.cpp request_handler.cpp dialog_handler.cpp \ + cef_log.cpp \ $(SRC_MORE) OBJ = $(filter %.o, $(SRC:.cpp=.o) $(SRC:.mm=.o)) diff --git a/src/client_handler/cef_log.cpp b/src/client_handler/cef_log.cpp new file mode 100644 index 000000000..62ef9ba81 --- /dev/null +++ b/src/client_handler/cef_log.cpp @@ -0,0 +1,17 @@ +// Copyright (c) 2017 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#include "include/base/cef_logging.h" + +void cef_log_info(char* msg) { + LOG(INFO) << msg; +} + +void cef_log_warning(char* msg) { + LOG(WARNING) << msg; +} + +void cef_log_error(char* msg) { + LOG(ERROR) << msg; +} diff --git a/src/client_handler/cef_log.h b/src/client_handler/cef_log.h new file mode 100644 index 000000000..094bc7194 --- /dev/null +++ b/src/client_handler/cef_log.h @@ -0,0 +1,9 @@ +// Copyright (c) 2017 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#pragma once + +void cef_log_info(char* msg); +void cef_log_warning(char* msg); +void cef_log_error(char* msg); diff --git a/src/client_handler/client_handler.cpp b/src/client_handler/client_handler.cpp index f14fbaf85..f55da8123 100644 --- a/src/client_handler/client_handler.cpp +++ b/src/client_handler/client_handler.cpp @@ -8,7 +8,7 @@ #include "client_handler.h" #include "common/cefpython_public_api.h" -#include "common/DebugLog.h" +#include "include/base/cef_logging.h" #if defined(OS_WIN) #include @@ -34,9 +34,9 @@ bool ClientHandler::OnProcessMessageReceived( return false; } std::string messageName = message->GetName().ToString(); - std::string logMessage = "Browser: OnProcessMessageReceived(): "; + std::string logMessage = "[Browser process] OnProcessMessageReceived(): "; logMessage.append(messageName.c_str()); - DebugLog(logMessage.c_str()); + LOG(INFO) << logMessage.c_str(); if (messageName == "OnContextCreated") { CefRefPtr arguments = message->GetArgumentList(); if (arguments->GetSize() == 1 && arguments->GetType(0) == VTYPE_INT) { @@ -45,8 +45,8 @@ bool ClientHandler::OnProcessMessageReceived( V8ContextHandler_OnContextCreated(browser, frame); return true; } else { - DebugLog("Browser: OnProcessMessageReceived(): invalid arguments" \ - ", messageName = OnContextCreated"); + LOG(ERROR) << "[Browser process] OnProcessMessageReceived():" + " invalid arguments, messageName=OnContextCreated"; return false; } } else if (messageName == "OnContextReleased") { @@ -59,8 +59,8 @@ bool ClientHandler::OnProcessMessageReceived( V8ContextHandler_OnContextReleased(browserId, frameId); return true; } else { - DebugLog("Browser: OnProcessMessageReceived(): invalid arguments" \ - ", messageName = OnContextReleased"); + LOG(ERROR) << "[Browser process] OnProcessMessageReceived():" + " invalid arguments, messageName=OnContextReleased"; return false; } } else if (messageName == "V8FunctionHandler::Execute") { @@ -80,8 +80,9 @@ bool ClientHandler::OnProcessMessageReceived( functionArguments); return true; } else { - DebugLog("Browser: OnProcessMessageReceived(): invalid arguments" \ - ", messageName = V8FunctionHandler::Execute"); + LOG(ERROR) << "[Browser process] OnProcessMessageReceived():" + " invalid arguments," + " messageName=V8FunctionHandler::Execute"; return false; } } else if (messageName == "ExecutePythonCallback") { @@ -94,8 +95,9 @@ bool ClientHandler::OnProcessMessageReceived( ExecutePythonCallback(browser, callbackId, functionArguments); return true; } else { - DebugLog("Browser: OnProcessMessageReceived(): invalid arguments" \ - ", messageName = ExecutePythonCallback"); + LOG(ERROR) << "[Browser process] OnProcessMessageReceived():" + " invalid arguments," + " messageName=ExecutePythonCallback"; return false; } } else if (messageName == "RemovePythonCallbacksForFrame") { @@ -105,8 +107,9 @@ bool ClientHandler::OnProcessMessageReceived( RemovePythonCallbacksForFrame(frameId); return true; } else { - DebugLog("Browser: OnProcessMessageReceived(): invalid arguments" \ - ", messageName = ExecutePythonCallback"); + LOG(ERROR) << "[Browser process] OnProcessMessageReceived():" + " invalid arguments," + " messageName=ExecutePythonCallback"; return false; } } diff --git a/src/client_handler/context_menu_handler.cpp b/src/client_handler/context_menu_handler.cpp index 1304e8035..e6be3986b 100644 --- a/src/client_handler/context_menu_handler.cpp +++ b/src/client_handler/context_menu_handler.cpp @@ -3,7 +3,7 @@ // Project website: https://github.com/cztomczak/cefpython #include "context_menu_handler.h" -#include "common/DebugLog.h" +#include "include/base/cef_logging.h" #define _MENU_ID_DEVTOOLS MENU_ID_USER_FIRST + 1 #define _MENU_ID_RELOAD_PAGE MENU_ID_USER_FIRST + 2 @@ -127,12 +127,13 @@ void OpenInExternalBrowser(const std::string& url) // Linux equivalent of ShellExecute if (url.empty()) { - DebugLog("Browser: OpenInExternalBrowser() FAILED: url is empty"); + LOG(ERROR) << "[Browser process] OpenInExternalBrowser():" + " url is empty"; return; } - std::string msg = "Browser: OpenInExternalBrowser(): url="; + std::string msg = "[Browser process] OpenInExternalBrowser(): url="; msg.append(url.c_str()); - DebugLog(msg.c_str()); + LOG(INFO) << msg.c_str(); // xdg-open is a desktop-independent tool for running // default applications. Installed by default on Ubuntu. diff --git a/src/client_handler/deprecated_client_handler_py27_64bit.vcproj b/src/client_handler/deprecated/deprecated_client_handler_py27_64bit.vcproj similarity index 100% rename from src/client_handler/deprecated_client_handler_py27_64bit.vcproj rename to src/client_handler/deprecated/deprecated_client_handler_py27_64bit.vcproj diff --git a/src/client_handler/client_handler_py27_win32.vcproj b/src/client_handler/deprecated/deprecated_client_handler_py27_win32.vcproj similarity index 100% rename from src/client_handler/client_handler_py27_win32.vcproj rename to src/client_handler/deprecated/deprecated_client_handler_py27_win32.vcproj diff --git a/src/client_handler/deprecated_client_handler_py34_32bit.vcproj b/src/client_handler/deprecated/deprecated_client_handler_py34_32bit.vcproj similarity index 100% rename from src/client_handler/deprecated_client_handler_py34_32bit.vcproj rename to src/client_handler/deprecated/deprecated_client_handler_py34_32bit.vcproj diff --git a/src/client_handler/dialog_handler_gtk.cpp b/src/client_handler/dialog_handler_gtk.cpp index 76bf18513..3d53e78a6 100644 --- a/src/client_handler/dialog_handler_gtk.cpp +++ b/src/client_handler/dialog_handler_gtk.cpp @@ -18,7 +18,7 @@ #include "include/cef_parser.h" #include "include/wrapper/cef_helpers.h" -#include "LOG_DEBUG.h" +#include "include/base/cef_logging.h" #include "dialog_handler_gtk.h" #include "x11.h" @@ -159,7 +159,7 @@ GtkWindow* GetWindow(CefRefPtr browser) { // internally, so GTK wasn't yet initialized and must do it // now, so that display is available. Also must install X11 // error handlers to avoid 'BadWindow' errors. - LOG_DEBUG << "Initialize GTK"; + LOG(INFO) << "[Browser process] Initialize GTK"; gtk_init(0, NULL); InstallX11ErrorHandlers(); // Now the display is available diff --git a/src/client_handler/download_handler.cpp b/src/client_handler/download_handler.cpp index fae9b3138..9e887e467 100644 --- a/src/client_handler/download_handler.cpp +++ b/src/client_handler/download_handler.cpp @@ -3,7 +3,7 @@ // Project website: https://github.com/cztomczak/cefpython #include "download_handler.h" -#include "common/DebugLog.h" +#include "include/base/cef_logging.h" void DownloadHandler::OnBeforeDownload( @@ -15,12 +15,13 @@ void DownloadHandler::OnBeforeDownload( REQUIRE_UI_THREAD(); bool downloads_enabled = ApplicationSettings_GetBool("downloads_enabled"); if (downloads_enabled) { - std::string msg = "Browser: About to download file: "; + std::string msg = "[Browser process] About to download file: "; msg.append(suggested_name.ToString().c_str()); - DebugLog(msg.c_str()); + LOG(INFO) << msg.c_str(); callback->Continue(suggested_name, true); } else { - DebugLog("Browser: Tried to download file, but downloads are disabled"); + LOG(INFO) << "[Browser process] Tried to download file," + " but downloads are disabled"; } } @@ -32,10 +33,10 @@ void DownloadHandler::OnDownloadUpdated( { REQUIRE_UI_THREAD(); if (download_item->IsComplete()) { - std::string msg = "Browser: Download completed, saved to: "; + std::string msg = "[Browser process] Download completed, saved to: "; msg.append(download_item->GetFullPath().ToString().c_str()); - DebugLog(msg.c_str()); + LOG(INFO) << msg.c_str(); } else if (download_item->IsCanceled()) { - DebugLog("Browser: Download was cancelled"); + LOG(INFO) << "[Browser process] Download was cancelled"; } } diff --git a/src/client_handler/dpi_aware.cpp b/src/client_handler/dpi_aware.cpp index 6c8a9213b..48e2a13f7 100644 --- a/src/client_handler/dpi_aware.cpp +++ b/src/client_handler/dpi_aware.cpp @@ -10,7 +10,7 @@ #include "dpi_aware.h" #include "include/wrapper/cef_closure_task.h" #include "include/base/cef_bind.h" -#include "LOG_DEBUG.h" +#include "include/base/cef_logging.h" const int DEFAULT_DPIX = 96; @@ -56,7 +56,8 @@ PROCESS_DPI_AWARENESS GetProcessDpiAwareness() { return result; } // Possible failures include E_INVALIDARG or E_ACCESSDENIED. - LOG_DEBUG << "Browser: GetProcessDpiAwareness failed with HR=" << hr; + LOG(INFO) << "[Browser process] GetProcessDpiAwareness():" + " hr=" << hr; } return PROCESS_DPI_UNAWARE; } @@ -88,23 +89,23 @@ void SetProcessDpiAware() { GetProcAddress(GetModuleHandleA("user32.dll"), "SetProcessDpiAwarenessInternal")); if (set_process_dpi_awareness_func) { - LOG_DEBUG << "Browser: SetProcessDpiAware: " - << "calling user32.dll SetProcessDpiAwareness"; + LOG(INFO) << "[Browser process] SetProcessDpiAware():" + " calling user32.dll SetProcessDpiAwareness"; HRESULT hr = set_process_dpi_awareness_func(PROCESS_SYSTEM_DPI_AWARE); if (SUCCEEDED(hr)) { - LOG_DEBUG << "Browser: SetBrowserDpiAware: " - "SetProcessDpiAwareness succeeded"; + LOG(INFO) << "[Browser process]: SetBrowserDpiAware():" + " SetProcessDpiAwareness succeeded"; return; } else if (hr == E_ACCESSDENIED) { - LOG_DEBUG << "Browser: SetBrowserDpiAware: " - "SetProcessDpiAwareness FAILED: " - "The DPI awareness is already set, either by calling " - "this API previously or through the application (.exe) " - "manifest."; + LOG(ERROR) << "[Browser process] SetBrowserDpiAware():" + " SetProcessDpiAwareness failed:" + " The DPI awareness is already set, either by calling" + " this API previously or through the application" + " (.exe) manifest."; // Do not return here, let's try to call SetProcessDPIAware. } else { - LOG_DEBUG << "Browser: SetBrowserDpiAware: " - "SetProcessDpiAwareness FAILED"; + LOG(ERROR) << "[Browser process] SetBrowserDpiAware():" + " SetProcessDpiAwareness failed"; // Do not return here, let's try to call SetProcessDPIAware. } } @@ -119,8 +120,8 @@ void SetProcessDpiAware() { // If cefpython.Initialize() wasn't yet called, then // this log message won't be written, as g_debug is // is set during CEF initialization. - LOG_DEBUG << "Browser: SetProcessDpiAware: " - << "calling user32.dll SetProcessDPIAware"; + LOG(INFO) << "[Browser process] SetProcessDpiAware():" + " calling user32.dll SetProcessDPIAware"; set_process_dpi_aware_func(); } } @@ -153,9 +154,9 @@ void GetDpiAwareWindowSize(int* width, int* height) { if (newZoomLevel > 0.0) { *width = *width + (int)ceil(newZoomLevel * 0.25 * (*width)); *height = *height + (int)ceil(newZoomLevel * 0.25 * (*height)); - LOG_DEBUG << "Browser: GetDpiAwareWindowSize: " - << "enlarged by " << ceil(newZoomLevel * 0.25 * 100) << "% " - << "new size = " << *width << "/" << *height; + LOG(INFO) << "[Browser process] GetDpiAwareWindowSize():" + " enlarged by " << ceil(newZoomLevel * 0.25 * 100) << "%" + " new size = " << *width << "/" << *height; } } @@ -191,9 +192,9 @@ void SetBrowserDpiSettings(CefRefPtr cefBrowser, cefBrowser->GetHost()->SetZoomLevel(newZoomLevel); if (cefBrowser->GetHost()->GetZoomLevel() != oldZoomLevel) { // OK succes. - LOG_DEBUG << "Browser: SetBrowserDpiSettings: " - << "DPI=" << dpix << " " - << "zoom=" << cefBrowser->GetHost()->GetZoomLevel(); + LOG(INFO) << "[Browser process] SetBrowserDpiSettings():" + " DPI=" << dpix << "" + " zoom=" << cefBrowser->GetHost()->GetZoomLevel(); } } else { // This code block running can also be a result of executing @@ -206,9 +207,9 @@ void SetBrowserDpiSettings(CefRefPtr cefBrowser, if (!already_logged) { already_logged = true; // OK success. - LOG_DEBUG << "Browser: SetBrowserDpiSettings: " - << "DPI=" << dpix << " " - << "zoom=" << cefBrowser->GetHost()->GetZoomLevel(); + LOG(INFO) << "[Browser process] SetBrowserDpiSettings():" + " DPI=" << dpix << "" + " zoom=" << cefBrowser->GetHost()->GetZoomLevel(); } } // We need to check zooming constantly, during loading of pages. diff --git a/src/client_handler/lifespan_handler.cpp b/src/client_handler/lifespan_handler.cpp index 0cfd2d1b1..d54f5f0a5 100644 --- a/src/client_handler/lifespan_handler.cpp +++ b/src/client_handler/lifespan_handler.cpp @@ -6,7 +6,7 @@ #if defined(OS_WIN) #include "dpi_aware.h" #endif -#include "LOG_DEBUG.h" +#include "include/base/cef_logging.h" bool LifespanHandler::OnBeforePopup(CefRefPtr browser, @@ -38,8 +38,8 @@ void LifespanHandler::OnAfterCreated(CefRefPtr browser) // High DPI support. CefString auto_zooming = ApplicationSettings_GetString("auto_zooming"); if (!auto_zooming.empty()) { - LOG_DEBUG << "Browser: OnAfterCreated(): auto_zooming = " - << auto_zooming.ToString(); + LOG(INFO) << "[Browser process] OnAfterCreated(): auto_zooming = " + << auto_zooming.ToString(); SetBrowserDpiSettings(browser, auto_zooming); } #endif // OS_WIN diff --git a/src/client_handler/request_context_handler.cpp b/src/client_handler/request_context_handler.cpp index ef7cc8b9b..bf816cf51 100644 --- a/src/client_handler/request_context_handler.cpp +++ b/src/client_handler/request_context_handler.cpp @@ -4,7 +4,6 @@ #include "request_context_handler.h" #include "common/cefpython_public_api.h" -#include "DebugLog.h" // -------------------------------------------------------------------------- // CefRequestContextHandler diff --git a/src/client_handler/request_handler.cpp b/src/client_handler/request_handler.cpp index 699043776..c06e098d3 100644 --- a/src/client_handler/request_handler.cpp +++ b/src/client_handler/request_handler.cpp @@ -3,7 +3,7 @@ // Project website: https://github.com/cztomczak/cefpython #include "request_handler.h" -#include "common/DebugLog.h" +#include "include/base/cef_logging.h" bool RequestHandler::OnBeforeBrowse(CefRefPtr browser, @@ -104,7 +104,7 @@ void RequestHandler::OnRenderProcessTerminated(CefRefPtr browser, cef_termination_status_t status) { REQUIRE_UI_THREAD(); - DebugLog("Browser: OnRenderProcessTerminated()"); + LOG(ERROR) << "[Browser process] OnRenderProcessTerminated()"; RequestHandler_OnRendererProcessTerminated(browser, status); } diff --git a/src/client_handler/x11.cpp b/src/client_handler/x11.cpp index 0c37bff40..18f7ce030 100644 --- a/src/client_handler/x11.cpp +++ b/src/client_handler/x11.cpp @@ -3,10 +3,10 @@ // Project website: https://github.com/cztomczak/cefpython #include "x11.h" -#include "LOG_DEBUG.h" +#include "include/base/cef_logging.h" int XErrorHandlerImpl(Display *display, XErrorEvent *event) { - LOG_DEBUG + LOG(INFO) << "[Browser process] " << "X error received: " << "type " << event->type << ", " << "serial " << event->serial << ", " diff --git a/src/common/DebugLog.h b/src/common/DebugLog.h deleted file mode 100644 index 4c5419a6b..000000000 --- a/src/common/DebugLog.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2012 CEF Python, see the Authors file. -// All rights reserved. Licensed under BSD 3-clause license. -// Project website: https://github.com/cztomczak/cefpython - -#pragma once -#include - -extern bool g_debug; -extern std::string g_logFile; - -// Defined as "inline" to get rid of the "already defined" errors -// when linking. -inline void DebugLog(const char* szString) -{ - if (!g_debug) - return; - // TODO: get the log_file option from CefSettings. - printf("[CEF Python] %s\n", szString); - if (g_logFile.length()) { - FILE* pFile = fopen(g_logFile.c_str(), "a"); - fprintf(pFile, "[CEF Python] App: %s\n", szString); - fclose(pFile); - } -} diff --git a/src/common/LOG_DEBUG.h b/src/common/LOG_DEBUG.h deleted file mode 100644 index 672ad25e1..000000000 --- a/src/common/LOG_DEBUG.h +++ /dev/null @@ -1,211 +0,0 @@ -// Taken from here: http://www.drdobbs.com/cpp/logging-in-c/201804215 -// Original author: Petru Marginean -// Modified by: Czarek Tomczak - -// Usage: -// LOG_DEBUG << "Browser: someVar = " << someVar; -// Warning: -// Log is written when ~Log() destructor is called, which -// occurs when outside of scope. This could result in log -// not being written if program crashes before code goes -// outside of current scope. You could embrace LOG_DEBUG -// statements with braces {} to guarantee the log to be -// written immediately. - -#ifndef __LOG_DEBUG_H__ -#define __LOG_DEBUG_H__ - -#include -#include -#include -#include "DebugLog.h" - -inline std::string NowTime(); - -enum TLogLevel {logERROR, logWARNING, logINFO, logDEBUG, - logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4}; - -template -class Log -{ -public: - Log(); - virtual ~Log(); - std::ostringstream& Get(TLogLevel level = logINFO); -public: - static TLogLevel& ReportingLevel(); - static std::string ToString(TLogLevel level); - static TLogLevel FromString(const std::string& level); -protected: - std::ostringstream os; -private: - Log(const Log&); - Log& operator =(const Log&); -}; - -template -Log::Log() -{ -} - -template -std::ostringstream& Log::Get(TLogLevel level) -{ - // os << "- " << NowTime(); - // os << " " << ToString(level) << ": "; - // os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t'); - return os; -} - -template -Log::~Log() -{ - os << std::endl; - /* - if (T::Stream() != stderr) { - fprintf(stderr, "%s", os.str().c_str()); - fflush(stderr); - } - T::Output(os.str()); - */ - DebugLog(os.str().c_str()); -} - -template -TLogLevel& Log::ReportingLevel() -{ - static TLogLevel reportingLevel = logDEBUG4; - return reportingLevel; -} - -template -std::string Log::ToString(TLogLevel level) -{ - static const char* const buffer[] = {"ERROR", "WARNING", "INFO", "DEBUG", - "DEBUG1", "DEBUG2", "DEBUG3", "DEBUG4"}; - return buffer[level]; -} - -template -TLogLevel Log::FromString(const std::string& level) -{ - if (level == "DEBUG4") - return logDEBUG4; - if (level == "DEBUG3") - return logDEBUG3; - if (level == "DEBUG2") - return logDEBUG2; - if (level == "DEBUG1") - return logDEBUG1; - if (level == "DEBUG") - return logDEBUG; - if (level == "INFO") - return logINFO; - if (level == "WARNING") - return logWARNING; - if (level == "ERROR") - return logERROR; - Log().Get(logWARNING) << "Unknown logging level '" << level - << "'. Using INFO level as default."; - return logINFO; -} - -class Output2FILE -{ -public: - static FILE*& Stream(); - static void Output(const std::string& msg); -}; - -inline FILE*& Output2FILE::Stream() -{ - static FILE* pStream = stderr; - return pStream; -} - -inline void Output2FILE::Output(const std::string& msg) -{ - /* - FILE* pStream = Stream(); - if (!pStream) - return; - fprintf(pStream, "%s", msg.c_str()); - fflush(pStream); - */ -} - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) -# if defined (BUILDING_FILELOG_DLL) -# define FILELOG_DECLSPEC __declspec (dllexport) -# elif defined (USING_FILELOG_DLL) -# define FILELOG_DECLSPEC __declspec (dllimport) -# else -# define FILELOG_DECLSPEC -# endif // BUILDING_DBSIMPLE_DLL -#else -# define FILELOG_DECLSPEC -#endif // _WIN32 - -class FILELOG_DECLSPEC FILELog : public Log {}; -//typedef Log FILELog; - -#ifndef FILELOG_MAX_LEVEL -#define FILELOG_MAX_LEVEL logDEBUG4 -#endif - -// Name conflits with cef_logging.h, see: -// https://bitbucket.org/chromiumembedded/cef/issues/1830 -// LOG, LOG_ERROR, LOG_WARNING and LOG_INFO are already defined -// in cef_logging.h and must not be used here. So far we've only -// used LOG_DEBUG in code. - -#define CEFPYTHON_LOG(level) \ - if (level > FILELOG_MAX_LEVEL) ;\ - else if (level > FILELog::ReportingLevel() || !Output2FILE::Stream()) ; \ - else FILELog().Get(level) \ - -// #define LOG_ERROR LOG(logERROR) -// #define LOG_WARNING LOG(logWARNING) -// #define LOG_INFO LOG(logINFO) -#define LOG_DEBUG CEFPYTHON_LOG(logDEBUG) - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) - -#include - -inline std::string NowTime() -{ - const int MAX_LEN = 200; - char buffer[MAX_LEN]; - if (GetTimeFormatA(LOCALE_USER_DEFAULT, 0, 0, - "HH':'mm':'ss", buffer, MAX_LEN) == 0) - return "Error in NowTime()"; - - char result[100] = {0}; - static DWORD first = GetTickCount(); - #pragma warning(suppress: 4996) - sprintf(result, "%s.%03ld", buffer, (long)(GetTickCount() - first) % 1000); - return result; -} - -#else - -#include - -inline std::string NowTime() -{ - char buffer[11]; - time_t t; - time(&t); - tm r = {0}; - strftime(buffer, sizeof(buffer), "%X", localtime_r(&t, &r)); - struct timeval tv; - gettimeofday(&tv, 0); - char result[100] = {0}; - sprintf(result, "%s.%03ld", buffer, (long)tv.tv_usec / 1000); - return result; -} - -#endif //WIN32 - -#endif //__LOG_DEBUG_H__ diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 632aab59c..35f850027 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Windows" -DEF PY_MAJOR_VERSION = 3 +DEF UNAME_SYSNAME = "Linux" +DEF PY_MAJOR_VERSION = 2 diff --git a/src/extern/cef_log.pxd b/src/extern/cef_log.pxd new file mode 100644 index 000000000..04a32cfbd --- /dev/null +++ b/src/extern/cef_log.pxd @@ -0,0 +1,8 @@ +# Copyright (c) 2017 CEF Python, see the Authors file. +# All rights reserved. Licensed under BSD 3-clause license. +# Project website: https://github.com/cztomczak/cefpython + +cdef extern from "client_handler/cef_log.h": + void cef_log_info(char* msg) + void cef_log_warning(char* msg) + void cef_log_error(char* msg) diff --git a/src/handlers/browser_process_handler.pyx b/src/handlers/browser_process_handler.pyx index 5847d6700..09463b0ef 100644 --- a/src/handlers/browser_process_handler.pyx +++ b/src/handlers/browser_process_handler.pyx @@ -8,7 +8,6 @@ cdef public void BrowserProcessHandler_OnRenderProcessThreadCreated( CefRefPtr[CefListValue] extra_info ) except * with gil: try: - # Keys 0 and 1 are already set in C++ code - to pass debug options. pass except: (exc_type, exc_value, exc_trace) = sys.exc_info() @@ -19,7 +18,6 @@ cdef public void BrowserProcessHandler_OnBeforeChildProcessLaunch( ) except * with gil: try: AppendSwitchesToCommandLine(cefCommandLine, g_commandLineSwitches) - Debug("BrowserProcessHandler_OnBeforeChildProcessLaunch()") except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) diff --git a/src/linux/binaries_64bit/kivy_.py b/src/linux/binaries_64bit/kivy_.py index 7d7ef4cc0..c980aa9f6 100644 --- a/src/linux/binaries_64bit/kivy_.py +++ b/src/linux/binaries_64bit/kivy_.py @@ -148,9 +148,6 @@ def start_cef(self): # Configure CEF settings = { - "debug": True, # debug messages in console and in log_file - "log_severity": cef.LOGSEVERITY_INFO, - "log_file": "debug.log", # This directories must be set on Linux "locales_dir_path": cef.GetModuleDirectory()+"/locales", "resources_dir_path": cef.GetModuleDirectory(), diff --git a/src/subprocess/cefpython_app.cpp b/src/subprocess/cefpython_app.cpp index 1f51ba270..950777027 100644 --- a/src/subprocess/cefpython_app.cpp +++ b/src/subprocess/cefpython_app.cpp @@ -22,8 +22,7 @@ #include "util.h" #include "include/wrapper/cef_closure_task.h" #include "include/base/cef_bind.h" -#include "DebugLog.h" -#include "LOG_DEBUG.h" +#include "include/base/cef_logging.h" #include #include #include "v8utils.h" @@ -34,12 +33,12 @@ #include "main_message_loop/main_message_loop_external_pump.h" #endif +// GLOBALS bool g_debug = false; -std::string g_logFile = "debug.log"; CefPythonApp::CefPythonApp() { #ifdef BROWSER_PROCESS - cefpython_GetDebugOptions(&g_debug, &g_logFile); + cefpython_GetDebugOptions(&g_debug); #endif } @@ -62,21 +61,26 @@ void CefPythonApp::OnBeforeCommandLineProcessing( if (process_name.empty()) { process_name = "browser"; } - std::string logMessage = "Command line string for the "; +#ifdef BROWSER_PROCESS + std::string logMessage = "[Browser process] "; +#else + std::string logMessage = "[Non-browser process] "; +#endif + logMessage.append("Command line string for the "); logMessage.append(process_name); logMessage.append(" process: "); std::string clString = command_line->GetCommandLineString().ToString(); logMessage.append(clString.c_str()); - // OnBeforeCommandLineProcessing() is called before - // CefRenderHandler::OnRenderThreadCreated() which sets - // the debug options. Thus debugging will always be Off - // at the time this method is called. The fix for that - // is to keep the command line string somewhere and call - // DebugLog later in OnRenderThreadCreated(). + // There is a bug in upstream CEF, log settings are initialized + // after OnBeforeCommandLineProcessing. So if g_debug is not + // checked it would always log msg even though logging info is + // disabled. However this issue does not matter, because command + // line is also logged in OnBeforeChildProcessLaunch(). + // In OnBeforeCommandLineProcessing() command line for browser + // process is logged and in OnBeforeChildProcessLaunch() command + // line for other processes is logged. if (g_debug) { - DebugLog(logMessage.c_str()); - } else { - commandLineString_ = logMessage; + LOG(INFO) << logMessage.c_str(); } } @@ -116,6 +120,16 @@ void CefPythonApp::OnBeforeChildProcessLaunch( // the libcefpythonapp library. BrowserProcessHandler_OnBeforeChildProcessLaunch(command_line); #endif // BROWSER_PROCESS + +#ifdef BROWSER_PROCESS + std::string logMessage = "[Browser process] "; +#else + std::string logMessage = "[Non-browser process] "; +#endif // BROWSER_PROCESS + logMessage.append("OnBeforeChildProcessLaunch() command line: "); + std::string clString = command_line->GetCommandLineString().ToString(); + logMessage.append(clString.c_str()); + LOG(INFO) << logMessage.c_str(); } void CefPythonApp::OnRenderProcessThreadCreated( @@ -127,8 +141,9 @@ void CefPythonApp::OnRenderProcessThreadCreated( // The equivalent in Cython is: // | extra_info.Assign(mylist.get()) REQUIRE_IO_THREAD(); - extra_info->SetBool(0, g_debug); - extra_info->SetString(1, g_logFile); + // Eg.: + // | extra_info->SetBool(0, false); + // | extra_info->SetString(1, "test"); // This is included only in the Browser process, when building // the libcefpythonapp library. BrowserProcessHandler_OnRenderProcessThreadCreated(extra_info); @@ -142,7 +157,7 @@ CefRefPtr CefPythonApp::GetPrintHandler() { // required for some of the examples. GdkDisplay* gdk_display = gdk_display_get_default(); if (!gdk_display) { - LOG_DEBUG << "Initialize GTK"; + LOG(INFO) << "[Browser process] Initialize GTK"; gtk_init(0, NULL); } #endif @@ -165,24 +180,6 @@ void CefPythonApp::OnScheduleMessagePumpWork(int64 delay_ms) { // ----------------------------------------------------------------------------- void CefPythonApp::OnRenderThreadCreated(CefRefPtr extra_info) { - if (extra_info->GetType(0) == VTYPE_BOOL) { - g_debug = extra_info->GetBool(0); - if (g_debug) { - FILELog::ReportingLevel() = logDEBUG; - } else { - // In reality this disables logging in LOG_DEBUG.h, as - // we're using only LOG_DEBUG macro. - FILELog::ReportingLevel() = logERROR; - } - } - if (extra_info->GetType(1) == VTYPE_STRING) { - g_logFile = extra_info->GetString(1).ToString(); - } - if (!commandLineString_.empty()) { - // See comment in OnBeforeCommandLineProcessing(). - DebugLog(commandLineString_.c_str()); - commandLineString_ = ""; - } } void CefPythonApp::OnWebKitInitialized() { @@ -192,7 +189,7 @@ void CefPythonApp::OnBrowserCreated(CefRefPtr browser) { } void CefPythonApp::OnBrowserDestroyed(CefRefPtr browser) { - DebugLog("Renderer: OnBrowserDestroyed()"); + LOG(INFO) << "[Renderer process] OnBrowserDestroyed()"; RemoveJavascriptBindings(browser); } @@ -207,7 +204,7 @@ bool CefPythonApp::OnBeforeNavigation(CefRefPtr browser, void CefPythonApp::OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) { - DebugLog("Renderer: OnContextCreated()"); + LOG(INFO) << "[Renderer process] OnContextCreated()"; CefRefPtr message = CefProcessMessage::Create( "OnContextCreated"); CefRefPtr arguments = message->GetArgumentList(); @@ -220,12 +217,12 @@ void CefPythonApp::OnContextCreated(CefRefPtr browser, /* // Example of converting int64 to string. Still need an // example of converting it back from string. - std::string logMessage = "OnContextCreated(): frameId="; + std::string logMessage = "[Renderer process] OnContextCreated(): frameId="; stringstream stream; int64 value = frame->GetIdentifier(); stream << value; logMessage.append(stream.str()); - DebugLog(logMessage.c_str()); + LOG(INFO) << logMessage.c_str(); */ // TODO: losing int64 precision, the solution is to convert // it to string and then in the Browser process back @@ -255,7 +252,7 @@ void CefPythonApp::OnContextCreated(CefRefPtr browser, void CefPythonApp::OnContextReleased(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) { - DebugLog("Renderer: OnContextReleased()"); + LOG(INFO) << "[Renderer process] OnContextReleased()"; CefRefPtr message; CefRefPtr arguments; // ------------------------------------------------------------------------ @@ -308,9 +305,9 @@ bool CefPythonApp::OnProcessMessageReceived(CefRefPtr browser, CefProcessId source_process, CefRefPtr message) { std::string messageName = message->GetName().ToString(); - std::string logMessage = "Renderer: OnProcessMessageReceived(): "; + std::string logMessage = "[Renderer process] OnProcessMessageReceived(): "; logMessage.append(messageName.c_str()); - DebugLog(logMessage.c_str()); + LOG(INFO) << logMessage.c_str(); CefRefPtr args = message->GetArgumentList(); if (messageName == "DoJavascriptBindings") { if (args->GetSize() == 1 @@ -321,8 +318,9 @@ bool CefPythonApp::OnProcessMessageReceived(CefRefPtr browser, args->GetDictionary(0)->Copy(false)); DoJavascriptBindingsForBrowser(browser); } else { - DebugLog("Renderer: OnProcessMessageReceived(): invalid arguments,"\ - " messageName=DoJavascriptBindings"); + LOG(ERROR) << "[Renderer process] OnProcessMessageReceived():" + " invalid arguments," + " messageName=DoJavascriptBindings"; return false; } } else if (messageName == "ExecuteJavascriptCallback") { @@ -338,9 +336,9 @@ bool CefPythonApp::OnProcessMessageReceived(CefRefPtr browser, jsArgs->Remove(0); ExecuteJavascriptCallback(jsCallbackId, jsArgs); } else { - DebugLog("Renderer: OnProcessMessageReceived: invalid arguments," \ - "expected first argument to be a javascript callback " \ - "(int)"); + LOG(ERROR) << "[Renderer process] OnProcessMessageReceived:" + " invalid arguments, expected first argument to be" + " a javascript callback (int)"; return false; } } @@ -383,16 +381,16 @@ bool CefPythonApp::BindedFunctionExists(CefRefPtr browser, std::string::npos)); if (!(jsBindings->HasKey("objects") && jsBindings->GetType("objects") == VTYPE_DICTIONARY)) { - DebugLog("Renderer: BindedFunctionExists() FAILED: "\ - "objects dictionary not found"); + LOG(ERROR) << "[Renderer process] BindedFunctionExists():" + " objects dictionary not found"; return false; } CefRefPtr objects = \ jsBindings->GetDictionary("objects"); if (objects->HasKey(objectName)) { if (!(objects->GetType(objectName) == VTYPE_DICTIONARY)) { - DebugLog("Renderer: BindedFunctionExists() FAILED: "\ - "objects dictionary has invalid type"); + LOG(ERROR) << "[Renderer process] BindedFunctionExists():" + " objects dictionary has invalid type"; return false; } CefRefPtr methods = \ @@ -405,8 +403,8 @@ bool CefPythonApp::BindedFunctionExists(CefRefPtr browser, // This is a function call. if (!(jsBindings->HasKey("functions") && jsBindings->GetType("functions") == VTYPE_DICTIONARY)) { - DebugLog("Renderer: BindedFunctionExists() FAILED: "\ - "functions dictionary not found"); + LOG(ERROR) << "[Renderer process] BindedFunctionExists():" + " functions dictionary not found"; return false; } CefRefPtr functions = \ @@ -425,8 +423,8 @@ void CefPythonApp::DoJavascriptBindingsForBrowser( CefRefPtr jsBindings = GetJavascriptBindings(browser); if (!jsBindings.get()) { // Bindings must be set before this function is called. - DebugLog("Renderer: DoJavascriptBindingsForBrowser() FAILED: " \ - "bindings not set"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForBrowser():" + " bindings not set"; return; } std::vector frameIds; @@ -461,8 +459,8 @@ void CefPythonApp::DoJavascriptBindingsForBrowser( frameIds.push_back(browser->GetMainFrame()->GetIdentifier()); } if (!frameIds.size()) { - DebugLog("Renderer: DoJavascriptBindingsForBrowser() FAILED: " \ - "frameIds.size() == 0"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForBrowser():" + " frameIds.size() == 0"; return; } for (std::vector::iterator it = frameIds.begin(); \ @@ -472,15 +470,15 @@ void CefPythonApp::DoJavascriptBindingsForBrowser( // filled with zeros. This problem was fixed by using' // GetFrameNames() so this block of code should not // be executed anymore. - DebugLog("Renderer: DoJavascriptBindingsForBrowser() WARNING: " \ - "frameId <= 0"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForBrowser():" + " frameId <= 0"; // printf("[CEF Python] Renderer: frameId = %lli\n", *it); continue; } CefRefPtr frame = browser->GetFrame(*it); if (!frame.get()) { - DebugLog("Renderer: DoJavascriptBindingsForBrowser() WARNING: " \ - "GetFrame() failed"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForBrowser():" + " GetFrame() failed"; continue; } CefRefPtr context = frame->GetV8Context(); @@ -498,10 +496,12 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, CefRefPtr jsBindings = GetJavascriptBindings(browser); if (!jsBindings.get()) { // Bindings may not yet be set, it's okay. - DebugLog("Renderer: DoJavascriptBindingsForFrame(): bindings not set"); + LOG(INFO) << "[Renderer process] DoJavascriptBindingsForFrame():" + " bindings not set yet"; return; } - DebugLog("Renderer: DoJavascriptBindingsForFrame(): bindings are set"); + LOG(INFO) << "[Renderer process] DoJavascriptBindingsForFrame():" + " bindings are set"; if (!(jsBindings->HasKey("functions") && jsBindings->GetType("functions") == VTYPE_DICTIONARY && jsBindings->HasKey("properties") @@ -510,8 +510,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, && jsBindings->GetType("objects") == VTYPE_DICTIONARY && jsBindings->HasKey("bindToFrames") && jsBindings->GetType("bindToFrames") == VTYPE_BOOL)) { - DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \ - "invalid data [1]"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " invalid data [1]"; return; } @@ -527,8 +527,10 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, // not be valid. May be a timing issue. Or may be caused by // a redirect to a different origin and that creates a new // renderer process. - DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED:"\ - " V8 context provided by CEF is invalid"); + // This message is logged in the tutorial.py example which + // uses data uri created from html string. + LOG(INFO) << "[Renderer process] DoJavascriptBindingsForFrame():" + " V8 context provided by CEF is invalid"; return; } context->Enter(); @@ -544,8 +546,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, // | bool bindToFrames = jsBindings->GetBool("bindToFrames"); if (!(functions->IsValid() && properties->IsValid() && objects->IsValid())) { - DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \ - "invalid data [2]"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " invalid data [2]"; if (didEnterContext) context->Exit(); return; @@ -556,8 +558,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, // FUNCTIONS. std::vector functionsVector; if (!functions->GetKeys(functionsVector)) { - DebugLog("Renderer: DoJavascriptBindingsForFrame(): " \ - "functions->GetKeys() FAILED"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " functions->GetKeys() failed"; if (didEnterContext) context->Exit(); return; @@ -575,8 +577,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, properties); std::vector v8Keys; if (!v8Properties->GetKeys(v8Keys)) { - DebugLog("DoJavascriptBindingsForFrame() FAILED: " \ - "v8Properties->GetKeys() failed"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " v8Properties->GetKeys() failed"; if (didEnterContext) context->Exit(); return; @@ -590,8 +592,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, // OBJECTS AND ITS METHODS. std::vector objectsVector; if (!objects->GetKeys(objectsVector)) { - DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \ - "objects->GetKeys() failed"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " objects->GetKeys() failed"; if (didEnterContext) context->Exit(); return; @@ -603,8 +605,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, v8Window->SetValue(objectName, v8Object, V8_PROPERTY_ATTRIBUTE_NONE); // METHODS. if (!(objects->GetType(objectName) == VTYPE_DICTIONARY)) { - DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \ - "objects->GetType() != VTYPE_DICTIONARY"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " objects->GetType() != VTYPE_DICTIONARY"; if (didEnterContext) context->Exit(); return; @@ -613,8 +615,8 @@ void CefPythonApp::DoJavascriptBindingsForFrame(CefRefPtr browser, objects->GetDictionary(objectName); std::vector methodsVector; if (!(methods->IsValid() && methods->GetKeys(methodsVector))) { - DebugLog("Renderer: DoJavascriptBindingsForFrame() FAILED: " \ - "methods->GetKeys() failed"); + LOG(ERROR) << "[Renderer process] DoJavascriptBindingsForFrame():" + " methods->GetKeys() failed"; if (didEnterContext) context->Exit(); return; diff --git a/src/subprocess/cefpython_app.h b/src/subprocess/cefpython_app.h index e76b2691c..f9336ba3c 100644 --- a/src/subprocess/cefpython_app.h +++ b/src/subprocess/cefpython_app.h @@ -10,9 +10,9 @@ // CefPythonApp class is instantiated in subprocess and in // cefpython.pyx for the browser process, so the code is shared. -// Using printf() in CefRenderProcessHandler won't work, use -// the DebugLog() function instead, it will write the message -// to the "debug.log" file. +// Using printf() in CefRenderProcessHandler won't work on some +// operating systems, use LOG(INFO) macro instead, it will write +// the message to the "debug.log" file. class CefPythonApp : public CefApp, @@ -20,7 +20,6 @@ class CefPythonApp : public CefRenderProcessHandler { protected: std::map > javascriptBindings_; - std::string commandLineString_; CefRefPtr print_handler_; public: diff --git a/src/subprocess/deprecated_libcefpythonapp_py27_64bit.vcproj b/src/subprocess/deprecated/deprecated_libcefpythonapp_py27_64bit.vcproj similarity index 100% rename from src/subprocess/deprecated_libcefpythonapp_py27_64bit.vcproj rename to src/subprocess/deprecated/deprecated_libcefpythonapp_py27_64bit.vcproj diff --git a/src/subprocess/libcefpythonapp_py27_win32.vcproj b/src/subprocess/deprecated/deprecated_libcefpythonapp_py27_win32.vcproj similarity index 100% rename from src/subprocess/libcefpythonapp_py27_win32.vcproj rename to src/subprocess/deprecated/deprecated_libcefpythonapp_py27_win32.vcproj diff --git a/src/subprocess/deprecated_libcefpythonapp_py34_32bit.vcproj b/src/subprocess/deprecated/deprecated_libcefpythonapp_py34_32bit.vcproj similarity index 100% rename from src/subprocess/deprecated_libcefpythonapp_py34_32bit.vcproj rename to src/subprocess/deprecated/deprecated_libcefpythonapp_py34_32bit.vcproj diff --git a/src/subprocess/deprecated_subprocess_64bit.vcproj b/src/subprocess/deprecated/deprecated_subprocess_64bit.vcproj similarity index 100% rename from src/subprocess/deprecated_subprocess_64bit.vcproj rename to src/subprocess/deprecated/deprecated_subprocess_64bit.vcproj diff --git a/src/subprocess/subprocess_win32.vcproj b/src/subprocess/deprecated/deprecated_subprocess_win32.vcproj similarity index 100% rename from src/subprocess/subprocess_win32.vcproj rename to src/subprocess/deprecated/deprecated_subprocess_win32.vcproj diff --git a/src/subprocess/javascript_callback.cpp b/src/subprocess/javascript_callback.cpp index fd9f55e5d..9aeaf80ca 100644 --- a/src/subprocess/javascript_callback.cpp +++ b/src/subprocess/javascript_callback.cpp @@ -5,9 +5,9 @@ #include "javascript_callback.h" #include #include -#include "DebugLog.h" #include "v8utils.h" #include "cefpython_app.h" +#include "include/base/cef_logging.h" template inline std::string AnyToString(const T& value) @@ -49,17 +49,18 @@ CefString PutJavascriptCallback( bool ExecuteJavascriptCallback(int callbackId, CefRefPtr args) { if (g_jsCallbackMap.empty()) { - DebugLog("Renderer: ExecuteJavascriptCallback() FAILED: " \ - "callback map is empty"); + LOG(ERROR) << "[Renderer process] ExecuteJavascriptCallback():" + " callback map is empty"; return false; } JavascriptCallbackMap::const_iterator it = g_jsCallbackMap.find( callbackId); if (it == g_jsCallbackMap.end()) { - std::string logMessage = "Renderer: ExecuteJavascriptCallback() " - "FAILED: callback not found, id="; + std::string logMessage = "[Renderer process]" + " ExecuteJavascriptCallback():" + " callback not found, id="; logMessage.append(AnyToString(callbackId)); - DebugLog(logMessage.c_str()); + LOG(ERROR) << logMessage.c_str(); return false; } CefRefPtr frame = it->second.first; @@ -74,8 +75,8 @@ bool ExecuteJavascriptCallback(int callbackId, CefRefPtr args) { return true; } else { context->Exit(); - DebugLog("Renderer: ExecuteJavascriptCallback() FAILED: " \ - "callback->ExecuteFunction() FAILED"); + LOG(ERROR) << "[Renderer process] ExecuteJavascriptCallback():" + " callback->ExecuteFunction() failed"; return false; } } @@ -95,8 +96,9 @@ void RemoveJavascriptCallbacksForFrame(CefRefPtr frame) { // | ++it; // This would cause an infinite loop. g_jsCallbackMap.erase(it++); - DebugLog("Renderer: RemoveJavascriptCallbacksForFrame(): " \ - "removed js callback from the map"); + LOG(INFO) << "[Renderer process]" + " RemoveJavascriptCallbacksForFrame():" + " removed js callback from the map"; } else { ++it; } diff --git a/src/subprocess/v8function_handler.cpp b/src/subprocess/v8function_handler.cpp index 508a78bbf..0ce3a980b 100644 --- a/src/subprocess/v8function_handler.cpp +++ b/src/subprocess/v8function_handler.cpp @@ -4,7 +4,7 @@ #include "cefpython_app.h" #include "v8utils.h" -#include "DebugLog.h" +#include "include/base/cef_logging.h" bool V8FunctionHandler::Execute(const CefString& functionName, CefRefPtr thisObject, @@ -14,15 +14,16 @@ bool V8FunctionHandler::Execute(const CefString& functionName, if (!CefV8Context::InContext()) { // CefV8Context::GetCurrentContext may not be called when // not in a V8 context. - DebugLog("Renderer: V8FunctionHandler::Execute() FAILED:"\ - " not inside a V8 context"); + LOG(ERROR) << "[Renderer process] V8FunctionHandler::Execute():" + " not inside a V8 context"; return false; } CefRefPtr context = CefV8Context::GetCurrentContext(); CefRefPtr browser = context.get()->GetBrowser(); CefRefPtr frame = context.get()->GetFrame(); if (pythonCallbackId_) { - DebugLog("Renderer: V8FunctionHandler::Execute(): python callback"); + LOG(INFO) << "[Renderer process] V8FunctionHandler::Execute():" + " python callback"; CefRefPtr functionArguments = V8ValueListToCefListValue( v8Arguments); CefRefPtr processMessage = \ @@ -35,7 +36,8 @@ bool V8FunctionHandler::Execute(const CefString& functionName, returnValue = CefV8Value::CreateNull(); return true; } else { - DebugLog("Renderer: V8FunctionHandler::Execute(): js binding"); + LOG(INFO) << "[Renderer process] V8FunctionHandler::Execute():" + " js binding"; if (!(cefPythonApp_.get() \ && cefPythonApp_->BindedFunctionExists( \ browser, functionName))) { diff --git a/src/subprocess/v8utils.cpp b/src/subprocess/v8utils.cpp index e53d46a41..684f8c4ec 100644 --- a/src/subprocess/v8utils.cpp +++ b/src/subprocess/v8utils.cpp @@ -9,7 +9,7 @@ #include "v8utils.h" #include "javascript_callback.h" -#include "DebugLog.h" +#include "include/base/cef_logging.h" #include "cefpython_app.h" #include @@ -33,12 +33,13 @@ void V8ValueAppendToCefListValue(CefRefPtr v8Value, CefRefPtr listValue, int nestingLevel) { if (!v8Value->IsValid()) { - DebugLog("V8ValueAppendToCefListValue(): IsValid() FAILED"); + LOG(ERROR) << "[Renderer process] V8ValueAppendToCefListValue():" + " IsValid() failed"; return; } if (nestingLevel > 8) { - DebugLog("V8ValueAppendToCefListValue(): WARNING: max nesting level (8) " \ - "exceeded"); + LOG(ERROR) << "[Renderer process] V8ValueAppendToCefListValue():" + " max nesting level (8) exceeded"; return; } if (v8Value->IsUndefined() || v8Value->IsNull()) { @@ -53,7 +54,8 @@ void V8ValueAppendToCefListValue(CefRefPtr v8Value, &uint32_value, sizeof(uint32_value)); listValue->SetBinary((int)listValue->GetSize(), binaryValue); } else if (v8Value->IsDouble()) { - listValue->SetDouble((int)listValue->GetSize(), v8Value->GetDoubleValue()); + listValue->SetDouble((int)listValue->GetSize(), + v8Value->GetDoubleValue()); } else if (v8Value->IsDate()) { // TODO: in time_utils.pyx there are already functions for // converting cef_time_t to python DateTime, we could easily @@ -86,8 +88,9 @@ void V8ValueAppendToCefListValue(CefRefPtr v8Value, listValue->SetString((int)listValue->GetSize(), strCallbackId); } else { listValue->SetNull((int)listValue->GetSize()); - DebugLog("V8ValueAppendToCefListValue() FAILED: not in V8 context" - " , FATAL ERROR!"); + LOG(ERROR) << "[Renderer process] V8ValueAppendToCefListValue():" + " not in V8 context"; + return; } } else if (v8Value->IsObject()) { // Check for IsObject() must happen after the IsArray() @@ -96,7 +99,8 @@ void V8ValueAppendToCefListValue(CefRefPtr v8Value, V8ObjectToCefDictionaryValue(v8Value, nestingLevel + 1)); } else { listValue->SetNull((int)listValue->GetSize()); - DebugLog("V8ValueAppendToCefListValue() FAILED: unknown V8 type"); + LOG(ERROR) << "[Renderer process] V8ValueAppendToCefListValue():" + " unknown V8 type"; } } @@ -104,22 +108,25 @@ CefRefPtr V8ObjectToCefDictionaryValue( CefRefPtr v8Object, int nestingLevel) { if (!v8Object->IsValid()) { - DebugLog("V8ObjectToCefDictionaryValue(): IsValid() FAILED"); + LOG(ERROR) << "[Renderer process] V8ObjectToCefDictionaryValue():" + " IsValid() failed"; return CefDictionaryValue::Create(); } if (nestingLevel > 8) { - DebugLog("V8ObjectToCefDictionaryValue(): WARNING: " \ - "max nesting level (8) exceeded"); + LOG(ERROR) << "[Renderer process] V8ObjectToCefDictionaryValue():" + " max nesting level (8) exceeded"; return CefDictionaryValue::Create(); } if (!v8Object->IsObject()) { - DebugLog("V8ObjectToCefDictionaryValue(): IsObject() FAILED"); + LOG(ERROR) << "[Renderer process] V8ObjectToCefDictionaryValue():" + " IsObject() failed"; return CefDictionaryValue::Create(); } CefRefPtr ret = CefDictionaryValue::Create(); std::vector keys; if (!v8Object->GetKeys(keys)) { - DebugLog("V8ObjectToCefDictionaryValue(): GetKeys() FAILED"); + LOG(ERROR) << "[Renderer process] V8ObjectToCefDictionaryValue():" + " GetKeys() failed"; return ret; } for (std::vector::iterator it = keys.begin(); \ @@ -172,8 +179,9 @@ CefRefPtr V8ObjectToCefDictionaryValue( ret->SetString(key, strCallbackId); } else { ret->SetNull(key); - DebugLog("V8ObjectToCefDictionaryValue() FAILED: " \ - "not in V8 context FATAL ERROR!"); + LOG(ERROR) << "[Renderer process]" + " V8ObjectToCefDictionaryValue():" + " not in V8 context"; } } else if (v8Value->IsObject()) { // Check for IsObject() must happen after the IsArray() @@ -182,7 +190,8 @@ CefRefPtr V8ObjectToCefDictionaryValue( V8ObjectToCefDictionaryValue(v8Value, nestingLevel + 1)); } else { ret->SetNull(key); - DebugLog("V8ObjectToCefDictionaryValue() FAILED: unknown V8 type"); + LOG(ERROR) << "[Renderer process] V8ObjectToCefDictionaryValue():" + " unknown V8 type"; } } return ret; @@ -223,13 +232,13 @@ CefRefPtr CefListValueToV8Value( CefRefPtr listValue, int nestingLevel) { if (!listValue->IsValid()) { - DebugLog("CefListValueToV8Value() FAILED: " \ - "CefDictionaryValue is invalid"); + LOG(ERROR) << "[Renderer process] CefListValueToV8Value():" + " CefDictionaryValue is invalid"; return CefV8Value::CreateNull(); } if (nestingLevel > 8) { - DebugLog("CefListValueToV8Value(): WARNING: " \ - "max nesting level (8) exceeded"); + LOG(ERROR) << "[Renderer process] CefListValueToV8Value():" + " max nesting level (8) exceeded"; return CefV8Value::CreateNull(); } int listSize = (int)listValue->GetSize(); @@ -269,8 +278,8 @@ CefRefPtr CefListValueToV8Value( CefV8Value::CreateFunction( callbackName, v8FunctionHandler)); } else { - DebugLog("CefListValueToV8Value(): WARNING: " \ - "unknown binary value, setting value to null"); + LOG(ERROR) << "[Renderer process] CefListValueToV8Value():" + " unknown binary value, setting value to null"; success = ret->SetValue(key, CefV8Value::CreateNull()); } } else if (valueType == VTYPE_DICTIONARY) { @@ -284,14 +293,14 @@ CefRefPtr CefListValueToV8Value( listValue->GetList(key), nestingLevel + 1)); } else { - DebugLog("CefListValueToV8Value(): WARNING: " \ - "unknown type, setting value to null"); + LOG(ERROR) << "[Renderer process] CefListValueToV8Value():" + " unknown type, setting value to null"; success = ret->SetValue(key, CefV8Value::CreateNull()); } if (!success) { - DebugLog("CefListValueToV8Value(): WARNING: " \ - "ret->SetValue() failed"); + LOG(ERROR) << "[Renderer process] CefListValueToV8Value():" + " ret->SetValue() failed"; } } return ret; @@ -301,19 +310,19 @@ CefRefPtr CefDictionaryValueToV8Value( CefRefPtr dictValue, int nestingLevel) { if (!dictValue->IsValid()) { - DebugLog("CefDictionaryValueToV8Value() FAILED: " \ - "CefDictionaryValue is invalid"); + LOG(ERROR) << "[Renderer process] CefDictionaryValueToV8Value():" + " CefDictionaryValue is invalid"; return CefV8Value::CreateNull(); } if (nestingLevel > 8) { - DebugLog("CefDictionaryValueToV8Value(): WARNING: " \ - "max nesting level (8) exceeded"); + LOG(ERROR) << "[Renderer process] CefDictionaryValueToV8Value():" + " max nesting level (8) exceeded"; return CefV8Value::CreateNull(); } std::vector keys; if (!dictValue->GetKeys(keys)) { - DebugLog("CefDictionaryValueToV8Value() FAILED: " \ - "dictValue->GetKeys() failed"); + LOG(ERROR) << "[Renderer process] CefDictionaryValueToV8Value():" + " dictValue->GetKeys() failed"; return CefV8Value::CreateNull(); } CefRefPtr ret = CefV8Value::CreateObject(NULL, NULL); @@ -360,8 +369,8 @@ CefRefPtr CefDictionaryValueToV8Value( callbackName, v8FunctionHandler), V8_PROPERTY_ATTRIBUTE_NONE); } else { - DebugLog("CefListValueToV8Value(): WARNING: " \ - "unknown binary value, setting value to null"); + LOG(ERROR) << "[Renderer process] CefListValueToV8Value():" + " unknown binary value, setting value to null"; success = ret->SetValue(key, CefV8Value::CreateNull(), V8_PROPERTY_ATTRIBUTE_NONE); @@ -379,15 +388,15 @@ CefRefPtr CefDictionaryValueToV8Value( nestingLevel + 1), V8_PROPERTY_ATTRIBUTE_NONE); } else { - DebugLog("CefDictionaryValueToV8Value(): WARNING: " \ - "unknown type, setting value to null"); + LOG(ERROR) << "[Renderer process] CefDictionaryValueToV8Value():" + " unknown type, setting value to null"; success = ret->SetValue(key, CefV8Value::CreateNull(), V8_PROPERTY_ATTRIBUTE_NONE); } if (!success) { - DebugLog("CefDictionaryValueToV8Value(): WARNING: " \ - "ret->SetValue() failed"); + LOG(ERROR) << "[Renderer process] CefDictionaryValueToV8Value():" + " ret->SetValue() failed"; } } return ret; diff --git a/src/utils.pyx b/src/utils.pyx index 3f51b25ae..62781196a 100644 --- a/src/utils.pyx +++ b/src/utils.pyx @@ -43,22 +43,17 @@ cpdef py_bool IsThread(int threadID): cpdef object Debug(py_string msg): """Print debug message. Will be shown only when settings.debug=True.""" - if not g_debug: - return # In Python 3 str or bytes may be passed if type(msg) != str and type(msg) == bytes: msg = msg.decode("utf-8", "replace") # Convert to str in case other kind of object was passed msg = str(msg) - msg = "[CEF Python] "+msg - print(msg) - if g_debugFile: - try: - with open(g_debugFile, "a") as file_: - file_.write(msg+"\n") - except: - print("[CEF Python] WARNING: failed writing to debug file: %s" % ( - g_debugFile)) + msg = "[Browser process] " + msg + # CEF logging is initialized only after CEF was initialized. + # Otherwise the default is LOGSEVERITY_INFO and log_file is + # none. + if g_cef_initialized or g_debug: + cef_log_info(msg) cdef void NonCriticalError(py_string msg) except *: """Notify about error gently. Does not terminate application.""" @@ -67,15 +62,8 @@ cdef void NonCriticalError(py_string msg) except *: msg = msg.decode("utf-8", "replace") # Convert to str in case other kind of object was passed msg = str(msg) - msg = "[CEF Python] ERROR: "+msg - print(msg) - if g_debugFile: - try: - with open(g_debugFile, "a") as file_: - file_.write(msg+"\n") - except: - print("[CEF Python] WARNING: failed writing to debug file: %s" % ( - g_debugFile)) + msg = "[Browser process] " + msg + cef_log_error(PyStringToChar(msg)) cpdef str GetSystemError(): IF UNAME_SYSNAME == "Windows": diff --git a/tools/cython_setup.py b/tools/cython_setup.py index 368d5ccc3..29baa755a 100644 --- a/tools/cython_setup.py +++ b/tools/cython_setup.py @@ -171,21 +171,21 @@ def set_compiler_options(options): if LINUX: os.environ["CC"] = "g++" os.environ["CXX"] = "g++" + extra_compile_args.extend(["-std=gnu++11"]) + + # Fix "ImportError ... undefined symbol ..." caused by CEF's + # include/base/ headers by adding the -flto flag (Issue #230). + # Unfortunately -flto prolongs compilation time significantly. + # More on the other flags: https://stackoverflow.com/questions/ + # 6687630/ . if FAST_FLAG: - extra_compile_args.extend(["-flto", - "-std=gnu++11"]) + extra_compile_args.extend(["-flto"]) extra_link_args.extend(["-flto"]) else: - # Fix "ImportError ... undefined symbol ..." caused by CEF's - # include/base/ headers by adding the -flto flag (Issue #230). - # Unfortunately -flto prolongs compilation time significantly. - # More on the other flags: https://stackoverflow.com/questions/ - # 6687630/ . extra_compile_args.extend(["-flto", "-fdata-sections", - "-ffunction-sections", - "-std=gnu++11"]) + "-ffunction-sections"]) extra_link_args.extend(["-flto", "-Wl,--gc-sections"]) From eeae125d59727ce5c77355128353741e5f8b4db3 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Wed, 19 Apr 2017 19:12:45 +0200 Subject: [PATCH 057/398] Fix build on Linux and fix logging function on Python 3 (#352). --- src/client_handler/client_handler.h | 2 -- src/common/cefpython_public_api.h | 15 +++++++++------ src/compile_time_constants.pxi | 2 +- src/utils.pyx | 2 +- tools/build.py | 19 +++++++++++++++---- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/client_handler/client_handler.h b/src/client_handler/client_handler.h index 4a5c29b7c..3e9e3917b 100644 --- a/src/client_handler/client_handler.h +++ b/src/client_handler/client_handler.h @@ -10,8 +10,6 @@ #include #endif -#include "common/cefpython_public_api.h" - #include "context_menu_handler.h" #include "dialog_handler.h" #include "display_handler.h" diff --git a/src/common/cefpython_public_api.h b/src/common/cefpython_public_api.h index 0fa115d41..0fbd60a56 100644 --- a/src/common/cefpython_public_api.h +++ b/src/common/cefpython_public_api.h @@ -9,18 +9,21 @@ #ifndef CEFPYTHON_PUBLIC_API_H #define CEFPYTHON_PUBLIC_API_H -// Includes required by "cefpython_fixed.h". -#include "include/cef_client.h" -#include "include/cef_urlrequest.h" -#include "include/cef_command_line.h" -#include "util.h" - #if defined(OS_WIN) #pragma warning(disable:4190) // cefpython API extern C-linkage warnings #endif +// Python.h must be included first otherwise error on Linux: +// >> error: "_POSIX_C_SOURCE" redefined #include "Python.h" + +// Includes required by "cefpython_fixed.h". +#include "include/cef_client.h" +#include "include/cef_urlrequest.h" +#include "include/cef_command_line.h" +#include "util.h" + // cefpython_fixed.h declares public functions using DL_IMPORT and these // macros are not available in Python 3. #ifndef DL_IMPORT diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..47dedaecd 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 2 +DEF PY_MAJOR_VERSION = 3 diff --git a/src/utils.pyx b/src/utils.pyx index 62781196a..e253e9f74 100644 --- a/src/utils.pyx +++ b/src/utils.pyx @@ -53,7 +53,7 @@ cpdef object Debug(py_string msg): # Otherwise the default is LOGSEVERITY_INFO and log_file is # none. if g_cef_initialized or g_debug: - cef_log_info(msg) + cef_log_info(PyStringToChar(msg)) cdef void NonCriticalError(py_string msg) except *: """Notify about error gently. Does not terminate application.""" diff --git a/tools/build.py b/tools/build.py index 3e4f0f8ba..1ae47b737 100644 --- a/tools/build.py +++ b/tools/build.py @@ -41,6 +41,14 @@ # function then you can't pass shell=True on Linux. If you pass # then it will execute args[0] and ignore others args. +# NOTE 2: When calling os.system() returned value may be 256 when eg. +# when running unit tests fail. If you pass 256 to sys.exit +# an undefined result will occur. In my case on Linux it caused +# that other script that called it sys.exit(256) was interpreted +# for the script execute successfully. So never pass to sys.exit +# a value returned from os.system. Check the value and call +# sys.exit(1). + # How to debug on Linux (OLD unsupported). # 1. Install "python-dbg" package # 2. Install "python-wxgtk2.8-dbg" package @@ -753,7 +761,10 @@ def build_cefpython_module(): args.extend(sys.argv[1:]) command = " ".join(args) ret = subprocess.call(command, shell=True) - sys.exit(ret) + # Always pass fixed value to sys.exit, read note at + # the top of the script about os.system and sys.exit + # issue. + sys.exit(0 if ret == 0 else 1) else: print("[build.py] ERROR: failed to build the cefpython module") sys.exit(1) @@ -823,7 +834,7 @@ def install_and_run(): ret = os.system(command) if ret != 0: print("[build.py] ERROR while making installer package") - sys.exit(ret) + sys.exit(1) # Install print("[build.py] Install the cefpython package") @@ -834,7 +845,7 @@ def install_and_run(): ret = os.system(command) if ret != 0: print("[build.py] ERROR while installing package") - sys.exit(ret) + sys.exit(1) os.chdir(BUILD_DIR) # Delete setup installer directory after the package was installed @@ -849,7 +860,7 @@ def install_and_run(): ret = os.system(command) if ret != 0: print("[build.py] ERROR while running unit tests") - sys.exit(ret) + sys.exit(1) # Run examples if not NO_RUN_EXAMPLES: From 321b34abe4067195ed3d042beef211f2546182b1 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Wed, 19 Apr 2017 21:29:10 +0200 Subject: [PATCH 058/398] Update docs for 56.2 release. Do not call client handlers nor javascript bindings in DevTools windows (#344). Update Tutorial and tutorial.py example. --- README.md | 2 +- docs/Migration-guide.md | 11 +++++------ docs/Tutorial.md | 6 +----- examples/Examples-README.md | 2 +- examples/tutorial.py | 18 +++--------------- 5 files changed, 11 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 114d2d358..7b4bbfaae 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ also download packages for offline installation available on the [GitHub Releases](../../releases) pages. ``` -pip install cefpython3==56.1 +pip install cefpython3==56.2 ``` ## Tutorial diff --git a/docs/Migration-guide.md b/docs/Migration-guide.md index 3bab17bc4..d938e70d2 100644 --- a/docs/Migration-guide.md +++ b/docs/Migration-guide.md @@ -40,12 +40,11 @@ Table of contents: ## v50+ Distribution packages -In latest CEF Python there are only two distribution packages -available. The first one is a wheel package distributed through -PyPI, which you can install using the pip tool (8.1+ required -on Linux). The second one is a setup package available for -download on the GitHub Releases pages, instructions on how to -install it are provided in README.txt. +In latest CEF Python there is only one distribution package +available: a wheel package. Wheel packages are distributed on +[PyPI](https://pypi.python.org/pypi/cefpython3) and you can +install it using the pip tool (8.1+ required on Linux). You +can also download wheel packages from [GitHub Releases](../../../releases). **Windows** diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 91cc029a1..c4a38ec25 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -36,7 +36,7 @@ Run the commands below to install the cefpython3 package, clone the repository and run the Hello World example: ```commandline -pip install cefpython3==56.1 +pip install cefpython3==56.2 git clone https://github.com/cztomczak/cefpython.git cd cefpython/examples/ python hello_world.py @@ -320,10 +320,6 @@ def set_client_handlers(browser): ... class LoadHandler(object): def OnLoadingStateChange(self, browser, is_loading, **_): - # Issue #344 will fix this in next release, so that client - # handlers are not called for Developer Tools windows. - if browser.GetUrl().startswith("chrome-devtools://"): - return # This callback is called twice, once when loading starts # (is_loading=True) and second time when loading ends # (is_loading=False). diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 2fae3e14a..7be26d960 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -11,7 +11,7 @@ Instructions to install the cefpython3 package, clone the repository and run the hello_world.py example: ``` -pip install cefpython3==56.1 +pip install cefpython3==56.2 git clone https://github.com/cztomczak/cefpython.git cd cefpython/examples/ python hello_world.py diff --git a/examples/tutorial.py b/examples/tutorial.py index 28d69ddb7..a2b04ca89 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -1,5 +1,5 @@ # Tutorial example. Doesn't depend on any third party GUI framework. -# Tested with CEF Python v56.1+ +# Tested with CEF Python v56.2+ from cefpython3 import cefpython as cef import base64 @@ -61,7 +61,7 @@ def main(): cef.Initialize() set_global_handler() browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code), - window_title="Hello World!") + window_title="Tutorial") set_client_handlers(browser) set_javascript_bindings(browser) cef.MessageLoop() @@ -72,7 +72,7 @@ def check_versions(): print("[tutorial.py] CEF Python {ver}".format(ver=cef.__version__)) print("[tutorial.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) - assert cef.__version__ >= "56.1", "CEF Python v56.1+ required to run this" + assert cef.__version__ >= "56.2", "CEF Python v56.2+ required to run this" def html_to_data_uri(html, js_callback=None): @@ -125,10 +125,6 @@ def js_print(browser, lang, event, msg): class GlobalHandler(object): def OnAfterCreated(self, browser, **_): - # Issue #344 will fix this in next release, so that client - # handlers are not called for Developer Tools windows. - if browser.GetUrl().startswith("chrome-devtools://"): - return # DOM is not yet loaded. Using js_print at this moment will # throw an error: "Uncaught ReferenceError: js_print is not defined". # We make this error on purpose. This error will be intercepted @@ -143,10 +139,6 @@ def OnAfterCreated(self, browser, **_): class LoadHandler(object): def OnLoadingStateChange(self, browser, is_loading, **_): - # Issue #344 will fix this in next release, so that client - # handlers are not called for Developer Tools windows. - if browser.GetUrl().startswith("chrome-devtools://"): - return # This callback is called twice, once when loading starts # (is_loading=True) and second time when loading ends # (is_loading=False). @@ -158,10 +150,6 @@ def OnLoadingStateChange(self, browser, is_loading, **_): class DisplayHandler(object): def OnConsoleMessage(self, browser, message, **_): - # Issue #344 will fix this in next release, so that client - # handlers are not called for Developer Tools windows. - if browser.GetUrl().startswith("chrome-devtools://"): - return # This will intercept js errors, see comments in OnAfterCreated if "error" in message.lower() or "uncaught" in message.lower(): # Prevent infinite recurrence in case something went wrong From 91ec299744c7854fb8eca5f4881274a925c1a4bd Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 20 Apr 2017 15:15:26 +0200 Subject: [PATCH 059/398] Update to Chromium 57.0.2987.133 on Linux (incomplete) (#341). CEF 3.2987.1601.gf035232 Update patches. --- patches/issue125.patch | 4 +- patches/issue231.patch | 8 +- patches/issue251.patch | 16 +- src/extern/cef/cef_base.pxd | 10 - src/include/base/cef_build.h | 9 + src/include/cef_app.h | 4 +- src/include/cef_auth_callback.h | 2 +- src/include/cef_base.h | 17 +- src/include/cef_browser.h | 12 +- src/include/cef_browser_process_handler.h | 2 +- src/include/cef_callback.h | 4 +- src/include/cef_client.h | 2 +- src/include/cef_command_line.h | 2 +- src/include/cef_context_menu_handler.h | 6 +- src/include/cef_cookie.h | 8 +- src/include/cef_dialog_handler.h | 4 +- src/include/cef_display_handler.h | 2 +- src/include/cef_dom.h | 6 +- src/include/cef_download_handler.h | 6 +- src/include/cef_download_item.h | 2 +- src/include/cef_drag_data.h | 2 +- src/include/cef_drag_handler.h | 2 +- src/include/cef_find_handler.h | 2 +- src/include/cef_focus_handler.h | 2 +- src/include/cef_frame.h | 2 +- src/include/cef_geolocation.h | 2 +- src/include/cef_geolocation_handler.h | 4 +- src/include/cef_image.h | 2 +- src/include/cef_jsdialog_handler.h | 4 +- src/include/cef_keyboard_handler.h | 2 +- src/include/cef_life_span_handler.h | 2 +- src/include/cef_load_handler.h | 2 +- src/include/cef_menu_model.h | 87 ++++- src/include/cef_menu_model_delegate.h | 26 +- src/include/cef_navigation_entry.h | 2 +- src/include/cef_print_handler.h | 6 +- src/include/cef_print_settings.h | 2 +- src/include/cef_process_message.h | 2 +- src/include/cef_render_handler.h | 2 +- src/include/cef_render_process_handler.h | 2 +- src/include/cef_request.h | 6 +- src/include/cef_request_context.h | 4 +- src/include/cef_request_context_handler.h | 2 +- src/include/cef_request_handler.h | 6 +- src/include/cef_resource_bundle.h | 2 +- src/include/cef_resource_bundle_handler.h | 2 +- src/include/cef_resource_handler.h | 2 +- src/include/cef_response.h | 2 +- src/include/cef_response_filter.h | 2 +- src/include/cef_scheme.h | 31 +- src/include/cef_ssl_info.h | 2 +- src/include/cef_ssl_status.h | 2 +- src/include/cef_stream.h | 8 +- src/include/cef_string_visitor.h | 2 +- src/include/cef_task.h | 4 +- src/include/cef_thread.h | 2 +- src/include/cef_trace.h | 2 +- src/include/cef_urlrequest.h | 4 +- src/include/cef_v8.h | 20 +- src/include/cef_values.h | 8 +- src/include/cef_waitable_event.h | 2 +- src/include/cef_web_plugin.h | 8 +- src/include/cef_x509_certificate.h | 4 +- src/include/cef_xml_reader.h | 2 +- src/include/cef_zip_reader.h | 2 +- src/include/internal/cef_ptr.h | 85 +++- src/include/internal/cef_string_types.h | 11 + src/include/internal/cef_types.h | 20 + src/include/internal/cef_types_wrappers.h | 2 + src/include/test/cef_translator_test.h | 409 +++++++++++++++++--- src/include/views/cef_browser_view.h | 12 + src/include/views/cef_button.h | 6 + src/include/views/cef_button_delegate.h | 6 + src/include/views/cef_display.h | 2 +- src/include/views/cef_layout.h | 2 +- src/include/views/cef_menu_button.h | 7 + src/include/views/cef_view.h | 16 +- src/include/views/cef_view_delegate.h | 14 +- src/include/views/cef_window.h | 24 ++ src/include/views/cef_window_delegate.h | 19 + src/include/wrapper/cef_byte_read_handler.h | 4 +- src/include/wrapper/cef_closure_task.h | 2 +- src/include/wrapper/cef_message_router.h | 2 +- src/include/wrapper/cef_zip_archive.h | 2 +- 84 files changed, 836 insertions(+), 223 deletions(-) delete mode 100644 src/extern/cef/cef_base.pxd diff --git a/patches/issue125.patch b/patches/issue125.patch index 1cde324ae..4c9f304ac 100644 --- a/patches/issue125.patch +++ b/patches/issue125.patch @@ -1,8 +1,8 @@ diff --git http_cache_transaction.cc http_cache_transaction.cc -index 717522eb6951..e92ba22a9ca9 100644 +index 24002d75215d..2a44a6368558 100644 --- http_cache_transaction.cc +++ http_cache_transaction.cc -@@ -2554,7 +2554,8 @@ int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) { +@@ -2601,7 +2601,8 @@ int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) { // blocking page is shown. An alternative would be to reverse-map the cert // status to a net error and replay the net error. if ((response_.headers->HasHeaderValue("cache-control", "no-store")) || diff --git a/patches/issue231.patch b/patches/issue231.patch index c265759bc..6e62498ed 100644 --- a/patches/issue231.patch +++ b/patches/issue231.patch @@ -83,10 +83,10 @@ index 6a759309..ad620d7f 100644 + return PathService::Override(pref_key, file_path); +} diff --git libcef_dll/libcef_dll.cc libcef_dll/libcef_dll.cc -index 1f037f92..b0274ad8 100644 +index 096eede2..05ecf9bd 100644 --- libcef_dll/libcef_dll.cc +++ libcef_dll/libcef_dll.cc -@@ -927,6 +927,23 @@ CEF_EXPORT int cef_get_path(cef_path_key_t key, cef_string_t* path) { +@@ -959,6 +959,23 @@ CEF_EXPORT int cef_get_path(cef_path_key_t key, cef_string_t* path) { return _retval; } @@ -111,10 +111,10 @@ index 1f037f92..b0274ad8 100644 // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING diff --git libcef_dll/wrapper/libcef_dll_wrapper.cc libcef_dll/wrapper/libcef_dll_wrapper.cc -index c4a1b559..a0b7765b 100644 +index 15026ab5..a3d21052 100644 --- libcef_dll/wrapper/libcef_dll_wrapper.cc +++ libcef_dll/wrapper/libcef_dll_wrapper.cc -@@ -848,6 +848,23 @@ CEF_GLOBAL bool CefGetPath(PathKey key, CefString& path) { +@@ -880,6 +880,23 @@ CEF_GLOBAL bool CefGetPath(PathKey key, CefString& path) { return _retval?true:false; } diff --git a/patches/issue251.patch b/patches/issue251.patch index e1e9bc4ea..1a7b51538 100644 --- a/patches/issue251.patch +++ b/patches/issue251.patch @@ -1,5 +1,5 @@ diff --git include/capi/cef_drag_data_capi.h include/capi/cef_drag_data_capi.h -index 2e84df8f..5b17e212 100644 +index e1fcfd8c..fc388247 100644 --- include/capi/cef_drag_data_capi.h +++ include/capi/cef_drag_data_capi.h @@ -39,6 +39,7 @@ @@ -34,7 +34,7 @@ index 2e84df8f..5b17e212 100644 diff --git include/cef_drag_data.h include/cef_drag_data.h -index 8f8094b4..8b4602b1 100644 +index 29b85e84..e3c9c2df 100644 --- include/cef_drag_data.h +++ include/cef_drag_data.h @@ -39,6 +39,7 @@ @@ -45,7 +45,7 @@ index 8f8094b4..8b4602b1 100644 #include "include/cef_stream.h" #include -@@ -193,6 +194,24 @@ class CefDragData : public virtual CefBase { +@@ -193,6 +194,24 @@ class CefDragData : public virtual CefBaseRefCounted { /// /*--cef(optional_param=display_name)--*/ virtual void AddFile(const CefString& path, const CefString& display_name) =0; @@ -181,7 +181,7 @@ index 64f29ed3..98707262 100644 // True if this object is read-only. bool read_only_; diff --git libcef_dll/cpptoc/drag_data_cpptoc.cc libcef_dll/cpptoc/drag_data_cpptoc.cc -index ffad1755..70a6c5c2 100644 +index 381b88b9..6f4925a0 100644 --- libcef_dll/cpptoc/drag_data_cpptoc.cc +++ libcef_dll/cpptoc/drag_data_cpptoc.cc @@ -11,6 +11,7 @@ @@ -254,9 +254,9 @@ index ffad1755..70a6c5c2 100644 + GetStruct()->has_image = drag_data_has_image; } - template<> CefRefPtr CefCppToC CefRefPtr CefCppToCRefCounted 199711L || \ + (defined(_MSC_VER) && _MSC_VER >= 1800) || \ + (defined(__GNUC__) && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ >= 40700)) +#define HAS_CPP11_TEMPLATE_ALIAS_SUPPORT +#endif + #endif // CEF_INCLUDE_BASE_CEF_BUILD_H_ diff --git a/src/include/cef_app.h b/src/include/cef_app.h index 098b6d756..5aca29b30 100644 --- a/src/include/cef_app.h +++ b/src/include/cef_app.h @@ -143,7 +143,7 @@ void CefEnableHighDPISupport(); // called by the process and/or thread indicated. /// /*--cef(source=client,no_debugct_check)--*/ -class CefApp : public virtual CefBase { +class CefApp : public virtual CefBaseRefCounted { public: /// // Provides an opportunity to view and/or modify command-line arguments before @@ -170,7 +170,7 @@ class CefApp : public virtual CefBase { /// /*--cef()--*/ virtual void OnRegisterCustomSchemes( - CefRefPtr registrar) { + CefRawPtr registrar) { } /// diff --git a/src/include/cef_auth_callback.h b/src/include/cef_auth_callback.h index 86d249ad3..ca511fdc4 100644 --- a/src/include/cef_auth_callback.h +++ b/src/include/cef_auth_callback.h @@ -45,7 +45,7 @@ // requests. /// /*--cef(source=library)--*/ -class CefAuthCallback : public virtual CefBase { +class CefAuthCallback : public virtual CefBaseRefCounted { public: /// // Continue the authentication request. diff --git a/src/include/cef_base.h b/src/include/cef_base.h index c9213588a..c152cf06d 100644 --- a/src/include/cef_base.h +++ b/src/include/cef_base.h @@ -48,10 +48,9 @@ #endif /// -// Interface defining the reference count implementation methods. All framework -// classes must extend the CefBase class. +// All ref-counted framework classes must extend this class. /// -class CefBase { +class CefBaseRefCounted { public: /// // Called to increment the reference count for the object. Should be called @@ -71,7 +70,15 @@ class CefBase { virtual bool HasOneRef() const =0; protected: - virtual ~CefBase() {} + virtual ~CefBaseRefCounted() {} +}; + +/// +// All scoped framework classes must extend this class. +/// +class CefBaseScoped { + public: + virtual ~CefBaseScoped() {} }; /// @@ -141,7 +148,7 @@ class CefRefCount { // #include "include/base/cef_lock.h" // // // Class declaration. -// class MyClass : public CefBase { +// class MyClass : public CefBaseRefCounted { // public: // MyClass() : value_(0) {} // // Method that may be called on multiple threads. diff --git a/src/include/cef_browser.h b/src/include/cef_browser.h index 2d4d8abb7..3b4ed1617 100644 --- a/src/include/cef_browser.h +++ b/src/include/cef_browser.h @@ -58,7 +58,7 @@ class CefClient; // this class may only be called on the main thread. /// /*--cef(source=library)--*/ -class CefBrowser : public virtual CefBase { +class CefBrowser : public virtual CefBaseRefCounted { public: /// // Returns the browser host object. This method can only be called in the @@ -197,7 +197,7 @@ class CefBrowser : public virtual CefBase { // class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefRunFileDialogCallback : public virtual CefBase { +class CefRunFileDialogCallback : public virtual CefBaseRefCounted { public: /// // Called asynchronously after the file dialog is dismissed. @@ -218,7 +218,7 @@ class CefRunFileDialogCallback : public virtual CefBase { // this class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefNavigationEntryVisitor : public virtual CefBase { +class CefNavigationEntryVisitor : public virtual CefBaseRefCounted { public: /// // Method that will be executed. Do not keep a reference to |entry| outside of @@ -240,7 +240,7 @@ class CefNavigationEntryVisitor : public virtual CefBase { // will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefPdfPrintCallback : public virtual CefBase { +class CefPdfPrintCallback : public virtual CefBaseRefCounted { public: /// // Method that will be executed when the PDF printing has completed. |path| @@ -257,7 +257,7 @@ class CefPdfPrintCallback : public virtual CefBase { // class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefDownloadImageCallback : public virtual CefBase { +class CefDownloadImageCallback : public virtual CefBaseRefCounted { public: /// // Method that will be executed when the image download has completed. @@ -280,7 +280,7 @@ class CefDownloadImageCallback : public virtual CefBase { // comments. /// /*--cef(source=library)--*/ -class CefBrowserHost : public virtual CefBase { +class CefBrowserHost : public virtual CefBaseRefCounted { public: typedef cef_drag_operations_mask_t DragOperationsMask; typedef cef_file_dialog_mode_t FileDialogMode; diff --git a/src/include/cef_browser_process_handler.h b/src/include/cef_browser_process_handler.h index 9045d09a9..e65503fbd 100644 --- a/src/include/cef_browser_process_handler.h +++ b/src/include/cef_browser_process_handler.h @@ -48,7 +48,7 @@ // will be called on the browser process main thread unless otherwise indicated. /// /*--cef(source=client)--*/ -class CefBrowserProcessHandler : public virtual CefBase { +class CefBrowserProcessHandler : public virtual CefBaseRefCounted { public: /// // Called on the browser process UI thread immediately after the CEF context diff --git a/src/include/cef_callback.h b/src/include/cef_callback.h index e5efebd2d..5be51e941 100644 --- a/src/include/cef_callback.h +++ b/src/include/cef_callback.h @@ -44,7 +44,7 @@ // Generic callback interface used for asynchronous continuation. /// /*--cef(source=library)--*/ -class CefCallback : public virtual CefBase { +class CefCallback : public virtual CefBaseRefCounted { public: /// // Continue processing. @@ -63,7 +63,7 @@ class CefCallback : public virtual CefBase { // Generic callback interface used for asynchronous completion. /// /*--cef(source=client)--*/ -class CefCompletionCallback : public virtual CefBase { +class CefCompletionCallback : public virtual CefBaseRefCounted { public: /// // Method that will be called once the task is complete. diff --git a/src/include/cef_client.h b/src/include/cef_client.h index 81a67ebae..9421c7c08 100644 --- a/src/include/cef_client.h +++ b/src/include/cef_client.h @@ -59,7 +59,7 @@ // Implement this interface to provide handler implementations. /// /*--cef(source=client,no_debugct_check)--*/ -class CefClient : public virtual CefBase { +class CefClient : public virtual CefBaseRefCounted { public: /// // Return the handler for context menus. If no handler is provided the default diff --git a/src/include/cef_command_line.h b/src/include/cef_command_line.h index 96241cf43..5e581b6e0 100644 --- a/src/include/cef_command_line.h +++ b/src/include/cef_command_line.h @@ -53,7 +53,7 @@ // used before CefInitialize() is called. /// /*--cef(source=library,no_debugct_check)--*/ -class CefCommandLine : public virtual CefBase { +class CefCommandLine : public virtual CefBaseRefCounted { public: typedef std::vector ArgumentList; typedef std::map SwitchMap; diff --git a/src/include/cef_context_menu_handler.h b/src/include/cef_context_menu_handler.h index 2bad463b1..7619b8ed0 100644 --- a/src/include/cef_context_menu_handler.h +++ b/src/include/cef_context_menu_handler.h @@ -50,7 +50,7 @@ class CefContextMenuParams; // Callback interface used for continuation of custom context menu display. /// /*--cef(source=library)--*/ -class CefRunContextMenuCallback : public virtual CefBase { +class CefRunContextMenuCallback : public virtual CefBaseRefCounted { public: typedef cef_event_flags_t EventFlags; @@ -74,7 +74,7 @@ class CefRunContextMenuCallback : public virtual CefBase { // class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefContextMenuHandler : public virtual CefBase { +class CefContextMenuHandler : public virtual CefBaseRefCounted { public: typedef cef_event_flags_t EventFlags; @@ -139,7 +139,7 @@ class CefContextMenuHandler : public virtual CefBase { // can only be accessed on browser process the UI thread. /// /*--cef(source=library)--*/ -class CefContextMenuParams : public virtual CefBase { +class CefContextMenuParams : public virtual CefBaseRefCounted { public: typedef cef_context_menu_type_flags_t TypeFlags; typedef cef_context_menu_media_type_t MediaType; diff --git a/src/include/cef_cookie.h b/src/include/cef_cookie.h index b4cb33cb5..28f056d8a 100644 --- a/src/include/cef_cookie.h +++ b/src/include/cef_cookie.h @@ -51,7 +51,7 @@ class CefDeleteCookiesCallback; // any thread unless otherwise indicated. /// /*--cef(source=library,no_debugct_check)--*/ -class CefCookieManager : public virtual CefBase { +class CefCookieManager : public virtual CefBaseRefCounted { public: /// // Returns the global cookie manager. By default data will be stored at @@ -171,7 +171,7 @@ class CefCookieManager : public virtual CefBase { // will always be called on the IO thread. /// /*--cef(source=client)--*/ -class CefCookieVisitor : public virtual CefBase { +class CefCookieVisitor : public virtual CefBaseRefCounted { public: /// // Method that will be called once for each cookie. |count| is the 0-based @@ -191,7 +191,7 @@ class CefCookieVisitor : public virtual CefBase { // CefCookieManager::SetCookie(). /// /*--cef(source=client)--*/ -class CefSetCookieCallback : public virtual CefBase { +class CefSetCookieCallback : public virtual CefBaseRefCounted { public: /// // Method that will be called upon completion. |success| will be true if the @@ -207,7 +207,7 @@ class CefSetCookieCallback : public virtual CefBase { // CefCookieManager::DeleteCookies(). /// /*--cef(source=client)--*/ -class CefDeleteCookiesCallback : public virtual CefBase { +class CefDeleteCookiesCallback : public virtual CefBaseRefCounted { public: /// // Method that will be called upon completion. |num_deleted| will be the diff --git a/src/include/cef_dialog_handler.h b/src/include/cef_dialog_handler.h index 93cd5de7c..3ca98fb51 100644 --- a/src/include/cef_dialog_handler.h +++ b/src/include/cef_dialog_handler.h @@ -45,7 +45,7 @@ // Callback interface for asynchronous continuation of file dialog requests. /// /*--cef(source=library)--*/ -class CefFileDialogCallback : public virtual CefBase { +class CefFileDialogCallback : public virtual CefBaseRefCounted { public: /// // Continue the file selection. |selected_accept_filter| should be the 0-based @@ -72,7 +72,7 @@ class CefFileDialogCallback : public virtual CefBase { // will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefDialogHandler : public virtual CefBase { +class CefDialogHandler : public virtual CefBaseRefCounted { public: typedef cef_file_dialog_mode_t FileDialogMode; diff --git a/src/include/cef_display_handler.h b/src/include/cef_display_handler.h index 3f0646102..6147cc799 100644 --- a/src/include/cef_display_handler.h +++ b/src/include/cef_display_handler.h @@ -47,7 +47,7 @@ // The methods of this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefDisplayHandler : public virtual CefBase { +class CefDisplayHandler : public virtual CefBaseRefCounted { public: /// // Called when a frame's address has changed. diff --git a/src/include/cef_dom.h b/src/include/cef_dom.h index 65933b758..f653a074f 100644 --- a/src/include/cef_dom.h +++ b/src/include/cef_dom.h @@ -49,7 +49,7 @@ class CefDOMNode; // be called on the render process main thread. /// /*--cef(source=client)--*/ -class CefDOMVisitor : public virtual CefBase { +class CefDOMVisitor : public virtual CefBaseRefCounted { public: /// // Method executed for visiting the DOM. The document object passed to this @@ -68,7 +68,7 @@ class CefDOMVisitor : public virtual CefBase { // be called on the render process main thread thread. /// /*--cef(source=library)--*/ -class CefDOMDocument : public virtual CefBase { +class CefDOMDocument : public virtual CefBaseRefCounted { public: typedef cef_dom_document_type_t Type; @@ -164,7 +164,7 @@ class CefDOMDocument : public virtual CefBase { // called on the render process main thread. /// /*--cef(source=library)--*/ -class CefDOMNode : public virtual CefBase { +class CefDOMNode : public virtual CefBaseRefCounted { public: typedef std::map AttributeMap; typedef cef_dom_node_type_t Type; diff --git a/src/include/cef_download_handler.h b/src/include/cef_download_handler.h index 4f58af485..b32c248e5 100644 --- a/src/include/cef_download_handler.h +++ b/src/include/cef_download_handler.h @@ -47,7 +47,7 @@ // Callback interface used to asynchronously continue a download. /// /*--cef(source=library)--*/ -class CefBeforeDownloadCallback : public virtual CefBase { +class CefBeforeDownloadCallback : public virtual CefBaseRefCounted { public: /// // Call to continue the download. Set |download_path| to the full file path @@ -64,7 +64,7 @@ class CefBeforeDownloadCallback : public virtual CefBase { // Callback interface used to asynchronously cancel a download. /// /*--cef(source=library)--*/ -class CefDownloadItemCallback : public virtual CefBase { +class CefDownloadItemCallback : public virtual CefBaseRefCounted { public: /// // Call to cancel the download. @@ -91,7 +91,7 @@ class CefDownloadItemCallback : public virtual CefBase { // on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefDownloadHandler : public virtual CefBase { +class CefDownloadHandler : public virtual CefBaseRefCounted { public: /// // Called before a download begins. |suggested_name| is the suggested name for diff --git a/src/include/cef_download_item.h b/src/include/cef_download_item.h index 988ef6bde..b0ea797ff 100644 --- a/src/include/cef_download_item.h +++ b/src/include/cef_download_item.h @@ -44,7 +44,7 @@ // Class used to represent a download item. /// /*--cef(source=library)--*/ -class CefDownloadItem : public virtual CefBase { +class CefDownloadItem : public virtual CefBaseRefCounted { public: /// // Returns true if this object is valid. Do not call any other methods if this diff --git a/src/include/cef_drag_data.h b/src/include/cef_drag_data.h index 8b4602b1b..e3c9c2df9 100644 --- a/src/include/cef_drag_data.h +++ b/src/include/cef_drag_data.h @@ -48,7 +48,7 @@ // on any thread. /// /*--cef(source=library)--*/ -class CefDragData : public virtual CefBase { +class CefDragData : public virtual CefBaseRefCounted { public: /// // Create a new CefDragData object. diff --git a/src/include/cef_drag_handler.h b/src/include/cef_drag_handler.h index 3e26f9755..aee013668 100644 --- a/src/include/cef_drag_handler.h +++ b/src/include/cef_drag_handler.h @@ -47,7 +47,7 @@ // this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefDragHandler : public virtual CefBase { +class CefDragHandler : public virtual CefBaseRefCounted { public: typedef cef_drag_operations_mask_t DragOperationsMask; diff --git a/src/include/cef_find_handler.h b/src/include/cef_find_handler.h index 410cf5fec..a9cac0a1e 100644 --- a/src/include/cef_find_handler.h +++ b/src/include/cef_find_handler.h @@ -46,7 +46,7 @@ // methods of this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefFindHandler : public virtual CefBase { +class CefFindHandler : public virtual CefBaseRefCounted { public: /// // Called to report find results returned by CefBrowserHost::Find(). diff --git a/src/include/cef_focus_handler.h b/src/include/cef_focus_handler.h index 1d91c42ab..27352fa2b 100644 --- a/src/include/cef_focus_handler.h +++ b/src/include/cef_focus_handler.h @@ -48,7 +48,7 @@ // this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefFocusHandler : public virtual CefBase { +class CefFocusHandler : public virtual CefBaseRefCounted { public: typedef cef_focus_source_t FocusSource; diff --git a/src/include/cef_frame.h b/src/include/cef_frame.h index 1ea172f1b..bcf680d63 100644 --- a/src/include/cef_frame.h +++ b/src/include/cef_frame.h @@ -54,7 +54,7 @@ class CefV8Context; // methods of this class may only be called on the main thread. /// /*--cef(source=library)--*/ -class CefFrame : public virtual CefBase { +class CefFrame : public virtual CefBaseRefCounted { public: /// // True if this object is currently attached to a valid frame. diff --git a/src/include/cef_geolocation.h b/src/include/cef_geolocation.h index 69c08779c..32005a430 100644 --- a/src/include/cef_geolocation.h +++ b/src/include/cef_geolocation.h @@ -45,7 +45,7 @@ // class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefGetGeolocationCallback : public virtual CefBase { +class CefGetGeolocationCallback : public virtual CefBaseRefCounted { public: /// // Called with the 'best available' location information or, if the location diff --git a/src/include/cef_geolocation_handler.h b/src/include/cef_geolocation_handler.h index dda52834a..b2a722cea 100644 --- a/src/include/cef_geolocation_handler.h +++ b/src/include/cef_geolocation_handler.h @@ -46,7 +46,7 @@ // permission requests. /// /*--cef(source=library)--*/ -class CefGeolocationCallback : public virtual CefBase { +class CefGeolocationCallback : public virtual CefBaseRefCounted { public: /// // Call to allow or deny geolocation access. @@ -62,7 +62,7 @@ class CefGeolocationCallback : public virtual CefBase { // thread. /// /*--cef(source=client)--*/ -class CefGeolocationHandler : public virtual CefBase { +class CefGeolocationHandler : public virtual CefBaseRefCounted { public: /// // Called when a page requests permission to access geolocation information. diff --git a/src/include/cef_image.h b/src/include/cef_image.h index 170a23758..71f69ab61 100644 --- a/src/include/cef_image.h +++ b/src/include/cef_image.h @@ -50,7 +50,7 @@ // be called on the browser process UI thread. /// /*--cef(source=library)--*/ -class CefImage : public virtual CefBase { +class CefImage : public virtual CefBaseRefCounted { public: /// // Create a new CefImage. It will initially be empty. Use the Add*() methods diff --git a/src/include/cef_jsdialog_handler.h b/src/include/cef_jsdialog_handler.h index fecaa7f78..4596430cc 100644 --- a/src/include/cef_jsdialog_handler.h +++ b/src/include/cef_jsdialog_handler.h @@ -46,7 +46,7 @@ // requests. /// /*--cef(source=library)--*/ -class CefJSDialogCallback : public virtual CefBase { +class CefJSDialogCallback : public virtual CefBaseRefCounted { public: /// // Continue the JS dialog request. Set |success| to true if the OK button was @@ -63,7 +63,7 @@ class CefJSDialogCallback : public virtual CefBase { // methods of this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefJSDialogHandler : public virtual CefBase { +class CefJSDialogHandler : public virtual CefBaseRefCounted { public: typedef cef_jsdialog_type_t JSDialogType; diff --git a/src/include/cef_keyboard_handler.h b/src/include/cef_keyboard_handler.h index 0346aa4ac..cd4c4d0ef 100644 --- a/src/include/cef_keyboard_handler.h +++ b/src/include/cef_keyboard_handler.h @@ -46,7 +46,7 @@ // methods of this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefKeyboardHandler : public virtual CefBase { +class CefKeyboardHandler : public virtual CefBaseRefCounted { public: /// // Called before a keyboard event is sent to the renderer. |event| contains diff --git a/src/include/cef_life_span_handler.h b/src/include/cef_life_span_handler.h index 6967e3e8e..b1acc4d7c 100644 --- a/src/include/cef_life_span_handler.h +++ b/src/include/cef_life_span_handler.h @@ -49,7 +49,7 @@ class CefClient; // indicated. /// /*--cef(source=client)--*/ -class CefLifeSpanHandler : public virtual CefBase { +class CefLifeSpanHandler : public virtual CefBaseRefCounted { public: typedef cef_window_open_disposition_t WindowOpenDisposition; diff --git a/src/include/cef_load_handler.h b/src/include/cef_load_handler.h index 1494a844b..99696ed89 100644 --- a/src/include/cef_load_handler.h +++ b/src/include/cef_load_handler.h @@ -48,7 +48,7 @@ // render process main thread (TID_RENDERER). /// /*--cef(source=client)--*/ -class CefLoadHandler : public virtual CefBase { +class CefLoadHandler : public virtual CefBaseRefCounted { public: typedef cef_errorcode_t ErrorCode; typedef cef_transition_type_t TransitionType; diff --git a/src/include/cef_menu_model.h b/src/include/cef_menu_model.h index beb95fb75..b039119ac 100644 --- a/src/include/cef_menu_model.h +++ b/src/include/cef_menu_model.h @@ -48,7 +48,7 @@ // this class can only be accessed on the browser process the UI thread. /// /*--cef(source=library)--*/ -class CefMenuModel : public virtual CefBase { +class CefMenuModel : public virtual CefBaseRefCounted { public: typedef cef_menu_item_type_t MenuItemType; @@ -59,6 +59,12 @@ class CefMenuModel : public virtual CefBase { static CefRefPtr CreateMenuModel( CefRefPtr delegate); + /// + // Returns true if this menu is a submenu. + /// + /*--cef()--*/ + virtual bool IsSubMenu() =0; + /// // Clears the menu. Returns true on success. /// @@ -405,6 +411,85 @@ class CefMenuModel : public virtual CefBase { bool& shift_pressed, bool& ctrl_pressed, bool& alt_pressed) =0; + + /// + // Set the explicit color for |command_id| and |color_type| to |color|. + // Specify a |color| value of 0 to remove the explicit color. If no explicit + // color or default color is set for |color_type| then the system color will + // be used. Returns true on success. + /// + /*--cef()--*/ + virtual bool SetColor(int command_id, + cef_menu_color_type_t color_type, + cef_color_t color) =0; + + /// + // Set the explicit color for |command_id| and |index| to |color|. Specify a + // |color| value of 0 to remove the explicit color. Specify an |index| value + // of -1 to set the default color for items that do not have an explicit + // color set. If no explicit color or default color is set for |color_type| + // then the system color will be used. Returns true on success. + /// + /*--cef()--*/ + virtual bool SetColorAt(int index, + cef_menu_color_type_t color_type, + cef_color_t color) =0; + + /// + // Returns in |color| the color that was explicitly set for |command_id| and + // |color_type|. If a color was not set then 0 will be returned in |color|. + // Returns true on success. + /// + /*--cef()--*/ + virtual bool GetColor(int command_id, + cef_menu_color_type_t color_type, + cef_color_t& color) =0; + + /// + // Returns in |color| the color that was explicitly set for |command_id| and + // |color_type|. Specify an |index| value of -1 to return the default color + // in |color|. If a color was not set then 0 will be returned in |color|. + // Returns true on success. + /// + /*--cef()--*/ + virtual bool GetColorAt(int index, + cef_menu_color_type_t color_type, + cef_color_t& color) =0; + + /// + // Sets the font list for the specified |command_id|. If |font_list| is empty + // the system font will be used. Returns true on success. The format is + // ",[STYLES] ", where: + // - FONT_FAMILY_LIST is a comma-separated list of font family names, + // - STYLES is an optional space-separated list of style names (case-sensitive + // "Bold" and "Italic" are supported), and + // - SIZE is an integer font size in pixels with the suffix "px". + // + // Here are examples of valid font description strings: + // - "Arial, Helvetica, Bold Italic 14px" + // - "Arial, 14px" + /// + /*--cef(optional_param=font_list)--*/ + virtual bool SetFontList(int command_id, + const CefString& font_list) =0; + + /// + // Sets the font list for the specified |index|. Specify an |index| value of + // -1 to set the default font. If |font_list| is empty the system font will + // be used. Returns true on success. The format is + // ",[STYLES] ", where: + // - FONT_FAMILY_LIST is a comma-separated list of font family names, + // - STYLES is an optional space-separated list of style names (case-sensitive + // "Bold" and "Italic" are supported), and + // - SIZE is an integer font size in pixels with the suffix "px". + // + // Here are examples of valid font description strings: + // - "Arial, Helvetica, Bold Italic 14px" + // - "Arial, 14px" + /// + /*--cef(optional_param=font_list)--*/ + virtual bool SetFontListAt(int index, + const CefString& font_list) =0; }; #endif // CEF_INCLUDE_CEF_MENU_MODEL_H_ diff --git a/src/include/cef_menu_model_delegate.h b/src/include/cef_menu_model_delegate.h index 84ebae18e..d05b580a6 100644 --- a/src/include/cef_menu_model_delegate.h +++ b/src/include/cef_menu_model_delegate.h @@ -48,7 +48,7 @@ class CefMenuModel; // indicated. /// /*--cef(source=client)--*/ -class CefMenuModelDelegate : public virtual CefBase { +class CefMenuModelDelegate : public virtual CefBaseRefCounted { public: /// // Perform the action associated with the specified |command_id| and @@ -59,6 +59,30 @@ class CefMenuModelDelegate : public virtual CefBase { int command_id, cef_event_flags_t event_flags) =0; + /// + // Called when the user moves the mouse outside the menu and over the owning + // window. + /// + /*--cef()--*/ + virtual void MouseOutsideMenu(CefRefPtr menu_model, + const CefPoint& screen_point) {} + + /// + // Called on unhandled open submenu keyboard commands. |is_rtl| will be true + // if the menu is displaying a right-to-left language. + /// + /*--cef()--*/ + virtual void UnhandledOpenSubmenu(CefRefPtr menu_model, + bool is_rtl) {} + + /// + // Called on unhandled close submenu keyboard commands. |is_rtl| will be true + // if the menu is displaying a right-to-left language. + /// + /*--cef()--*/ + virtual void UnhandledCloseSubmenu(CefRefPtr menu_model, + bool is_rtl) {} + /// // The menu is about to show. /// diff --git a/src/include/cef_navigation_entry.h b/src/include/cef_navigation_entry.h index 6dc3a4c57..9c86a338a 100644 --- a/src/include/cef_navigation_entry.h +++ b/src/include/cef_navigation_entry.h @@ -45,7 +45,7 @@ // Class used to represent an entry in navigation history. /// /*--cef(source=library)--*/ -class CefNavigationEntry : public virtual CefBase { +class CefNavigationEntry : public virtual CefBaseRefCounted { public: typedef cef_transition_type_t TransitionType; diff --git a/src/include/cef_print_handler.h b/src/include/cef_print_handler.h index cac16244b..50519c4fe 100644 --- a/src/include/cef_print_handler.h +++ b/src/include/cef_print_handler.h @@ -46,7 +46,7 @@ // Callback interface for asynchronous continuation of print dialog requests. /// /*--cef(source=library)--*/ -class CefPrintDialogCallback : public virtual CefBase { +class CefPrintDialogCallback : public virtual CefBaseRefCounted { public: /// // Continue printing with the specified |settings|. @@ -65,7 +65,7 @@ class CefPrintDialogCallback : public virtual CefBase { // Callback interface for asynchronous continuation of print job requests. /// /*--cef(source=library)--*/ -class CefPrintJobCallback : public virtual CefBase { +class CefPrintJobCallback : public virtual CefBaseRefCounted { public: /// // Indicate completion of the print job. @@ -80,7 +80,7 @@ class CefPrintJobCallback : public virtual CefBase { // class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefPrintHandler : public virtual CefBase { +class CefPrintHandler : public virtual CefBaseRefCounted { public: /// // Called when printing has started for the specified |browser|. This method diff --git a/src/include/cef_print_settings.h b/src/include/cef_print_settings.h index 709d36b39..0fe2a0ba0 100644 --- a/src/include/cef_print_settings.h +++ b/src/include/cef_print_settings.h @@ -46,7 +46,7 @@ // Class representing print settings. /// /*--cef(source=library)--*/ -class CefPrintSettings : public virtual CefBase { +class CefPrintSettings : public virtual CefBaseRefCounted { public: typedef cef_color_model_t ColorModel; typedef cef_duplex_mode_t DuplexMode; diff --git a/src/include/cef_process_message.h b/src/include/cef_process_message.h index 1e27bd681..a6b6a18aa 100644 --- a/src/include/cef_process_message.h +++ b/src/include/cef_process_message.h @@ -47,7 +47,7 @@ typedef cef_process_id_t CefProcessId; // Class representing a message. Can be used on any process and thread. /// /*--cef(source=library)--*/ -class CefProcessMessage : public virtual CefBase { +class CefProcessMessage : public virtual CefBaseRefCounted { public: /// // Create a new CefProcessMessage object with the specified name. diff --git a/src/include/cef_render_handler.h b/src/include/cef_render_handler.h index 0f857b07d..9c253535e 100644 --- a/src/include/cef_render_handler.h +++ b/src/include/cef_render_handler.h @@ -49,7 +49,7 @@ // The methods of this class will be called on the UI thread. /// /*--cef(source=client)--*/ -class CefRenderHandler : public virtual CefBase { +class CefRenderHandler : public virtual CefBaseRefCounted { public: typedef cef_cursor_type_t CursorType; typedef cef_drag_operations_mask_t DragOperation; diff --git a/src/include/cef_render_process_handler.h b/src/include/cef_render_process_handler.h index 98ab391bc..ecf6fc59e 100644 --- a/src/include/cef_render_process_handler.h +++ b/src/include/cef_render_process_handler.h @@ -53,7 +53,7 @@ // otherwise indicated. /// /*--cef(source=client)--*/ -class CefRenderProcessHandler : public virtual CefBase { +class CefRenderProcessHandler : public virtual CefBaseRefCounted { public: typedef cef_navigation_type_t NavigationType; diff --git a/src/include/cef_request.h b/src/include/cef_request.h index a4626aa02..8d48f41d9 100644 --- a/src/include/cef_request.h +++ b/src/include/cef_request.h @@ -50,7 +50,7 @@ class CefPostDataElement; // called on any thread. /// /*--cef(source=library,no_debugct_check)--*/ -class CefRequest : public virtual CefBase { +class CefRequest : public virtual CefBaseRefCounted { public: typedef std::multimap HeaderMap; typedef cef_referrer_policy_t ReferrerPolicy; @@ -207,7 +207,7 @@ class CefRequest : public virtual CefBase { // class may be called on any thread. /// /*--cef(source=library,no_debugct_check)--*/ -class CefPostData : public virtual CefBase { +class CefPostData : public virtual CefBaseRefCounted { public: typedef std::vector > ElementVector; @@ -270,7 +270,7 @@ class CefPostData : public virtual CefBase { // methods of this class may be called on any thread. /// /*--cef(source=library,no_debugct_check)--*/ -class CefPostDataElement : public virtual CefBase { +class CefPostDataElement : public virtual CefBaseRefCounted { public: /// // Post data elements may represent either bytes or files. diff --git a/src/include/cef_request_context.h b/src/include/cef_request_context.h index 8892d5f7f..3f6d8fd4c 100644 --- a/src/include/cef_request_context.h +++ b/src/include/cef_request_context.h @@ -52,7 +52,7 @@ class CefSchemeHandlerFactory; // Callback interface for CefRequestContext::ResolveHost. /// /*--cef(source=client)--*/ -class CefResolveCallback : public virtual CefBase { +class CefResolveCallback : public virtual CefBaseRefCounted { public: /// // Called after the ResolveHost request has completed. |result| will be the @@ -83,7 +83,7 @@ class CefResolveCallback : public virtual CefBase { // all other request context objects will be ignored. /// /*--cef(source=library,no_debugct_check)--*/ -class CefRequestContext : public virtual CefBase { +class CefRequestContext : public virtual CefBaseRefCounted { public: /// // Returns the global context object. diff --git a/src/include/cef_request_context_handler.h b/src/include/cef_request_context_handler.h index 6996d9ede..26864e88b 100644 --- a/src/include/cef_request_context_handler.h +++ b/src/include/cef_request_context_handler.h @@ -48,7 +48,7 @@ // been destroyed. /// /*--cef(source=client,no_debugct_check)--*/ -class CefRequestContextHandler : public virtual CefBase { +class CefRequestContextHandler : public virtual CefBaseRefCounted { public: typedef cef_plugin_policy_t PluginPolicy; diff --git a/src/include/cef_request_handler.h b/src/include/cef_request_handler.h index 69334397a..c44a3dfaa 100644 --- a/src/include/cef_request_handler.h +++ b/src/include/cef_request_handler.h @@ -55,7 +55,7 @@ // Callback interface used for asynchronous continuation of url requests. /// /*--cef(source=library)--*/ -class CefRequestCallback : public virtual CefBase { +class CefRequestCallback : public virtual CefBaseRefCounted { public: /// // Continue the url request. If |allow| is true the request will be continued. @@ -76,7 +76,7 @@ class CefRequestCallback : public virtual CefBase { // Callback interface used to select a client certificate for authentication. /// /*--cef(source=library)--*/ -class CefSelectClientCertificateCallback : public virtual CefBase { +class CefSelectClientCertificateCallback : public virtual CefBaseRefCounted { public: /// // Chooses the specified certificate for client certificate authentication. @@ -92,7 +92,7 @@ class CefSelectClientCertificateCallback : public virtual CefBase { // methods of this class will be called on the thread indicated. /// /*--cef(source=client)--*/ -class CefRequestHandler : public virtual CefBase { +class CefRequestHandler : public virtual CefBaseRefCounted { public: typedef cef_return_value_t ReturnValue; typedef cef_termination_status_t TerminationStatus; diff --git a/src/include/cef_resource_bundle.h b/src/include/cef_resource_bundle.h index 3b064ffec..3fda8998e 100644 --- a/src/include/cef_resource_bundle.h +++ b/src/include/cef_resource_bundle.h @@ -48,7 +48,7 @@ // on any thread unless otherwise indicated. /// /*--cef(source=library,no_debugct_check)--*/ -class CefResourceBundle : public virtual CefBase { +class CefResourceBundle : public virtual CefBaseRefCounted { public: typedef cef_scale_factor_t ScaleFactor; diff --git a/src/include/cef_resource_bundle_handler.h b/src/include/cef_resource_bundle_handler.h index afc46da74..932ef9f5b 100644 --- a/src/include/cef_resource_bundle_handler.h +++ b/src/include/cef_resource_bundle_handler.h @@ -46,7 +46,7 @@ // this class may be called on multiple threads. /// /*--cef(source=client)--*/ -class CefResourceBundleHandler : public virtual CefBase { +class CefResourceBundleHandler : public virtual CefBaseRefCounted { public: typedef cef_scale_factor_t ScaleFactor; diff --git a/src/include/cef_resource_handler.h b/src/include/cef_resource_handler.h index 19913c756..abe7196dc 100644 --- a/src/include/cef_resource_handler.h +++ b/src/include/cef_resource_handler.h @@ -50,7 +50,7 @@ // this class will always be called on the IO thread. /// /*--cef(source=client)--*/ -class CefResourceHandler : public virtual CefBase { +class CefResourceHandler : public virtual CefBaseRefCounted { public: /// // Begin processing the request. To handle the request return true and call diff --git a/src/include/cef_response.h b/src/include/cef_response.h index 6fcb4e236..cd7356efd 100644 --- a/src/include/cef_response.h +++ b/src/include/cef_response.h @@ -46,7 +46,7 @@ // called on any thread. /// /*--cef(source=library,no_debugct_check)--*/ -class CefResponse : public virtual CefBase { +class CefResponse : public virtual CefBaseRefCounted { public: typedef std::multimap HeaderMap; diff --git a/src/include/cef_response_filter.h b/src/include/cef_response_filter.h index 55e667770..58c978ea1 100644 --- a/src/include/cef_response_filter.h +++ b/src/include/cef_response_filter.h @@ -45,7 +45,7 @@ // this class will be called on the browser process IO thread. /// /*--cef(source=client)--*/ -class CefResponseFilter : public virtual CefBase { +class CefResponseFilter : public virtual CefBaseRefCounted { public: typedef cef_response_filter_status_t FilterStatus; diff --git a/src/include/cef_scheme.h b/src/include/cef_scheme.h index a966a9a79..d4b1ac0c5 100644 --- a/src/include/cef_scheme.h +++ b/src/include/cef_scheme.h @@ -82,7 +82,7 @@ bool CefClearSchemeHandlerFactories(); // Class that manages custom scheme registrations. /// /*--cef(source=library)--*/ -class CefSchemeRegistrar : public virtual CefBase { +class CefSchemeRegistrar : public CefBaseScoped { public: /// // Register a custom scheme. This method should not be called for the built-in @@ -111,9 +111,9 @@ class CefSchemeRegistrar : public virtual CefBase { // as-is. For example, "scheme:///some%20text" will remain the same. // Non-standard scheme URLs cannot be used as a target for form submission. // - // If |is_local| is true the scheme will be treated as local (i.e., with the - // same security rules as those applied to "file" URLs). Normal pages cannot - // link to or access local URLs. Also, by default, local URLs can only perform + // If |is_local| is true the scheme will be treated with the same security + // rules as those applied to "file" URLs. Normal pages cannot link to or + // access local URLs. Also, by default, local URLs can only perform // XMLHttpRequest calls to the same URL (origin + path) that originated the // request. To allow XMLHttpRequest calls from a local URL to other URLs with // the same origin set the CefSettings.file_access_from_file_urls_allowed @@ -121,10 +121,19 @@ class CefSchemeRegistrar : public virtual CefBase { // origins set the CefSettings.universal_access_from_file_urls_allowed value // to true. // - // If |is_display_isolated| is true the scheme will be treated as display- - // isolated. This means that pages cannot display these URLs unless they are - // from the same scheme. For example, pages in another origin cannot create - // iframes or hyperlinks to URLs with this scheme. + // If |is_display_isolated| is true the scheme can only be displayed from + // other content hosted with the same scheme. For example, pages in other + // origins cannot create iframes or hyperlinks to URLs with the scheme. For + // schemes that must be accessible from other schemes set this value to false, + // set |is_cors_enabled| to true, and use CORS "Access-Control-Allow-Origin" + // headers to further restrict access. + // + // If |is_secure| is true the scheme will be treated with the same security + // rules as those applied to "https" URLs. For example, loading this scheme + // from other secure schemes will not trigger mixed content warnings. + // + // If |is_cors_enabled| is true the scheme that can be sent CORS requests. + // This value should be true in most cases where |is_standard| is true. // // This function may be called on any thread. It should only be called once // per unique |scheme_name| value. If |scheme_name| is already registered or @@ -134,7 +143,9 @@ class CefSchemeRegistrar : public virtual CefBase { virtual bool AddCustomScheme(const CefString& scheme_name, bool is_standard, bool is_local, - bool is_display_isolated) =0; + bool is_display_isolated, + bool is_secure, + bool is_cors_enabled) =0; }; @@ -143,7 +154,7 @@ class CefSchemeRegistrar : public virtual CefBase { // The methods of this class will always be called on the IO thread. /// /*--cef(source=client)--*/ -class CefSchemeHandlerFactory : public virtual CefBase { +class CefSchemeHandlerFactory : public virtual CefBaseRefCounted { public: /// // Return a new resource handler instance to handle the request or an empty diff --git a/src/include/cef_ssl_info.h b/src/include/cef_ssl_info.h index 732171889..db075cabc 100644 --- a/src/include/cef_ssl_info.h +++ b/src/include/cef_ssl_info.h @@ -47,7 +47,7 @@ // Class representing SSL information. /// /*--cef(source=library)--*/ -class CefSSLInfo : public virtual CefBase { +class CefSSLInfo : public virtual CefBaseRefCounted { public: /// // Returns a bitmask containing any and all problems verifying the server diff --git a/src/include/cef_ssl_status.h b/src/include/cef_ssl_status.h index 614a607d5..3a5f39501 100644 --- a/src/include/cef_ssl_status.h +++ b/src/include/cef_ssl_status.h @@ -46,7 +46,7 @@ // Class representing the SSL information for a navigation entry. /// /*--cef(source=library)--*/ -class CefSSLStatus : public virtual CefBase { +class CefSSLStatus : public virtual CefBaseRefCounted { public: /// // Returns true if the status is related to a secure SSL/TLS connection. diff --git a/src/include/cef_stream.h b/src/include/cef_stream.h index 3d0633c25..81b0b9164 100644 --- a/src/include/cef_stream.h +++ b/src/include/cef_stream.h @@ -44,7 +44,7 @@ // methods of this class may be called on any thread. /// /*--cef(source=client)--*/ -class CefReadHandler : public virtual CefBase { +class CefReadHandler : public virtual CefBaseRefCounted { public: /// // Read raw binary data. @@ -87,7 +87,7 @@ class CefReadHandler : public virtual CefBase { // called on any thread. /// /*--cef(source=library)--*/ -class CefStreamReader : public virtual CefBase { +class CefStreamReader : public virtual CefBaseRefCounted { public: /// // Create a new CefStreamReader object from a file. @@ -147,7 +147,7 @@ class CefStreamReader : public virtual CefBase { // methods of this class may be called on any thread. /// /*--cef(source=client)--*/ -class CefWriteHandler : public virtual CefBase { +class CefWriteHandler : public virtual CefBaseRefCounted { public: /// // Write raw binary data. @@ -190,7 +190,7 @@ class CefWriteHandler : public virtual CefBase { // on any thread. /// /*--cef(source=library)--*/ -class CefStreamWriter : public virtual CefBase { +class CefStreamWriter : public virtual CefBaseRefCounted { public: /// // Create a new CefStreamWriter object for a file. diff --git a/src/include/cef_string_visitor.h b/src/include/cef_string_visitor.h index 549371473..36a97ba35 100644 --- a/src/include/cef_string_visitor.h +++ b/src/include/cef_string_visitor.h @@ -43,7 +43,7 @@ // Implement this interface to receive string values asynchronously. /// /*--cef(source=client)--*/ -class CefStringVisitor : public virtual CefBase { +class CefStringVisitor : public virtual CefBaseRefCounted { public: /// // Method that will be executed. diff --git a/src/include/cef_task.h b/src/include/cef_task.h index 0ecaa7526..ea3b2d96b 100644 --- a/src/include/cef_task.h +++ b/src/include/cef_task.h @@ -50,7 +50,7 @@ typedef cef_thread_id_t CefThreadId; // task object destructor. /// /*--cef(source=client)--*/ -class CefTask : public virtual CefBase { +class CefTask : public virtual CefBaseRefCounted { public: /// // Method that will be executed on the target thread. @@ -69,7 +69,7 @@ class CefTask : public virtual CefBase { // other CEF threads as appropriate (for example, V8 WebWorker threads). /// /*--cef(source=library)--*/ -class CefTaskRunner : public virtual CefBase { +class CefTaskRunner : public virtual CefBaseRefCounted { public: /// // Returns the task runner for the current thread. Only CEF threads will have diff --git a/src/include/cef_thread.h b/src/include/cef_thread.h index a0e78c493..dfe652d2e 100644 --- a/src/include/cef_thread.h +++ b/src/include/cef_thread.h @@ -53,7 +53,7 @@ // see cef_task.h for details. /// /*--cef(source=library)--*/ -class CefThread : public CefBase { +class CefThread : public CefBaseRefCounted { public: /// // Create and start a new thread. This method does not block waiting for the diff --git a/src/include/cef_trace.h b/src/include/cef_trace.h index 5b977c6e5..fa7f62781 100644 --- a/src/include/cef_trace.h +++ b/src/include/cef_trace.h @@ -49,7 +49,7 @@ // The methods of this class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefEndTracingCallback : public virtual CefBase { +class CefEndTracingCallback : public virtual CefBaseRefCounted { public: /// // Called after all processes have sent their trace data. |tracing_file| is diff --git a/src/include/cef_urlrequest.h b/src/include/cef_urlrequest.h index 5b58b6349..23bc821ec 100644 --- a/src/include/cef_urlrequest.h +++ b/src/include/cef_urlrequest.h @@ -54,7 +54,7 @@ class CefURLRequestClient; // on the same thread that created it. /// /*--cef(source=library)--*/ -class CefURLRequest : public virtual CefBase { +class CefURLRequest : public virtual CefBaseRefCounted { public: typedef cef_urlrequest_status_t Status; typedef cef_errorcode_t ErrorCode; @@ -127,7 +127,7 @@ class CefURLRequest : public virtual CefBase { // request unless otherwise documented. /// /*--cef(source=client)--*/ -class CefURLRequestClient : public virtual CefBase { +class CefURLRequestClient : public virtual CefBaseRefCounted { public: /// // Notifies the client that the request has completed. Use the diff --git a/src/include/cef_v8.h b/src/include/cef_v8.h index f33090a46..2ec4feb8e 100644 --- a/src/include/cef_v8.h +++ b/src/include/cef_v8.h @@ -124,7 +124,7 @@ bool CefRegisterExtension(const CefString& extension_name, // the CefV8Context::GetTaskRunner() method. /// /*--cef(source=library)--*/ -class CefV8Context : public virtual CefBase { +class CefV8Context : public virtual CefBaseRefCounted { public: /// // Returns the current (top) context object in the V8 context stack. @@ -229,7 +229,7 @@ typedef std::vector > CefV8ValueList; // of this class will be called on the thread associated with the V8 function. /// /*--cef(source=client)--*/ -class CefV8Handler : public virtual CefBase { +class CefV8Handler : public virtual CefBaseRefCounted { public: /// // Handle execution of the function identified by |name|. |object| is the @@ -252,7 +252,7 @@ class CefV8Handler : public virtual CefBase { // of this class will be called on the thread associated with the V8 accessor. /// /*--cef(source=client)--*/ -class CefV8Accessor : public virtual CefBase { +class CefV8Accessor : public virtual CefBaseRefCounted { public: /// // Handle retrieval the accessor value identified by |name|. |object| is the @@ -290,7 +290,7 @@ class CefV8Accessor : public virtual CefBase { // by integer. /// /*--cef(source=client)--*/ -class CefV8Interceptor : public virtual CefBase { +class CefV8Interceptor : public virtual CefBaseRefCounted { public: /// // Handle retrieval of the interceptor value identified by |name|. |object| is @@ -354,7 +354,7 @@ class CefV8Interceptor : public virtual CefBase { // any render process thread. /// /*--cef(source=library)--*/ -class CefV8Exception : public virtual CefBase { +class CefV8Exception : public virtual CefBaseRefCounted { public: /// // Returns the exception message. @@ -419,7 +419,7 @@ class CefV8Exception : public virtual CefBase { // the CefV8Context::GetTaskRunner() method. /// /*--cef(source=library)--*/ -class CefV8Value : public virtual CefBase { +class CefV8Value : public virtual CefBaseRefCounted { public: typedef cef_v8_accesscontrol_t AccessControl; typedef cef_v8_propertyattribute_t PropertyAttribute; @@ -759,13 +759,13 @@ class CefV8Value : public virtual CefBase { // on user created objects. /// /*--cef(optional_param=user_data)--*/ - virtual bool SetUserData(CefRefPtr user_data) =0; + virtual bool SetUserData(CefRefPtr user_data) =0; /// // Returns the user data, if any, assigned to this object. /// /*--cef()--*/ - virtual CefRefPtr GetUserData() =0; + virtual CefRefPtr GetUserData() =0; /// // Returns the amount of externally allocated memory registered for the @@ -850,7 +850,7 @@ class CefV8Value : public virtual CefBase { // retrieved via the CefV8Context::GetTaskRunner() method. /// /*--cef(source=library)--*/ -class CefV8StackTrace : public virtual CefBase { +class CefV8StackTrace : public virtual CefBaseRefCounted { public: /// // Returns the stack trace for the currently active context. |frame_limit| is @@ -888,7 +888,7 @@ class CefV8StackTrace : public virtual CefBase { // retrieved via the CefV8Context::GetTaskRunner() method. /// /*--cef(source=library)--*/ -class CefV8StackFrame : public virtual CefBase { +class CefV8StackFrame : public virtual CefBaseRefCounted { public: /// // Returns true if the underlying handle is valid and it can be accessed on diff --git a/src/include/cef_values.h b/src/include/cef_values.h index 86fa40ef6..6bbd1e27d 100644 --- a/src/include/cef_values.h +++ b/src/include/cef_values.h @@ -53,7 +53,7 @@ typedef cef_value_type_t CefValueType; // process and thread. /// /*--cef(source=library)--*/ -class CefValue : public virtual CefBase { +class CefValue : public virtual CefBaseRefCounted { public: /// // Creates a new object. @@ -234,7 +234,7 @@ class CefValue : public virtual CefBase { // Class representing a binary value. Can be used on any process and thread. /// /*--cef(source=library)--*/ -class CefBinaryValue : public virtual CefBase { +class CefBinaryValue : public virtual CefBaseRefCounted { public: /// // Creates a new object that is not owned by any other object. The specified @@ -300,7 +300,7 @@ class CefBinaryValue : public virtual CefBase { // Class representing a dictionary value. Can be used on any process and thread. /// /*--cef(source=library)--*/ -class CefDictionaryValue : public virtual CefBase { +class CefDictionaryValue : public virtual CefBaseRefCounted { public: typedef std::vector KeyList; @@ -533,7 +533,7 @@ class CefDictionaryValue : public virtual CefBase { // Class representing a list value. Can be used on any process and thread. /// /*--cef(source=library)--*/ -class CefListValue : public virtual CefBase { +class CefListValue : public virtual CefBaseRefCounted { public: /// // Creates a new object that is not owned by any other object. diff --git a/src/include/cef_waitable_event.h b/src/include/cef_waitable_event.h index 4a4e9950c..070ca6813 100644 --- a/src/include/cef_waitable_event.h +++ b/src/include/cef_waitable_event.h @@ -52,7 +52,7 @@ // process UI or IO threads. /// /*--cef(source=library)--*/ -class CefWaitableEvent : public CefBase { +class CefWaitableEvent : public CefBaseRefCounted { public: /// // Create a new waitable event. If |automatic_reset| is true then the event diff --git a/src/include/cef_web_plugin.h b/src/include/cef_web_plugin.h index 731be7b94..3eea54130 100644 --- a/src/include/cef_web_plugin.h +++ b/src/include/cef_web_plugin.h @@ -45,7 +45,7 @@ class CefBrowser; // Information about a specific web plugin. /// /*--cef(source=library)--*/ -class CefWebPluginInfo : public virtual CefBase { +class CefWebPluginInfo : public virtual CefBaseRefCounted { public: /// // Returns the plugin name (i.e. Flash). @@ -77,7 +77,7 @@ class CefWebPluginInfo : public virtual CefBase { // this class will be called on the browser process UI thread. /// /*--cef(source=client)--*/ -class CefWebPluginInfoVisitor : public virtual CefBase { +class CefWebPluginInfoVisitor : public virtual CefBaseRefCounted { public: /// // Method that will be called once for each plugin. |count| is the 0-based @@ -124,7 +124,7 @@ void CefRegisterWebPluginCrash(const CefString& path); // of this class will be called on the browser process IO thread. /// /*--cef(source=client)--*/ -class CefWebPluginUnstableCallback : public virtual CefBase { +class CefWebPluginUnstableCallback : public virtual CefBaseRefCounted { public: /// // Method that will be called for the requested plugin. |unstable| will be @@ -150,7 +150,7 @@ void CefIsWebPluginUnstable(const CefString& path, // UI thread. /// /*--cef(source=client)--*/ -class CefRegisterCdmCallback : public virtual CefBase { +class CefRegisterCdmCallback : public virtual CefBaseRefCounted { public: /// // Method that will be called when CDM registration is complete. |result| diff --git a/src/include/cef_x509_certificate.h b/src/include/cef_x509_certificate.h index f036ab5ed..c784ce1b0 100644 --- a/src/include/cef_x509_certificate.h +++ b/src/include/cef_x509_certificate.h @@ -47,7 +47,7 @@ // Class representing the issuer or subject field of an X.509 certificate. /// /*--cef(source=library)--*/ -class CefX509CertPrincipal : public virtual CefBase { +class CefX509CertPrincipal : public virtual CefBaseRefCounted { public: /// // Returns a name that can be used to represent the issuer. It tries in this @@ -110,7 +110,7 @@ class CefX509CertPrincipal : public virtual CefBase { // Class representing a X.509 certificate. /// /*--cef(source=library)--*/ -class CefX509Certificate : public virtual CefBase { +class CefX509Certificate : public virtual CefBaseRefCounted { public: typedef std::vector > IssuerChainBinaryList; diff --git a/src/include/cef_xml_reader.h b/src/include/cef_xml_reader.h index 86be8bac7..ab5244e34 100644 --- a/src/include/cef_xml_reader.h +++ b/src/include/cef_xml_reader.h @@ -47,7 +47,7 @@ // the object. /// /*--cef(source=library)--*/ -class CefXmlReader : public virtual CefBase { +class CefXmlReader : public virtual CefBaseRefCounted { public: typedef cef_xml_encoding_type_t EncodingType; typedef cef_xml_node_type_t NodeType; diff --git a/src/include/cef_zip_reader.h b/src/include/cef_zip_reader.h index 5d8788e94..80640087f 100644 --- a/src/include/cef_zip_reader.h +++ b/src/include/cef_zip_reader.h @@ -46,7 +46,7 @@ // the object. /// /*--cef(source=library)--*/ -class CefZipReader : public virtual CefBase { +class CefZipReader : public virtual CefBaseRefCounted { public: /// // Create a new CefZipReader object. The returned object's methods can only diff --git a/src/include/internal/cef_ptr.h b/src/include/internal/cef_ptr.h index e38543911..25ce74557 100644 --- a/src/include/internal/cef_ptr.h +++ b/src/include/internal/cef_ptr.h @@ -32,8 +32,15 @@ #define CEF_INCLUDE_INTERNAL_CEF_PTR_H_ #pragma once +#include "include/base/cef_build.h" #include "include/base/cef_ref_counted.h" +#if defined(USING_CHROMIUM_INCLUDES) +#include // For std::unique_ptr. +#else +#include "include/base/cef_scoped_ptr.h" +#endif + /// // Smart pointer implementation that is an alias of scoped_refptr from // include/base/cef_ref_counted.h. @@ -43,7 +50,7 @@ // avoid common memory leaks caused by forgetting to Release an object // reference. Sample usage: //
-//   class MyFoo : public CefBase {
+//   class MyFoo : public CefBaseRefCounted {
 //    ...
 //   };
 //
@@ -143,19 +150,83 @@
 // 
//

/// +#if defined(HAS_CPP11_TEMPLATE_ALIAS_SUPPORT) +template +using CefRefPtr = scoped_refptr; +#else +// When template aliases are not supported use a define instead of subclassing +// because it's otherwise hard to get the constructors to behave correctly. +#define CefRefPtr scoped_refptr +#endif + + +/// +// A CefOwnPtr is like a T*, except that the destructor of CefOwnPtr +// automatically deletes the pointer it holds (if any). That is, CefOwnPtr +// owns the T object that it points to. Like a T*, a CefOwnPtr may hold +// either NULL or a pointer to a T object. Also like T*, CefOwnPtr is +// thread-compatible, and once you dereference it, you get the thread safety +// guarantees of T. +/// +#if defined(USING_CHROMIUM_INCLUDES) +// Implementation-side code uses std::unique_ptr instead of scoped_ptr. +template > +using CefOwnPtr = std::unique_ptr; +#elif defined(HAS_CPP11_TEMPLATE_ALIAS_SUPPORT) +template > +using CefOwnPtr = scoped_ptr; +#else +// When template aliases are not supported use a define instead of subclassing +// because it's otherwise hard to get the constructors to behave correctly. +#define CefOwnPtr scoped_ptr +#endif + + +/// +// A CefRawPtr is the same as T* +/// +#if defined(HAS_CPP11_TEMPLATE_ALIAS_SUPPORT) template -class CefRefPtr : public scoped_refptr { +using CefRawPtr = T*; +#else +// Simple wrapper implementation that behaves as much like T* as possible. +template +class CefRawPtr { public: - typedef scoped_refptr parent; + CefRawPtr() : ptr_(nullptr) {} + CefRawPtr(T* p) : ptr_(p) {} + CefRawPtr(const CefRawPtr& r) : ptr_(r.ptr_) {} + + template + CefRawPtr(const CefRawPtr& r) : ptr_(r.get()) {} + + T* get() const { return ptr_; } - CefRefPtr() : parent() {} + // Allow CefRawPtr to be used in boolean expression and comparison operations. + operator T*() const { return ptr_; } - CefRefPtr(T* p) : parent(p) {} + T* operator->() const { + assert(ptr_ != NULL); + return ptr_; + } - CefRefPtr(const scoped_refptr& r) : parent(r) {} + CefRawPtr& operator=(T* p) { + ptr_ = p; + return *this; + } + + CefRawPtr& operator=(const CefRawPtr& r) { + return *this = r.ptr_; + } template - CefRefPtr(const scoped_refptr& r) : parent(r) {} + CefRawPtr& operator=(const CefRawPtr& r) { + return *this = r.get(); + } + + private: + T* ptr_; }; +#endif #endif // CEF_INCLUDE_INTERNAL_CEF_PTR_H_ diff --git a/src/include/internal/cef_string_types.h b/src/include/internal/cef_string_types.h index ab21fe84c..062065709 100644 --- a/src/include/internal/cef_string_types.h +++ b/src/include/internal/cef_string_types.h @@ -185,6 +185,17 @@ CEF_EXPORT void cef_string_userfree_utf8_free(cef_string_userfree_utf8_t str); CEF_EXPORT void cef_string_userfree_utf16_free(cef_string_userfree_utf16_t str); +/// +// These functions convert utf16 string case using the current ICU locale. This +// may change the length of the string in some cases. +/// + +CEF_EXPORT int cef_string_utf16_to_lower(const char16* src, size_t src_len, + cef_string_utf16_t* output); +CEF_EXPORT int cef_string_utf16_to_upper(const char16* src, size_t src_len, + cef_string_utf16_t* output); + + #ifdef __cplusplus } #endif diff --git a/src/include/internal/cef_types.h b/src/include/internal/cef_types.h index 01ef7d1ae..b275079db 100644 --- a/src/include/internal/cef_types.h +++ b/src/include/internal/cef_types.h @@ -2396,6 +2396,13 @@ typedef struct _cef_pdf_print_settings_t { int page_width; int page_height; + /// + // The percentage to scale the PDF by before printing (e.g. 50 is 50%). + // If this value is less than or equal to zero the default value of 100 + // will be used. + /// + int scale_factor; + /// // Margins in millimeters. Only used if |margin_type| is set to // PDF_PRINT_MARGIN_CUSTOM. @@ -2732,6 +2739,19 @@ typedef enum { CEF_MENU_ANCHOR_BOTTOMCENTER, } cef_menu_anchor_position_t; +/// +// Supported color types for menu items. +/// +typedef enum { + CEF_MENU_COLOR_TEXT, + CEF_MENU_COLOR_TEXT_HOVERED, + CEF_MENU_COLOR_TEXT_ACCELERATOR, + CEF_MENU_COLOR_TEXT_ACCELERATOR_HOVERED, + CEF_MENU_COLOR_BACKGROUND, + CEF_MENU_COLOR_BACKGROUND_HOVERED, + CEF_MENU_COLOR_COUNT, +} cef_menu_color_type_t; + // Supported SSL version values. See net/ssl/ssl_connection_status_flags.h // for more information. typedef enum { diff --git a/src/include/internal/cef_types_wrappers.h b/src/include/internal/cef_types_wrappers.h index 0c125c965..da41f8d32 100644 --- a/src/include/internal/cef_types_wrappers.h +++ b/src/include/internal/cef_types_wrappers.h @@ -945,6 +945,8 @@ struct CefPdfPrintSettingsTraits { target->page_width = src->page_width; target->page_height = src->page_height; + target->scale_factor = src->scale_factor; + target->margin_top = src->margin_top; target->margin_right = src->margin_right; target->margin_bottom = src->margin_bottom; diff --git a/src/include/test/cef_translator_test.h b/src/include/test/cef_translator_test.h index c9398dad9..29d1da262 100644 --- a/src/include/test/cef_translator_test.h +++ b/src/include/test/cef_translator_test.h @@ -53,10 +53,14 @@ #include "include/cef_base.h" -class CefTranslatorTestHandler; -class CefTranslatorTestHandlerChild; -class CefTranslatorTestObject; -class CefTranslatorTestObjectChild; +class CefTranslatorTestRefPtrClient; +class CefTranslatorTestRefPtrClientChild; +class CefTranslatorTestRefPtrLibrary; +class CefTranslatorTestRefPtrLibraryChild; +class CefTranslatorTestScopedClient; +class CefTranslatorTestScopedClientChild; +class CefTranslatorTestScopedLibrary; +class CefTranslatorTestScopedLibraryChild; // Test values. #define TEST_INT_VAL 5 @@ -79,8 +83,8 @@ class CefTranslatorTestObjectChild; /// // Class for testing all of the possible data transfer types. /// -/*--cef(source=library,no_debugct_check)--*/ -class CefTranslatorTest : public CefBase { +/*--cef(source=library)--*/ +class CefTranslatorTest : public CefBaseRefCounted { public: /// // Create the test object. @@ -309,146 +313,311 @@ class CefTranslatorTest : public CefBase { virtual size_t GetPointListSize() = 0; - // LIBRARY-SIDE OBJECT VALUES + // LIBRARY-SIDE REFPTR VALUES /// // Return an new library-side object. /// /*--cef()--*/ - virtual CefRefPtr GetObject(int val) =0; + virtual CefRefPtr GetRefPtrLibrary( + int val) =0; /// - // Set an object. Returns the value from CefTranslatorTestObject::GetValue(). + // Set an object. Returns the value from + // CefTranslatorTestRefPtrLibrary::GetValue(). // This tests input and execution of a library-side object type. /// /*--cef()--*/ - virtual int SetObject(CefRefPtr val) =0; + virtual int SetRefPtrLibrary( + CefRefPtr val) =0; /// // Set an object. Returns the object passed in. This tests input and output // of a library-side object type. /// /*--cef()--*/ - virtual CefRefPtr SetObjectAndReturn( - CefRefPtr val) =0; + virtual CefRefPtr SetRefPtrLibraryAndReturn( + CefRefPtr val) =0; /// // Set a child object. Returns the value from - // CefTranslatorTestObject::GetValue(). This tests input of a library-side - // child object type and execution as the parent type. + // CefTranslatorTestRefPtrLibrary::GetValue(). This tests input of a library- + // side child object type and execution as the parent type. /// /*--cef()--*/ - virtual int SetChildObject(CefRefPtr val) =0; + virtual int SetChildRefPtrLibrary( + CefRefPtr val) =0; /// // Set a child object. Returns the object as the parent type. This tests input // of a library-side child object type and return as the parent type. /// /*--cef()--*/ - virtual CefRefPtr SetChildObjectAndReturnParent( - CefRefPtr val) =0; + virtual CefRefPtr + SetChildRefPtrLibraryAndReturnParent( + CefRefPtr val) =0; - // LIBRARY-SIDE OBJECT LIST VALUES + // LIBRARY-SIDE REFPTR LIST VALUES // Test both with and without a typedef. - typedef std::vector > ObjectList; + typedef std::vector > + RefPtrLibraryList; /// // Set an object list vlaue. /// /*--cef()--*/ - virtual bool SetObjectList( - const std::vector >& val, + virtual bool SetRefPtrLibraryList( + const std::vector >& val, int val1, int val2) =0; /// // Return an object list value by out-param. /// - /*--cef(count_func=val:GetObjectListSize)--*/ - virtual bool GetObjectListByRef(ObjectList& val, int val1, int val2) =0; + /*--cef(count_func=val:GetRefPtrLibraryListSize)--*/ + virtual bool GetRefPtrLibraryListByRef(RefPtrLibraryList& val, int val1, + int val2) =0; /// // Return the number of object that will be output above. /// /*--cef()--*/ - virtual size_t GetObjectListSize() = 0; + virtual size_t GetRefPtrLibraryListSize() = 0; - // CLIENT-SIDE OBJECT VALUES + // CLIENT-SIDE REFPTR VALUES /// - // Set an object. Returns the value from CefTranslatorTestHandler::GetValue(). + // Set an object. Returns the value from + // CefTranslatorTestRefPtrClient::GetValue(). // This tests input and execution of a client-side object type. /// /*--cef()--*/ - virtual int SetHandler(CefRefPtr val) =0; + virtual int SetRefPtrClient(CefRefPtr val) =0; /// // Set an object. Returns the handler passed in. This tests input and output // of a client-side object type. /// /*--cef()--*/ - virtual CefRefPtr SetHandlerAndReturn( - CefRefPtr val) =0; + virtual CefRefPtr SetRefPtrClientAndReturn( + CefRefPtr val) =0; /// // Set a child object. Returns the value from - // CefTranslatorTestHandler::GetValue(). This tests input of a client-side - // child object type and execution as the parent type. + // CefTranslatorTestRefPtrClient::GetValue(). This tests input of a client- + // side child object type and execution as the parent type. /// /*--cef()--*/ - virtual int SetChildHandler(CefRefPtr val) =0; + virtual int SetChildRefPtrClient( + CefRefPtr val) =0; /// // Set a child object. Returns the object as the parent type. This tests // input of a client-side child object type and return as the parent type. /// /*--cef()--*/ - virtual CefRefPtr SetChildHandlerAndReturnParent( - CefRefPtr val) =0; + virtual CefRefPtr + SetChildRefPtrClientAndReturnParent( + CefRefPtr val) =0; - // CLIENT-SIDE OBJECT LIST VALUES + // CLIENT-SIDE REFPTR LIST VALUES // Test both with and without a typedef. - typedef std::vector > HandlerList; + typedef std::vector > + RefPtrClientList; /// // Set an object list vlaue. /// /*--cef()--*/ - virtual bool SetHandlerList( - const std::vector >& val, + virtual bool SetRefPtrClientList( + const std::vector >& val, int val1, int val2) =0; /// // Return an object list value by out-param. /// - /*--cef(count_func=val:GetObjectListSize)--*/ - virtual bool GetHandlerListByRef( - HandlerList& val, - CefRefPtr val1, - CefRefPtr val2) =0; + /*--cef(count_func=val:GetRefPtrLibraryListSize)--*/ + virtual bool GetRefPtrClientListByRef( + RefPtrClientList& val, + CefRefPtr val1, + CefRefPtr val2) =0; /// // Return the number of object that will be output above. /// /*--cef()--*/ - virtual size_t GetHandlerListSize() = 0; + virtual size_t GetRefPtrClientListSize() = 0; + + + // LIBRARY-SIDE OWNPTR VALUES + + /// + // Return an new library-side object. + /// + /*--cef()--*/ + virtual CefOwnPtr GetOwnPtrLibrary( + int val) =0; + + /// + // Set an object. Returns the value from + // CefTranslatorTestScopedLibrary::GetValue(). + // This tests input and execution of a library-side object type. + /// + /*--cef()--*/ + virtual int SetOwnPtrLibrary( + CefOwnPtr val) =0; + + /// + // Set an object. Returns the object passed in. This tests input and output + // of a library-side object type. + /// + /*--cef()--*/ + virtual CefOwnPtr SetOwnPtrLibraryAndReturn( + CefOwnPtr val) =0; + + /// + // Set a child object. Returns the value from + // CefTranslatorTestScopedLibrary::GetValue(). This tests input of a library- + // side child object type and execution as the parent type. + /// + /*--cef()--*/ + virtual int SetChildOwnPtrLibrary( + CefOwnPtr val) =0; + + /// + // Set a child object. Returns the object as the parent type. This tests input + // of a library-side child object type and return as the parent type. + /// + /*--cef()--*/ + virtual CefOwnPtr + SetChildOwnPtrLibraryAndReturnParent( + CefOwnPtr val) =0; + + + // CLIENT-SIDE OWNPTR VALUES + + /// + // Set an object. Returns the value from + // CefTranslatorTestScopedClient::GetValue(). + // This tests input and execution of a client-side object type. + /// + /*--cef()--*/ + virtual int SetOwnPtrClient(CefOwnPtr val) =0; + + /// + // Set an object. Returns the handler passed in. This tests input and output + // of a client-side object type. + /// + /*--cef()--*/ + virtual CefOwnPtr SetOwnPtrClientAndReturn( + CefOwnPtr val) =0; + + /// + // Set a child object. Returns the value from + // CefTranslatorTestScopedClient::GetValue(). This tests input of a client- + // side child object type and execution as the parent type. + /// + /*--cef()--*/ + virtual int SetChildOwnPtrClient( + CefOwnPtr val) =0; + + /// + // Set a child object. Returns the object as the parent type. This tests + // input of a client-side child object type and return as the parent type. + /// + /*--cef()--*/ + virtual CefOwnPtr + SetChildOwnPtrClientAndReturnParent( + CefOwnPtr val) =0; + + + // LIBRARY-SIDE RAWPTR VALUES + + /// + // Set an object. Returns the value from + // CefTranslatorTestScopedLibrary::GetValue(). + // This tests input and execution of a library-side object type. + /// + /*--cef()--*/ + virtual int SetRawPtrLibrary( + CefRawPtr val) =0; + + /// + // Set a child object. Returns the value from + // CefTranslatorTestScopedLibrary::GetValue(). This tests input of a library- + // side child object type and execution as the parent type. + /// + /*--cef()--*/ + virtual int SetChildRawPtrLibrary( + CefRawPtr val) =0; + + + // LIBRARY-SIDE RAWPTR LIST VALUES + + // Test both with and without a typedef. + typedef std::vector > + RawPtrLibraryList; + + /// + // Set an object list vlaue. + /// + /*--cef()--*/ + virtual bool SetRawPtrLibraryList( + const std::vector >& val, + int val1, int val2) =0; + + + // CLIENT-SIDE RAWPTR VALUES + + /// + // Set an object. Returns the value from + // CefTranslatorTestScopedClient::GetValue(). + // This tests input and execution of a client-side object type. + /// + /*--cef()--*/ + virtual int SetRawPtrClient(CefRawPtr val) =0; + + /// + // Set a child object. Returns the value from + // CefTranslatorTestScopedClient::GetValue(). This tests input of a client- + // side child object type and execution as the parent type. + /// + /*--cef()--*/ + virtual int SetChildRawPtrClient( + CefRawPtr val) =0; + + + // CLIENT-SIDE RAWPTR LIST VALUES + + // Test both with and without a typedef. + typedef std::vector > + RawPtrClientList; + + /// + // Set an object list vlaue. + /// + /*--cef()--*/ + virtual bool SetRawPtrClientList( + const std::vector >& val, + int val1, int val2) =0; }; + /// -// Library-side test object. +// Library-side test object for RefPtr. /// -/*--cef(source=library,no_debugct_check)--*/ -class CefTranslatorTestObject : public CefBase { +/*--cef(source=library)--*/ +class CefTranslatorTestRefPtrLibrary : public CefBaseRefCounted { public: /// // Create the test object. /// /*--cef()--*/ - static CefRefPtr Create(int value); + static CefRefPtr Create(int value); /// // Return a value. @@ -464,17 +633,129 @@ class CefTranslatorTestObject : public CefBase { }; /// -// Library-side child test object. +// Library-side child test object for RefPtr. /// -/*--cef(source=library,no_debugct_check)--*/ -class CefTranslatorTestObjectChild : public CefTranslatorTestObject { +/*--cef(source=library)--*/ +class CefTranslatorTestRefPtrLibraryChild : + public CefTranslatorTestRefPtrLibrary { public: /// // Create the test object. /// /*--cef()--*/ - static CefRefPtr Create(int value, - int other_value); + static CefRefPtr Create( + int value, + int other_value); + + /// + // Return a value. + /// + /*--cef()--*/ + virtual int GetOtherValue() =0; + + /// + // Set a value. + /// + /*--cef()--*/ + virtual void SetOtherValue(int value) =0; +}; + +/// +// Another library-side child test object for RefPtr. +/// +/*--cef(source=library)--*/ +class CefTranslatorTestRefPtrLibraryChildChild : + public CefTranslatorTestRefPtrLibraryChild { + public: + /// + // Create the test object. + /// + /*--cef()--*/ + static CefRefPtr Create( + int value, + int other_value, + int other_other_value); + + /// + // Return a value. + /// + /*--cef()--*/ + virtual int GetOtherOtherValue() =0; + + /// + // Set a value. + /// + /*--cef()--*/ + virtual void SetOtherOtherValue(int value) =0; +}; + +/// +// Client-side test object for RefPtr. +/// +/*--cef(source=client)--*/ +class CefTranslatorTestRefPtrClient : public virtual CefBaseRefCounted { + public: + /// + // Return a value. + /// + /*--cef()--*/ + virtual int GetValue() =0; +}; + +/// +// Client-side child test object for RefPtr. +/// +/*--cef(source=client)--*/ +class CefTranslatorTestRefPtrClientChild : + public CefTranslatorTestRefPtrClient { + public: + /// + // Return a value. + /// + /*--cef()--*/ + virtual int GetOtherValue() =0; +}; + + +/// +// Library-side test object for OwnPtr/RawPtr. +/// +/*--cef(source=library)--*/ +class CefTranslatorTestScopedLibrary : public CefBaseScoped { + public: + /// + // Create the test object. + /// + /*--cef()--*/ + static CefOwnPtr Create(int value); + + /// + // Return a value. + /// + /*--cef()--*/ + virtual int GetValue() =0; + + /// + // Set a value. + /// + /*--cef()--*/ + virtual void SetValue(int value) =0; +}; + +/// +// Library-side child test object for OwnPtr/RawPtr. +/// +/*--cef(source=library)--*/ +class CefTranslatorTestScopedLibraryChild : + public CefTranslatorTestScopedLibrary { + public: + /// + // Create the test object. + /// + /*--cef()--*/ + static CefOwnPtr Create( + int value, + int other_value); /// // Return a value. @@ -490,16 +771,17 @@ class CefTranslatorTestObjectChild : public CefTranslatorTestObject { }; /// -// Another library-side child test object. +// Another library-side child test object for OwnPtr/RawPtr. /// -/*--cef(source=library,no_debugct_check)--*/ -class CefTranslatorTestObjectChildChild : public CefTranslatorTestObjectChild { +/*--cef(source=library)--*/ +class CefTranslatorTestScopedLibraryChildChild : + public CefTranslatorTestScopedLibraryChild { public: /// // Create the test object. /// /*--cef()--*/ - static CefRefPtr Create( + static CefOwnPtr Create( int value, int other_value, int other_other_value); @@ -518,10 +800,10 @@ class CefTranslatorTestObjectChildChild : public CefTranslatorTestObjectChild { }; /// -// Client-side test object. +// Client-side test object for OwnPtr/RawPtr. /// -/*--cef(source=client,no_debugct_check)--*/ -class CefTranslatorTestHandler : public virtual CefBase { +/*--cef(source=client)--*/ +class CefTranslatorTestScopedClient : public virtual CefBaseScoped { public: /// // Return a value. @@ -531,10 +813,11 @@ class CefTranslatorTestHandler : public virtual CefBase { }; /// -// Client-side child test object. +// Client-side child test object for OwnPtr/RawPtr. /// -/*--cef(source=client,no_debugct_check)--*/ -class CefTranslatorTestHandlerChild : public CefTranslatorTestHandler { +/*--cef(source=client)--*/ +class CefTranslatorTestScopedClientChild : + public CefTranslatorTestScopedClient { public: /// // Return a value. diff --git a/src/include/views/cef_browser_view.h b/src/include/views/cef_browser_view.h index 2ae068d96..db622d4d1 100644 --- a/src/include/views/cef_browser_view.h +++ b/src/include/views/cef_browser_view.h @@ -74,6 +74,18 @@ class CefBrowserView : public CefView { /// /*--cef()--*/ virtual CefRefPtr GetBrowser() =0; + + /// + // Sets whether accelerators registered with CefWindow::SetAccelerator are + // triggered before or after the event is sent to the CefBrowser. If + // |prefer_accelerators| is true then the matching accelerator will be + // triggered immediately and the event will not be sent to the CefBrowser. If + // |prefer_accelerators| is false then the matching accelerator will only be + // triggered if the event is not handled by web content or by + // CefKeyboardHandler. The default value is false. + /// + /*--cef()--*/ + virtual void SetPreferAccelerators(bool prefer_accelerators) =0; }; #endif // CEF_INCLUDE_VIEWS_CEF_BROWSER_VIEW_H_ diff --git a/src/include/views/cef_button.h b/src/include/views/cef_button.h index 2feda9a63..db3db7d62 100644 --- a/src/include/views/cef_button.h +++ b/src/include/views/cef_button.h @@ -68,6 +68,12 @@ class CefButton : public CefView { /*--cef(default_retval=CEF_BUTTON_STATE_NORMAL)--*/ virtual cef_button_state_t GetState() =0; + /// + // Sets the Button will use an ink drop effect for displaying state changes. + /// + /*--cef()--*/ + virtual void SetInkDropEnabled(bool enabled) =0; + /// // Sets the tooltip text that will be displayed when the user hovers the mouse // cursor over the Button. diff --git a/src/include/views/cef_button_delegate.h b/src/include/views/cef_button_delegate.h index 99bd126e8..ec33d9be7 100644 --- a/src/include/views/cef_button_delegate.h +++ b/src/include/views/cef_button_delegate.h @@ -54,6 +54,12 @@ class CefButtonDelegate : public CefViewDelegate { /// /*--cef()--*/ virtual void OnButtonPressed(CefRefPtr button) =0; + + /// + // Called when the state of |button| changes. + /// + /*--cef()--*/ + virtual void OnButtonStateChanged(CefRefPtr button) {}; }; #endif // CEF_INCLUDE_VIEWS_CEF_BUTTON_DELEGATE_H_ diff --git a/src/include/views/cef_display.h b/src/include/views/cef_display.h index d9da06029..e3a84561d 100644 --- a/src/include/views/cef_display.h +++ b/src/include/views/cef_display.h @@ -51,7 +51,7 @@ // indicated. /// /*--cef(source=library)--*/ -class CefDisplay : public CefBase { +class CefDisplay : public CefBaseRefCounted { public: /// // Returns the primary Display. diff --git a/src/include/views/cef_layout.h b/src/include/views/cef_layout.h index d820a2753..5f1fcf4ef 100644 --- a/src/include/views/cef_layout.h +++ b/src/include/views/cef_layout.h @@ -49,7 +49,7 @@ class CefFillLayout; // process UI thread unless otherwise indicated. /// /*--cef(source=library)--*/ -class CefLayout : public CefBase { +class CefLayout : public CefBaseRefCounted { public: /// // Returns this Layout as a BoxLayout or NULL if this is not a BoxLayout. diff --git a/src/include/views/cef_menu_button.h b/src/include/views/cef_menu_button.h index 4ae8c4f3a..60f803c47 100644 --- a/src/include/views/cef_menu_button.h +++ b/src/include/views/cef_menu_button.h @@ -78,6 +78,13 @@ class CefMenuButton : public CefLabelButton { virtual void ShowMenu(CefRefPtr menu_model, const CefPoint& screen_point, cef_menu_anchor_position_t anchor_position) =0; + + /// + // Show the menu for this button. Results in a call to + // CefMenuButtonDelegate::OnMenuButtonPressed(). + /// + /*--cef()--*/ + virtual void TriggerMenu() =0; }; #endif // CEF_INCLUDE_VIEWS_CEF_MENU_BUTTON_H_ diff --git a/src/include/views/cef_view.h b/src/include/views/cef_view.h index b6f321e01..78422fecd 100644 --- a/src/include/views/cef_view.h +++ b/src/include/views/cef_view.h @@ -54,7 +54,7 @@ class CefWindow; // process UI thread unless otherwise indicated. /// /*--cef(source=library)--*/ -class CefView : public CefBase { +class CefView : public CefBaseRefCounted { public: /// // Returns this View as a BrowserView or NULL if this is not a BrowserView. @@ -145,6 +145,20 @@ class CefView : public CefBase { /*--cef()--*/ virtual void SetID(int id) =0; + /// + // Returns the group id of this View, or -1 if not set. + /// + /*--cef()--*/ + virtual int GetGroupID() =0; + + /// + // A group id is used to tag Views which are part of the same logical group. + // Focus can be moved between views with the same group using the arrow keys. + // The group id is immutable once it's set. + /// + /*--cef()--*/ + virtual void SetGroupID(int group_id) =0; + /// // Returns the View that contains this View, if any. /// diff --git a/src/include/views/cef_view_delegate.h b/src/include/views/cef_view_delegate.h index 70fd176c5..523b05282 100644 --- a/src/include/views/cef_view_delegate.h +++ b/src/include/views/cef_view_delegate.h @@ -47,7 +47,7 @@ class CefView; // will be called on the browser process UI thread unless otherwise indicated. /// /*--cef(source=client)--*/ -class CefViewDelegate : public virtual CefBase { +class CefViewDelegate : public virtual CefBaseRefCounted { public: /// // Return the preferred size for |view|. The Layout will use this information @@ -108,6 +108,18 @@ class CefViewDelegate : public virtual CefBase { virtual void OnChildViewChanged(CefRefPtr view, bool added, CefRefPtr child) {} + + /// + // Called when |view| gains focus. + /// + /*--cef()--*/ + virtual void OnFocus(CefRefPtr view) {} + + /// + // Called when |view| loses focus. + /// + /*--cef()--*/ + virtual void OnBlur(CefRefPtr view) {} }; #endif // CEF_INCLUDE_VIEWS_CEF_WINDOW_DELEGATE_H_ diff --git a/src/include/views/cef_window.h b/src/include/views/cef_window.h index b6ec638ec..a89ab63a4 100644 --- a/src/include/views/cef_window.h +++ b/src/include/views/cef_window.h @@ -289,6 +289,30 @@ class CefWindow : public CefPanel { virtual void SendMouseEvents(cef_mouse_button_type_t button, bool mouse_down, bool mouse_up) =0; + /// + // Set the keyboard accelerator for the specified |command_id|. |key_code| can + // be any virtual key or character value. CefWindowDelegate::OnAccelerator + // will be called if the keyboard combination is triggered while this window + // has focus. + /// + /*--cef()--*/ + virtual void SetAccelerator(int command_id, + int key_code, + bool shift_pressed, + bool ctrl_pressed, + bool alt_pressed) =0; + + /// + // Remove the keyboard accelerator for the specified |command_id|. + /// + /*--cef()--*/ + virtual void RemoveAccelerator(int command_id) =0; + + /// + // Remove all keyboard accelerators. + /// + /*--cef()--*/ + virtual void RemoveAllAccelerators() =0; }; #endif // CEF_INCLUDE_VIEWS_CEF_WINDOW_H_ diff --git a/src/include/views/cef_window_delegate.h b/src/include/views/cef_window_delegate.h index 19deae72a..a50e1fc41 100644 --- a/src/include/views/cef_window_delegate.h +++ b/src/include/views/cef_window_delegate.h @@ -95,6 +95,25 @@ class CefWindowDelegate : public CefPanelDelegate { /// /*--cef()--*/ virtual bool CanClose(CefRefPtr window) { return true; } + + /// + // Called when a keyboard accelerator registered with + // CefWindow::SetAccelerator is triggered. Return true if the accelerator was + // handled or false otherwise. + /// + /*--cef()--*/ + virtual bool OnAccelerator(CefRefPtr window, + int command_id) { return false; } + + /// + // Called after all other controls in the window have had a chance to + // handle the event. |event| contains information about the keyboard event. + // Return true if the keyboard event was handled or false otherwise. + /// + /*--cef()--*/ + virtual bool OnKeyEvent(CefRefPtr window, + const CefKeyEvent& event) { return false; } + }; #endif // CEF_INCLUDE_VIEWS_CEF_WINDOW_DELEGATE_H_ diff --git a/src/include/wrapper/cef_byte_read_handler.h b/src/include/wrapper/cef_byte_read_handler.h index 559cdd818..bf593d0ab 100644 --- a/src/include/wrapper/cef_byte_read_handler.h +++ b/src/include/wrapper/cef_byte_read_handler.h @@ -55,7 +55,7 @@ class CefByteReadHandler : public CefReadHandler { /// CefByteReadHandler(const unsigned char* bytes, size_t size, - CefRefPtr source); + CefRefPtr source); // CefReadHandler methods. virtual size_t Read(void* ptr, size_t size, size_t n) OVERRIDE; @@ -68,7 +68,7 @@ class CefByteReadHandler : public CefReadHandler { const unsigned char* bytes_; int64 size_; int64 offset_; - CefRefPtr source_; + CefRefPtr source_; base::Lock lock_; diff --git a/src/include/wrapper/cef_closure_task.h b/src/include/wrapper/cef_closure_task.h index 8828c6566..d9b559b6f 100644 --- a/src/include/wrapper/cef_closure_task.h +++ b/src/include/wrapper/cef_closure_task.h @@ -64,7 +64,7 @@ // Example of executing a bound method: // // // Define a class. -// class MyClass : public CefBase { +// class MyClass : public CefBaseRefCounted { // public: // MyClass() {} // void MyMethod(int arg) { /* do something with |arg| on the UI thread */ } diff --git a/src/include/wrapper/cef_message_router.h b/src/include/wrapper/cef_message_router.h index 2a6fcfe6f..23510ddcb 100644 --- a/src/include/wrapper/cef_message_router.h +++ b/src/include/wrapper/cef_message_router.h @@ -223,7 +223,7 @@ class CefMessageRouterBrowserSide : // the callback methods. The methods of this class may be called on any // browser process thread. /// - class Callback : public CefBase { + class Callback : public CefBaseRefCounted { public: /// // Notify the associated JavaScript onSuccess callback that the query has diff --git a/src/include/wrapper/cef_zip_archive.h b/src/include/wrapper/cef_zip_archive.h index 7ad10bda8..5de0960fc 100644 --- a/src/include/wrapper/cef_zip_archive.h +++ b/src/include/wrapper/cef_zip_archive.h @@ -62,7 +62,7 @@ class CefZipArchive : public base::RefCountedThreadSafe { // Class representing a file in the archive. Accessing the file data from // multiple threads is safe provided a reference to the File object is kept. /// - class File : public CefBase { + class File : public CefBaseRefCounted { public: /// // Returns the read-only data contained in the file. From 9930aa87f180165b9b0f22be83d6896b2f9ab19a Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 20 Apr 2017 15:18:24 +0200 Subject: [PATCH 060/398] Support Qt 5 in qt4.py example (type: qt4.py pyqt5) (#325). Tested on Linux. --- examples/qt4.py | 72 ++++++++++++--------- examples/resources/{pyqt.png => pyqt4.png} | Bin examples/resources/pyqt5.png | Bin 0 -> 20781 bytes 3 files changed, 43 insertions(+), 29 deletions(-) rename examples/resources/{pyqt.png => pyqt4.png} (100%) create mode 100644 examples/resources/pyqt5.png diff --git a/examples/qt4.py b/examples/qt4.py index 7b3516a2c..fe591eeda 100644 --- a/examples/qt4.py +++ b/examples/qt4.py @@ -1,12 +1,14 @@ -# Example of embedding CEF browser using PyQt/PySide libraries. -# This example has two widgets: a navigation bar and a browser. +# Example of embedding CEF browser using PyQt4, PyQt5 and +# PySide libraries. This example has two widgets: a navigation +# bar and a browser. # # Tested configurations: +# - PyQt 5.8.2 on Linux # - PyQt 4.11 (qt 4.8) on Windows/Linux # - PySide 1.2 (qt 4.8) on Windows/Linux/Mac # - CEF Python v55.4+ # -# Issues on Mac (tested with PySide): +# Issues with PySide 1.2 on Mac: # - Keyboard focus issues when switching between controls (Issue #284) # - Mouse cursor never changes when hovering over links (Issue #311) # - Sometimes process hangs when quitting app @@ -17,25 +19,32 @@ import platform import sys +# GLOBALS +PYQT4 = False +PYQT5 = False +PYSIDE = False + # PyQt imports -if "pyqt" in sys.argv: - # noinspection PyUnresolvedReferences +if "pyqt4" in sys.argv: + PYQT4 = True from PyQt4.QtGui import * - # noinspection PyUnresolvedReferences from PyQt4.QtCore import * +elif "pyqt5" in sys.argv: + PYQT5 = True + from PyQt5.QtGui import * + from PyQt5.QtCore import * + from PyQt5.QtWidgets import * # PySide imports elif "pyside" in sys.argv: - # noinspection PyUnresolvedReferences + PYSIDE = True import PySide - # noinspection PyUnresolvedReferences from PySide import QtCore - # noinspection PyUnresolvedReferences from PySide.QtGui import * - # noinspection PyUnresolvedReferences from PySide.QtCore import * else: print("USAGE:") - print(" qt4.py pyqt") + print(" qt4.py pyqt4") + print(" qt4.py pyqt5") print(" qt4.py pyside") sys.exit(1) @@ -53,8 +62,7 @@ # OS differences CefWidgetParent = QWidget -if LINUX: - # noinspection PyUnresolvedReferences +if LINUX and (PYQT4 or PYSIDE): CefWidgetParent = QX11EmbedContainer @@ -78,17 +86,14 @@ def check_versions(): print("[qt4.py] CEF Python {ver}".format(ver=cef.__version__)) print("[qt4.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) - # PyQt version - if "pyqt" in sys.argv: - # noinspection PyUnresolvedReferences + if PYQT4 or PYQT5: print("[qt4.py] PyQt {v1} (qt {v2})".format( v1=PYQT_VERSION_STR, v2=qVersion())) - # PySide version - elif "pyside" in sys.argv: + elif PYSIDE: print("[qt4.py] PySide {v1} (qt {v2})".format( v1=PySide.__version__, v2=QtCore.__version__)) # CEF Python version requirement - assert cef.__version__ >= "55.4", "CEF Python v55.+ required to run this" + assert cef.__version__ >= "55.4", "CEF Python v55.4+ required to run this" class MainWindow(QMainWindow): @@ -96,9 +101,11 @@ def __init__(self): super(MainWindow, self).__init__(None) self.cef_widget = None self.navigation_bar = None - if "pyqt" in sys.argv: - self.setWindowTitle("PyQt example") - elif "pyside" in sys.argv: + if PYQT4: + self.setWindowTitle("PyQt4 example") + elif PYQT5: + self.setWindowTitle("PyQt5 example") + elif PYSIDE: self.setWindowTitle("PySide example") self.setFocusPolicy(Qt.StrongFocus) self.setupLayout() @@ -119,6 +126,10 @@ def setupLayout(self): self.setCentralWidget(frame) # Browser can be embedded only after layout was set up self.cef_widget.embedBrowser() + if LINUX and PYQT5: + self.container = QWidget.createWindowContainer( + self.cef_widget.hidden_window, parent=self) + layout.addWidget(self.container, 1, 0) def closeEvent(self, event): # Close browser (force=True) and free CEF reference @@ -137,6 +148,7 @@ def __init__(self, parent=None): super(CefWidget, self).__init__(parent) self.parent = parent self.browser = None + self.hidden_window = None # Required for PyQt5 on Linux self.show() def focusInEvent(self, event): @@ -154,6 +166,8 @@ def focusOutEvent(self, event): self.browser.SetFocus(False) def embedBrowser(self): + if LINUX and PYQT5: + self.hidden_window = QWindow() window_info = cef.WindowInfo() rect = [0, 0, self.width(), self.height()] window_info.SetAsChild(self.getHandle(), rect) @@ -163,11 +177,16 @@ def embedBrowser(self): self.browser.SetClientHandler(FocusHandler(self)) def getHandle(self): - # PySide bug: QWidget.winId() returns - # There is no easy way to convert it to int. + # PyQt5 on Linux + if self.hidden_window: + return int(self.hidden_window.winId()) try: + # PyQt4 and PyQt5 return int(self.winId()) except: + # PySide: + # | QWidget.winId() returns + # | Converting it to int using ctypes. if sys.version_info[0] == 2: # Python 2 ctypes.pythonapi.PyCObject_AsVoidPtr.restype = ( @@ -214,7 +233,6 @@ def __init__(self, args): def createTimer(self): timer = QTimer() - # noinspection PyUnresolvedReferences timer.timeout.connect(self.onTimer) timer.start(10) return timer @@ -280,25 +298,21 @@ def __init__(self, cef_widget): # Back button self.back = self.createButton("back") - # noinspection PyUnresolvedReferences self.back.clicked.connect(self.onBack) layout.addWidget(self.back, 0, 0) # Forward button self.forward = self.createButton("forward") - # noinspection PyUnresolvedReferences self.forward.clicked.connect(self.onForward) layout.addWidget(self.forward, 0, 1) # Reload button self.reload = self.createButton("reload") - # noinspection PyUnresolvedReferences self.reload.clicked.connect(self.onReload) layout.addWidget(self.reload, 0, 2) # Url input self.url = QLineEdit("") - # noinspection PyUnresolvedReferences self.url.returnPressed.connect(self.onGoUrl) layout.addWidget(self.url, 0, 3) diff --git a/examples/resources/pyqt.png b/examples/resources/pyqt4.png similarity index 100% rename from examples/resources/pyqt.png rename to examples/resources/pyqt4.png diff --git a/examples/resources/pyqt5.png b/examples/resources/pyqt5.png new file mode 100644 index 0000000000000000000000000000000000000000..a41981951b57be5fe10d2fee9562aba5a04ade37 GIT binary patch literal 20781 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

D@1|QmjDw zbsYnMT09x{osChf4cCTY^fczbJQAG=p{#ul3 zS?vsl7|?oE#ZUV!MVGizkE*RdOlZKK)?&)JYp6NobDDDY|&E%5AO7!iu-VRIoF)nv~0mz{~+g4hEC<3P!e^Mty4 z_5%dcE0R%&u&a>(Sq_FRef|JuCq+lx2S#S?u7@f3BqN{6n!U)LMmg5Gbj$-ZlmauB z=*US~EVE^hx$)*`sj%BMGaPbLhchC+)}z|!4Hds-P~Gk9g=AtjZic2xt~%Xa^AJ2b z4$PRMg)@Y@)^J7Ak&8QSu9CL1X(|^QCg5Gp+47ZM>ix`?EGFfotZfEk@T{gW6VkM= z>a^eBcJPe_F)tlbGsV)YOiGRH>Kl zTEcp!RbK{ zKC}UNIxMV%;QcZ{V_`u@Api&{gBA<0cv$?OK Date: Thu, 20 Apr 2017 15:27:48 +0200 Subject: [PATCH 061/398] Rename qt4.py to qt.py, as it now supports Qt 5 as well (#325). Update docs and tools. --- examples/Examples-README.md | 5 +++-- examples/{qt4.py => qt.py} | 18 +++++++++--------- tools/run_examples.py | 22 +++++++++++++++------- 3 files changed, 27 insertions(+), 18 deletions(-) rename examples/{qt4.py => qt.py} (96%) diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 7be26d960..9b0ad482a 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -30,8 +30,9 @@ maintained: library (GTK 2) - [gtk3.py](gtk3.py): example for [PyGObject/PyGI](https://wiki.gnome.org/Projects/PyGObject) library (GTK 3). Currently broken on Linux/Mac, see top comments in sources. -- [qt4.py](qt4.py): example for [PyQt4](https://wiki.python.org/moin/PyQt4) - and [PySide](https://wiki.qt.io/PySide) libraries (Qt 4) +- [qt.py](qt.py): example for [PyQt4](https://wiki.python.org/moin/PyQt4), + [PyQt5](https://pypi.python.org/pypi/PyQt5) + and [PySide](https://wiki.qt.io/PySide) libraries - [tkinter_.py](tkinter_.py): example for [Tkinter](https://wiki.python.org/moin/TkInter). Currently broken on Mac. - [wxpython.py](wxpython.py): example for [wxPython](https://wxpython.org/) diff --git a/examples/qt4.py b/examples/qt.py similarity index 96% rename from examples/qt4.py rename to examples/qt.py index fe591eeda..0a551b246 100644 --- a/examples/qt4.py +++ b/examples/qt.py @@ -43,9 +43,9 @@ from PySide.QtCore import * else: print("USAGE:") - print(" qt4.py pyqt4") - print(" qt4.py pyqt5") - print(" qt4.py pyside") + print(" qt.py pyqt4") + print(" qt.py pyqt5") + print(" qt.py pyside") sys.exit(1) # Fix for PyCharm hints warnings when using static methods @@ -83,14 +83,14 @@ def main(): def check_versions(): - print("[qt4.py] CEF Python {ver}".format(ver=cef.__version__)) - print("[qt4.py] Python {ver} {arch}".format( + print("[qt.py] CEF Python {ver}".format(ver=cef.__version__)) + print("[qt.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) if PYQT4 or PYQT5: - print("[qt4.py] PyQt {v1} (qt {v2})".format( + print("[qt.py] PyQt {v1} (qt {v2})".format( v1=PYQT_VERSION_STR, v2=qVersion())) elif PYSIDE: - print("[qt4.py] PySide {v1} (qt {v2})".format( + print("[qt.py] PySide {v1} (qt {v2})".format( v1=PySide.__version__, v2=QtCore.__version__)) # CEF Python version requirement assert cef.__version__ >= "55.4", "CEF Python v55.4+ required to run this" @@ -265,7 +265,7 @@ def OnLoadStart(self, browser, **_): self.navigation_bar.cef_widget.setFocus() # Temporary fix no. 2 for focus issue on Linux (Issue #284) if LINUX: - print("[qt4.py] LoadHandler.OnLoadStart:" + print("[qt.py] LoadHandler.OnLoadStart:" " keyboard focus fix no. 2 (Issue #284)") browser.SetFocus(True) self.initial_app_loading = False @@ -281,7 +281,7 @@ def OnSetFocus(self, **_): def OnGotFocus(self, browser, **_): # Temporary fix no. 1 for focus issues on Linux (Issue #284) if LINUX: - print("[qt4.py] FocusHandler.OnGotFocus:" + print("[qt.py] FocusHandler.OnGotFocus:" " keyboard focus fix no. 1 (Issue #284)") browser.SetFocus(True) diff --git a/tools/run_examples.py b/tools/run_examples.py index 241a94173..afc061a26 100644 --- a/tools/run_examples.py +++ b/tools/run_examples.py @@ -72,19 +72,26 @@ def main(): print("[run_examples.py] PASS: gtk3.py (Gtk 3 not installed)") passed.append("gtk3.py") - # pyqt + # pyqt4 if packages["PyQt4"]: - examples.append("qt4.py pyqt") + examples.append("qt.py pyqt4") else: - print("[run_examples.py] PASS: qt4.py pyqt (PyQt4 not installed)") - passed.append("qt4.py pyqt") + print("[run_examples.py] PASS: qt.py pyqt4 (PyQt4 not installed)") + passed.append("qt.py pyqt4") + + # pyqt5 + if packages["PyQt5"]: + examples.append("qt.py pyqt5") + else: + print("[run_examples.py] PASS: qt.py pyqt5 (PyQt5 not installed)") + passed.append("qt.py pyqt5") # pyside if packages["PySide"]: - examples.append("qt4.py pyside") + examples.append("qt.py pyside") else: - print("[run_examples.py] PASS: qt4.py pyside (PySide not installed)") - passed.append("qt4.py pyside") + print("[run_examples.py] PASS: qt.py pyside (PySide not installed)") + passed.append("qt.py pyside") # tkinter if MAC: @@ -160,6 +167,7 @@ def check_installed_packages(): "gi": False, "kivy": False, "PyQt4": False, + "PyQt5": False, "PySide": False, "tkinter": False, "Tkinter": False, From c07c099e83762b67ee7f24c038b047b4f0822b0b Mon Sep 17 00:00:00 2001 From: cztomczak Date: Thu, 20 Apr 2017 22:16:10 +0200 Subject: [PATCH 062/398] Update to Chromium 57.0.2987.133 on Linux PART 2 (#341) --- src/compile_time_constants.pxi | 2 +- src/subprocess/cefpython_app.cpp | 2 +- src/subprocess/cefpython_app.h | 2 +- src/version/cef_version_linux.h | 20 ++++++++++---------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 47dedaecd..35f850027 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 3 +DEF PY_MAJOR_VERSION = 2 diff --git a/src/subprocess/cefpython_app.cpp b/src/subprocess/cefpython_app.cpp index 950777027..867f0646d 100644 --- a/src/subprocess/cefpython_app.cpp +++ b/src/subprocess/cefpython_app.cpp @@ -85,7 +85,7 @@ void CefPythonApp::OnBeforeCommandLineProcessing( } void CefPythonApp::OnRegisterCustomSchemes( - CefRefPtr registrar) { + CefRawPtr registrar) { } CefRefPtr CefPythonApp::GetResourceBundleHandler() { diff --git a/src/subprocess/cefpython_app.h b/src/subprocess/cefpython_app.h index f9336ba3c..e12fd570b 100644 --- a/src/subprocess/cefpython_app.h +++ b/src/subprocess/cefpython_app.h @@ -30,7 +30,7 @@ class CefPythonApp : CefRefPtr command_line) override; void OnRegisterCustomSchemes( - CefRefPtr registrar) override; + CefRawPtr registrar) override; CefRefPtr GetResourceBundleHandler() override; diff --git a/src/version/cef_version_linux.h b/src/version/cef_version_linux.h index 5e718118b..11454273f 100644 --- a/src/version/cef_version_linux.h +++ b/src/version/cef_version_linux.h @@ -35,16 +35,16 @@ #ifndef CEF_INCLUDE_CEF_VERSION_H_ #define CEF_INCLUDE_CEF_VERSION_H_ -#define CEF_VERSION "3.2924.1575.g97389a9" +#define CEF_VERSION "3.2987.1601.gf035232" #define CEF_VERSION_MAJOR 3 -#define CEF_COMMIT_NUMBER 1575 -#define CEF_COMMIT_HASH "97389a92ee2309ded830338d6afd61ba109d31d8" +#define CEF_COMMIT_NUMBER 1601 +#define CEF_COMMIT_HASH "f035232c082f837d2b85bd7821a93a54fc742775" #define COPYRIGHT_YEAR 2017 -#define CHROME_VERSION_MAJOR 56 +#define CHROME_VERSION_MAJOR 57 #define CHROME_VERSION_MINOR 0 -#define CHROME_VERSION_BUILD 2924 -#define CHROME_VERSION_PATCH 76 +#define CHROME_VERSION_BUILD 2987 +#define CHROME_VERSION_PATCH 133 #define DO_MAKE_STRING(p) #p #define MAKE_STRING(p) DO_MAKE_STRING(p) @@ -63,13 +63,13 @@ extern "C" { // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected. -#define CEF_API_HASH_UNIVERSAL "66de193ba22e1d92a99bb29d60f3107709aeefda" +#define CEF_API_HASH_UNIVERSAL "b0a24e3e202f3d8b72f2fbc1ebc5864f96ec16ae" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "8055740cd08db66cefe838a826dc90806fadfb33" +#define CEF_API_HASH_PLATFORM "1c6a27f840ac87c8c971350c907edbe2c5fa0387" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "12d8ab423df369b68d37c3667123a1812bc0d345" +#define CEF_API_HASH_PLATFORM "1567db600ee83cc2a59bb8c17ca416d11a7c9b8a" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "86ab23c0d7dafbdff7f66764cf8dac5ec1712af4" +#define CEF_API_HASH_PLATFORM "1f9f9e15bf7cf13de2557ddd411dfc9f694503b0" #endif // Returns CEF version information for the libcef library. The |entry| From b89b93ff9ea63e0b2fbffa9ddaa0b7e4b849dd53 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 21 Apr 2017 07:58:47 +0200 Subject: [PATCH 063/398] Update distrib directory names on various platforms --- tools/automate.py | 6 +++--- tools/common.py | 22 +++++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tools/automate.py b/tools/automate.py index 741242b8a..19289aed9 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -224,11 +224,11 @@ def build_cef(): % Options.binary_distrib) if Options.x86: - print("[automate.py] INFO: to build CEF projects and create prebuilt" - " binaries you have to use 32-bit chroot. Copy the binary" + print("[automate.py] INFO: Build CEF projects and create prebuilt" + " binaries on Linux 32-bit using eg. VirtualBox. Copy the binary" " distrib's cef_binary_*/ directory (path displayed above) to" " cefpython's build/ directory. Then run automate.py" - " --prebuilt-cef using 32-bit chroot.") + " --prebuilt-cef on Linux 32-bit.") sys.exit(0) else: # Build cefclient, cefsimple, ceftests, libcef_dll_wrapper diff --git a/tools/common.py b/tools/common.py index 386979c98..4ca34d73e 100644 --- a/tools/common.py +++ b/tools/common.py @@ -27,8 +27,14 @@ ARCH_STR = platform.architecture()[0] # OS_POSTFIX is for directories/files names in cefpython sources +# and doesn't include architecture type, just OS name. + # OS_POSTFIX2 is for platform name in cefpython binaries +# and includes architecture type (32bit/64bit). + # CEF_POSTFIX2 is for platform name in upstream CEF binaries +# and includes architecture type (32bit/64bit). + OS_POSTFIX = ("win" if platform.system() == "Windows" else "linux" if platform.system() == "Linux" else "mac" if platform.system() == "Darwin" else "unknown") @@ -353,17 +359,23 @@ def _detect_distrib_dir(): # Will only be set when called from scripts that had version # number arg passed on command line: build.py, build_distrib.py, # make_installer.py, etc. - if LINUX: - # On Linux buildig 32bit and 64bit separately, so don't + if LINUX or MAC: + # - On Linux buildig 32bit and 64bit separately, so don't # delete eg. 64bit distrib when building 32bit distrib. # Keep them in different directories. + # - On Mac only 64bit is supported. dirname = ("distrib_{version}_{postfix2}" .format(version=version, postfix2=OS_POSTFIX2)) - else: + elif WINDOWS: # On Windows both 32bit and 64bit distribs are built at # the same time. - # On Mac only 64bit is supported. - dirname = "distrib_{version}".format(version=version) + dirname = ("distrib_{version}_{win32}_{win64}" + .format(version=version, + win32=OS_POSTFIX2_ARCH[WINDOWS]["32bit"], + win64=OS_POSTFIX2_ARCH[WINDOWS]["64bit"])) + else: + dirname = ("distrib_{version}_{postfix}" + .format(version=version, postfix=OS_POSTFIX)) DISTRIB_DIR = os.path.join(BUILD_DIR, dirname) From cd9613ffa581e2e3245e9cc741a8f0e384293313 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 21 Apr 2017 09:44:41 +0200 Subject: [PATCH 064/398] Update to Chromium v57 on Mac (#341). Print user agent and cefpython version in tutorial.py example. Print various version info in main_test.py. --- examples/tutorial.py | 3 +++ src/compile_time_constants.pxi | 4 ++-- src/version/cef_version_mac.h | 20 ++++++++++---------- unittests/main_test.py | 8 +++++++- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/tutorial.py b/examples/tutorial.py index a2b04ca89..d770de8f3 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -42,6 +42,8 @@ window.onload = function(){ js_print("Javascript", "window.onload", "Called"); js_print("Javascript", "python_property", python_property); + js_print("Javascript", "navigator.userAgent", navigator.userAgent); + js_print("Javascript", "cefpython_version", cefpython_version.version); html_to_data_uri("test", js_callback_1); external.test_multiple_callbacks(js_callback_2); }; @@ -113,6 +115,7 @@ def set_javascript_bindings(browser): bindings = cef.JavascriptBindings( bindToFrames=False, bindToPopups=False) bindings.SetProperty("python_property", "This property was set in Python") + bindings.SetProperty("cefpython_version", cef.GetVersion()) bindings.SetFunction("html_to_data_uri", html_to_data_uri) bindings.SetObject("external", external) browser.SetJavascriptBindings(bindings) diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..457e4a580 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Linux" -DEF PY_MAJOR_VERSION = 2 +DEF UNAME_SYSNAME = "Darwin" +DEF PY_MAJOR_VERSION = 3 diff --git a/src/version/cef_version_mac.h b/src/version/cef_version_mac.h index 5e718118b..11454273f 100644 --- a/src/version/cef_version_mac.h +++ b/src/version/cef_version_mac.h @@ -35,16 +35,16 @@ #ifndef CEF_INCLUDE_CEF_VERSION_H_ #define CEF_INCLUDE_CEF_VERSION_H_ -#define CEF_VERSION "3.2924.1575.g97389a9" +#define CEF_VERSION "3.2987.1601.gf035232" #define CEF_VERSION_MAJOR 3 -#define CEF_COMMIT_NUMBER 1575 -#define CEF_COMMIT_HASH "97389a92ee2309ded830338d6afd61ba109d31d8" +#define CEF_COMMIT_NUMBER 1601 +#define CEF_COMMIT_HASH "f035232c082f837d2b85bd7821a93a54fc742775" #define COPYRIGHT_YEAR 2017 -#define CHROME_VERSION_MAJOR 56 +#define CHROME_VERSION_MAJOR 57 #define CHROME_VERSION_MINOR 0 -#define CHROME_VERSION_BUILD 2924 -#define CHROME_VERSION_PATCH 76 +#define CHROME_VERSION_BUILD 2987 +#define CHROME_VERSION_PATCH 133 #define DO_MAKE_STRING(p) #p #define MAKE_STRING(p) DO_MAKE_STRING(p) @@ -63,13 +63,13 @@ extern "C" { // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected. -#define CEF_API_HASH_UNIVERSAL "66de193ba22e1d92a99bb29d60f3107709aeefda" +#define CEF_API_HASH_UNIVERSAL "b0a24e3e202f3d8b72f2fbc1ebc5864f96ec16ae" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "8055740cd08db66cefe838a826dc90806fadfb33" +#define CEF_API_HASH_PLATFORM "1c6a27f840ac87c8c971350c907edbe2c5fa0387" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "12d8ab423df369b68d37c3667123a1812bc0d345" +#define CEF_API_HASH_PLATFORM "1567db600ee83cc2a59bb8c17ca416d11a7c9b8a" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "86ab23c0d7dafbdff7f66764cf8dac5ec1712af4" +#define CEF_API_HASH_PLATFORM "1f9f9e15bf7cf13de2557ddd411dfc9f694503b0" #endif // Returns CEF version information for the libcef library. The |entry| diff --git a/unittests/main_test.py b/unittests/main_test.py index a75b4a9e6..d7a2fbf95 100644 --- a/unittests/main_test.py +++ b/unittests/main_test.py @@ -14,7 +14,7 @@ import sys # To show the window for an extended period of time increase this number. -MESSAGE_LOOP_RANGE = 100 # each iteration is 0.01 sec +MESSAGE_LOOP_RANGE = 200 # each iteration is 0.01 sec g_datauri_data = """ @@ -36,6 +36,11 @@ window.onload = function(){ print("window.onload() ok"); + version = cefpython_version + print("CEF Python: "+version.version+""); + print("Chrome: "+version.chrome_version+""); + print("CEF: "+version.cef_version+""); + // Test binding property: test_property1 if (test_property1 == "Test binding property to the 'window' object") { print("test_property_1 ok"); @@ -140,6 +145,7 @@ def test_main(self): bindings.SetFunction("test_function", external.test_function) bindings.SetProperty("test_property1", external.test_property1) bindings.SetProperty("test_property2", external.test_property2) + bindings.SetProperty("cefpython_version", cef.GetVersion()) bindings.SetObject("external", external) browser.SetJavascriptBindings(bindings) subtest_message("browser.SetJavascriptBindings() ok") From 79a8ebcc51ab7e96adbb8d0d44eec4c89c7c1cc9 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 21 Apr 2017 14:50:17 +0100 Subject: [PATCH 065/398] Update to Chromium v57 on Win (#341) and fix PyQt5 example on Win (#325). Fix PyQt5 support on Windows in qt.py example (#325). Update automate.py to use --ninja-jobs also when building CEF projects. Default jobs ninja uses is very CPU intensive and can cause much lag in OS. --- examples/qt.py | 21 +++++++++++++++++---- src/compile_time_constants.pxi | 4 ++-- src/version/cef_version_win.h | 20 ++++++++++---------- tools/automate.py | 13 +++++++++---- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/examples/qt.py b/examples/qt.py index 0a551b246..47348a42f 100644 --- a/examples/qt.py +++ b/examples/qt.py @@ -3,7 +3,7 @@ # bar and a browser. # # Tested configurations: -# - PyQt 5.8.2 on Linux +# - PyQt 5.8.2 on Windows/Linux/Mac # - PyQt 4.11 (qt 4.8) on Windows/Linux # - PySide 1.2 (qt 4.8) on Windows/Linux/Mac # - CEF Python v55.4+ @@ -124,9 +124,22 @@ def setupLayout(self): frame = QFrame() frame.setLayout(layout) self.setCentralWidget(frame) + + if PYQT5 and WINDOWS: + # On Windows with PyQt5 main window must be shown first + # before CEF browser is embedded, otherwise window is + # not resized and application hangs during resize. + self.show() + # Browser can be embedded only after layout was set up self.cef_widget.embedBrowser() - if LINUX and PYQT5: + + if PYQT5 and LINUX: + # On Linux with PyQt5 the QX11EmbedContainer widget is + # no more available. An equivalent in Qt5 is to create + # a hidden window, embed CEF browser in it and then + # create a container for that hidden window and replace + # cef widget in the layout with the container. self.container = QWidget.createWindowContainer( self.cef_widget.hidden_window, parent=self) layout.addWidget(self.container, 1, 0) @@ -166,7 +179,7 @@ def focusOutEvent(self, event): self.browser.SetFocus(False) def embedBrowser(self): - if LINUX and PYQT5: + if PYQT5 and LINUX: self.hidden_window = QWindow() window_info = cef.WindowInfo() rect = [0, 0, self.width(), self.height()] @@ -177,8 +190,8 @@ def embedBrowser(self): self.browser.SetClientHandler(FocusHandler(self)) def getHandle(self): - # PyQt5 on Linux if self.hidden_window: + # PyQt5 on Linux return int(self.hidden_window.winId()) try: # PyQt4 and PyQt5 diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 457e4a580..10ec798af 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Darwin" -DEF PY_MAJOR_VERSION = 3 +DEF UNAME_SYSNAME = "Windows" +DEF PY_MAJOR_VERSION = 2 diff --git a/src/version/cef_version_win.h b/src/version/cef_version_win.h index 5e718118b..11454273f 100644 --- a/src/version/cef_version_win.h +++ b/src/version/cef_version_win.h @@ -35,16 +35,16 @@ #ifndef CEF_INCLUDE_CEF_VERSION_H_ #define CEF_INCLUDE_CEF_VERSION_H_ -#define CEF_VERSION "3.2924.1575.g97389a9" +#define CEF_VERSION "3.2987.1601.gf035232" #define CEF_VERSION_MAJOR 3 -#define CEF_COMMIT_NUMBER 1575 -#define CEF_COMMIT_HASH "97389a92ee2309ded830338d6afd61ba109d31d8" +#define CEF_COMMIT_NUMBER 1601 +#define CEF_COMMIT_HASH "f035232c082f837d2b85bd7821a93a54fc742775" #define COPYRIGHT_YEAR 2017 -#define CHROME_VERSION_MAJOR 56 +#define CHROME_VERSION_MAJOR 57 #define CHROME_VERSION_MINOR 0 -#define CHROME_VERSION_BUILD 2924 -#define CHROME_VERSION_PATCH 76 +#define CHROME_VERSION_BUILD 2987 +#define CHROME_VERSION_PATCH 133 #define DO_MAKE_STRING(p) #p #define MAKE_STRING(p) DO_MAKE_STRING(p) @@ -63,13 +63,13 @@ extern "C" { // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected. -#define CEF_API_HASH_UNIVERSAL "66de193ba22e1d92a99bb29d60f3107709aeefda" +#define CEF_API_HASH_UNIVERSAL "b0a24e3e202f3d8b72f2fbc1ebc5864f96ec16ae" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "8055740cd08db66cefe838a826dc90806fadfb33" +#define CEF_API_HASH_PLATFORM "1c6a27f840ac87c8c971350c907edbe2c5fa0387" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "12d8ab423df369b68d37c3667123a1812bc0d345" +#define CEF_API_HASH_PLATFORM "1567db600ee83cc2a59bb8c17ca416d11a7c9b8a" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "86ab23c0d7dafbdff7f66764cf8dac5ec1712af4" +#define CEF_API_HASH_PLATFORM "1f9f9e15bf7cf13de2557ddd411dfc9f694503b0" #endif // Returns CEF version information for the libcef library. The |entry| diff --git a/tools/automate.py b/tools/automate.py index 19289aed9..45a592d2c 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -201,6 +201,7 @@ def setup_options(docopt_args): Options.ninja_jobs = int(multiprocessing.cpu_count() / 2) if Options.ninja_jobs < 1: Options.ninja_jobs = 1 + Options.ninja_jobs = str(Options.ninja_jobs) def build_cef(): @@ -410,9 +411,11 @@ def build_cef_projects(): # > cefclient_mac.mm:22:29: error: property 'mainMenu' not found if MAC: # Build only cefsimple - command.extend(["ninja", "cefsimple"]) + command.extend(["ninja", "-j", Options.ninja_jobs, + "cefsimple"]) else: - command.extend(["ninja", "cefclient", "cefsimple", "ceftests"]) + command.extend(["ninja", "-j", Options.ninja_jobs, + "cefclient", "cefsimple", "ceftests"]) run_command(command, build_cefclient_dir) print("[automate.py] OK") assert os.path.exists(cefclient_exe) @@ -507,7 +510,8 @@ def build_wrapper_library_windows(runtime_library, msvs, vcvars): # Run ninja ninja_wrapper = prepare_build_command(build_lib=True, vcvars=vcvars) - ninja_wrapper.extend(["ninja", "libcef_dll_wrapper"]) + ninja_wrapper.extend(["ninja", "-j", Options.ninja_jobs, + "libcef_dll_wrapper"]) run_command(ninja_wrapper, working_dir=build_wrapper_dir) print("[automate.py] ninja OK") assert os.path.exists(wrapper_lib) @@ -630,7 +634,8 @@ def build_wrapper_library_mac(): print("[automate.py] cmake OK") # Ninja ninja_wrapper = prepare_build_command(build_lib=True) - ninja_wrapper.extend(["ninja", "libcef_dll_wrapper"]) + ninja_wrapper.extend(["ninja", "-j", Options.ninja_jobs, + "libcef_dll_wrapper"]) run_command(ninja_wrapper, build_wrapper_dir) print("[automate.py] ninja OK") assert os.path.exists(wrapper_lib) From 940c28ab9c8c1eaf960907a238f8ea1e7ce25048 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 21 Apr 2017 15:53:26 +0200 Subject: [PATCH 066/398] Update docs for v57.0 release --- README.md | 2 +- docs/Tutorial.md | 2 +- examples/Examples-README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b4bbfaae..5918fff98 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ also download packages for offline installation available on the [GitHub Releases](../../releases) pages. ``` -pip install cefpython3==56.2 +pip install cefpython3==57.0 ``` ## Tutorial diff --git a/docs/Tutorial.md b/docs/Tutorial.md index c4a38ec25..f52769b36 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -36,7 +36,7 @@ Run the commands below to install the cefpython3 package, clone the repository and run the Hello World example: ```commandline -pip install cefpython3==56.2 +pip install cefpython3==57.0 git clone https://github.com/cztomczak/cefpython.git cd cefpython/examples/ python hello_world.py diff --git a/examples/Examples-README.md b/examples/Examples-README.md index 9b0ad482a..e2b4ee93a 100644 --- a/examples/Examples-README.md +++ b/examples/Examples-README.md @@ -11,7 +11,7 @@ Instructions to install the cefpython3 package, clone the repository and run the hello_world.py example: ``` -pip install cefpython3==56.2 +pip install cefpython3==57.0 git clone https://github.com/cztomczak/cefpython.git cd cefpython/examples/ python hello_world.py From ac701e3186a4ccaa222fac227b5f8a865c21efdc Mon Sep 17 00:00:00 2001 From: cztomczak Date: Fri, 21 Apr 2017 15:54:04 +0200 Subject: [PATCH 067/398] Merge branch 'master' of https://github.com/cztomczak/cefpython --- examples/qt.py | 21 +++++++++++++++++---- examples/tutorial.py | 3 +++ src/compile_time_constants.pxi | 2 +- src/version/cef_version_mac.h | 20 ++++++++++---------- src/version/cef_version_win.h | 20 ++++++++++---------- tools/automate.py | 13 +++++++++---- unittests/main_test.py | 8 +++++++- 7 files changed, 57 insertions(+), 30 deletions(-) diff --git a/examples/qt.py b/examples/qt.py index 0a551b246..47348a42f 100644 --- a/examples/qt.py +++ b/examples/qt.py @@ -3,7 +3,7 @@ # bar and a browser. # # Tested configurations: -# - PyQt 5.8.2 on Linux +# - PyQt 5.8.2 on Windows/Linux/Mac # - PyQt 4.11 (qt 4.8) on Windows/Linux # - PySide 1.2 (qt 4.8) on Windows/Linux/Mac # - CEF Python v55.4+ @@ -124,9 +124,22 @@ def setupLayout(self): frame = QFrame() frame.setLayout(layout) self.setCentralWidget(frame) + + if PYQT5 and WINDOWS: + # On Windows with PyQt5 main window must be shown first + # before CEF browser is embedded, otherwise window is + # not resized and application hangs during resize. + self.show() + # Browser can be embedded only after layout was set up self.cef_widget.embedBrowser() - if LINUX and PYQT5: + + if PYQT5 and LINUX: + # On Linux with PyQt5 the QX11EmbedContainer widget is + # no more available. An equivalent in Qt5 is to create + # a hidden window, embed CEF browser in it and then + # create a container for that hidden window and replace + # cef widget in the layout with the container. self.container = QWidget.createWindowContainer( self.cef_widget.hidden_window, parent=self) layout.addWidget(self.container, 1, 0) @@ -166,7 +179,7 @@ def focusOutEvent(self, event): self.browser.SetFocus(False) def embedBrowser(self): - if LINUX and PYQT5: + if PYQT5 and LINUX: self.hidden_window = QWindow() window_info = cef.WindowInfo() rect = [0, 0, self.width(), self.height()] @@ -177,8 +190,8 @@ def embedBrowser(self): self.browser.SetClientHandler(FocusHandler(self)) def getHandle(self): - # PyQt5 on Linux if self.hidden_window: + # PyQt5 on Linux return int(self.hidden_window.winId()) try: # PyQt4 and PyQt5 diff --git a/examples/tutorial.py b/examples/tutorial.py index a2b04ca89..d770de8f3 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -42,6 +42,8 @@ window.onload = function(){ js_print("Javascript", "window.onload", "Called"); js_print("Javascript", "python_property", python_property); + js_print("Javascript", "navigator.userAgent", navigator.userAgent); + js_print("Javascript", "cefpython_version", cefpython_version.version); html_to_data_uri("test", js_callback_1); external.test_multiple_callbacks(js_callback_2); }; @@ -113,6 +115,7 @@ def set_javascript_bindings(browser): bindings = cef.JavascriptBindings( bindToFrames=False, bindToPopups=False) bindings.SetProperty("python_property", "This property was set in Python") + bindings.SetProperty("cefpython_version", cef.GetVersion()) bindings.SetFunction("html_to_data_uri", html_to_data_uri) bindings.SetObject("external", external) browser.SetJavascriptBindings(bindings) diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index 35f850027..10ec798af 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,3 +1,3 @@ # This file was generated by setup.py -DEF UNAME_SYSNAME = "Linux" +DEF UNAME_SYSNAME = "Windows" DEF PY_MAJOR_VERSION = 2 diff --git a/src/version/cef_version_mac.h b/src/version/cef_version_mac.h index 5e718118b..11454273f 100644 --- a/src/version/cef_version_mac.h +++ b/src/version/cef_version_mac.h @@ -35,16 +35,16 @@ #ifndef CEF_INCLUDE_CEF_VERSION_H_ #define CEF_INCLUDE_CEF_VERSION_H_ -#define CEF_VERSION "3.2924.1575.g97389a9" +#define CEF_VERSION "3.2987.1601.gf035232" #define CEF_VERSION_MAJOR 3 -#define CEF_COMMIT_NUMBER 1575 -#define CEF_COMMIT_HASH "97389a92ee2309ded830338d6afd61ba109d31d8" +#define CEF_COMMIT_NUMBER 1601 +#define CEF_COMMIT_HASH "f035232c082f837d2b85bd7821a93a54fc742775" #define COPYRIGHT_YEAR 2017 -#define CHROME_VERSION_MAJOR 56 +#define CHROME_VERSION_MAJOR 57 #define CHROME_VERSION_MINOR 0 -#define CHROME_VERSION_BUILD 2924 -#define CHROME_VERSION_PATCH 76 +#define CHROME_VERSION_BUILD 2987 +#define CHROME_VERSION_PATCH 133 #define DO_MAKE_STRING(p) #p #define MAKE_STRING(p) DO_MAKE_STRING(p) @@ -63,13 +63,13 @@ extern "C" { // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected. -#define CEF_API_HASH_UNIVERSAL "66de193ba22e1d92a99bb29d60f3107709aeefda" +#define CEF_API_HASH_UNIVERSAL "b0a24e3e202f3d8b72f2fbc1ebc5864f96ec16ae" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "8055740cd08db66cefe838a826dc90806fadfb33" +#define CEF_API_HASH_PLATFORM "1c6a27f840ac87c8c971350c907edbe2c5fa0387" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "12d8ab423df369b68d37c3667123a1812bc0d345" +#define CEF_API_HASH_PLATFORM "1567db600ee83cc2a59bb8c17ca416d11a7c9b8a" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "86ab23c0d7dafbdff7f66764cf8dac5ec1712af4" +#define CEF_API_HASH_PLATFORM "1f9f9e15bf7cf13de2557ddd411dfc9f694503b0" #endif // Returns CEF version information for the libcef library. The |entry| diff --git a/src/version/cef_version_win.h b/src/version/cef_version_win.h index 5e718118b..11454273f 100644 --- a/src/version/cef_version_win.h +++ b/src/version/cef_version_win.h @@ -35,16 +35,16 @@ #ifndef CEF_INCLUDE_CEF_VERSION_H_ #define CEF_INCLUDE_CEF_VERSION_H_ -#define CEF_VERSION "3.2924.1575.g97389a9" +#define CEF_VERSION "3.2987.1601.gf035232" #define CEF_VERSION_MAJOR 3 -#define CEF_COMMIT_NUMBER 1575 -#define CEF_COMMIT_HASH "97389a92ee2309ded830338d6afd61ba109d31d8" +#define CEF_COMMIT_NUMBER 1601 +#define CEF_COMMIT_HASH "f035232c082f837d2b85bd7821a93a54fc742775" #define COPYRIGHT_YEAR 2017 -#define CHROME_VERSION_MAJOR 56 +#define CHROME_VERSION_MAJOR 57 #define CHROME_VERSION_MINOR 0 -#define CHROME_VERSION_BUILD 2924 -#define CHROME_VERSION_PATCH 76 +#define CHROME_VERSION_BUILD 2987 +#define CHROME_VERSION_PATCH 133 #define DO_MAKE_STRING(p) #p #define MAKE_STRING(p) DO_MAKE_STRING(p) @@ -63,13 +63,13 @@ extern "C" { // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected. -#define CEF_API_HASH_UNIVERSAL "66de193ba22e1d92a99bb29d60f3107709aeefda" +#define CEF_API_HASH_UNIVERSAL "b0a24e3e202f3d8b72f2fbc1ebc5864f96ec16ae" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "8055740cd08db66cefe838a826dc90806fadfb33" +#define CEF_API_HASH_PLATFORM "1c6a27f840ac87c8c971350c907edbe2c5fa0387" #elif defined(OS_MACOSX) -#define CEF_API_HASH_PLATFORM "12d8ab423df369b68d37c3667123a1812bc0d345" +#define CEF_API_HASH_PLATFORM "1567db600ee83cc2a59bb8c17ca416d11a7c9b8a" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "86ab23c0d7dafbdff7f66764cf8dac5ec1712af4" +#define CEF_API_HASH_PLATFORM "1f9f9e15bf7cf13de2557ddd411dfc9f694503b0" #endif // Returns CEF version information for the libcef library. The |entry| diff --git a/tools/automate.py b/tools/automate.py index 19289aed9..45a592d2c 100644 --- a/tools/automate.py +++ b/tools/automate.py @@ -201,6 +201,7 @@ def setup_options(docopt_args): Options.ninja_jobs = int(multiprocessing.cpu_count() / 2) if Options.ninja_jobs < 1: Options.ninja_jobs = 1 + Options.ninja_jobs = str(Options.ninja_jobs) def build_cef(): @@ -410,9 +411,11 @@ def build_cef_projects(): # > cefclient_mac.mm:22:29: error: property 'mainMenu' not found if MAC: # Build only cefsimple - command.extend(["ninja", "cefsimple"]) + command.extend(["ninja", "-j", Options.ninja_jobs, + "cefsimple"]) else: - command.extend(["ninja", "cefclient", "cefsimple", "ceftests"]) + command.extend(["ninja", "-j", Options.ninja_jobs, + "cefclient", "cefsimple", "ceftests"]) run_command(command, build_cefclient_dir) print("[automate.py] OK") assert os.path.exists(cefclient_exe) @@ -507,7 +510,8 @@ def build_wrapper_library_windows(runtime_library, msvs, vcvars): # Run ninja ninja_wrapper = prepare_build_command(build_lib=True, vcvars=vcvars) - ninja_wrapper.extend(["ninja", "libcef_dll_wrapper"]) + ninja_wrapper.extend(["ninja", "-j", Options.ninja_jobs, + "libcef_dll_wrapper"]) run_command(ninja_wrapper, working_dir=build_wrapper_dir) print("[automate.py] ninja OK") assert os.path.exists(wrapper_lib) @@ -630,7 +634,8 @@ def build_wrapper_library_mac(): print("[automate.py] cmake OK") # Ninja ninja_wrapper = prepare_build_command(build_lib=True) - ninja_wrapper.extend(["ninja", "libcef_dll_wrapper"]) + ninja_wrapper.extend(["ninja", "-j", Options.ninja_jobs, + "libcef_dll_wrapper"]) run_command(ninja_wrapper, build_wrapper_dir) print("[automate.py] ninja OK") assert os.path.exists(wrapper_lib) diff --git a/unittests/main_test.py b/unittests/main_test.py index a75b4a9e6..d7a2fbf95 100644 --- a/unittests/main_test.py +++ b/unittests/main_test.py @@ -14,7 +14,7 @@ import sys # To show the window for an extended period of time increase this number. -MESSAGE_LOOP_RANGE = 100 # each iteration is 0.01 sec +MESSAGE_LOOP_RANGE = 200 # each iteration is 0.01 sec g_datauri_data = """ @@ -36,6 +36,11 @@ window.onload = function(){ print("window.onload() ok"); + version = cefpython_version + print("CEF Python: "+version.version+""); + print("Chrome: "+version.chrome_version+""); + print("CEF: "+version.cef_version+""); + // Test binding property: test_property1 if (test_property1 == "Test binding property to the 'window' object") { print("test_property_1 ok"); @@ -140,6 +145,7 @@ def test_main(self): bindings.SetFunction("test_function", external.test_function) bindings.SetProperty("test_property1", external.test_property1) bindings.SetProperty("test_property2", external.test_property2) + bindings.SetProperty("cefpython_version", cef.GetVersion()) bindings.SetObject("external", external) browser.SetJavascriptBindings(bindings) subtest_message("browser.SetJavascriptBindings() ok") From 804a0a121787c2c9e745cbf8e72ed1664fa32ef3 Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 22 Apr 2017 08:13:16 +0200 Subject: [PATCH 068/398] Fix PyCharm warnings in qt.py and update tested configurat. in gtk3.py --- README.md | 5 +++-- examples/gtk3.py | 17 +++++++---------- examples/qt.py | 30 ++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5918fff98..314dfc13d 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,10 @@ or other kind of internet bots. ## Install -You can install with pip. On Linux pip 8.1+ is required. You can +You can install [pypi/cefpython3](https://pypi.python.org/pypi/cefpython3) +package using pip tool. On Linux pip 8.1+ is required. You can also download packages for offline installation available on the -[GitHub Releases](../../releases) pages. +[GitHub Releases](../../releases) pages. Command to install with pip: ``` pip install cefpython3==57.0 diff --git a/examples/gtk3.py b/examples/gtk3.py index 6e0032600..acc2ee075 100644 --- a/examples/gtk3.py +++ b/examples/gtk3.py @@ -1,16 +1,13 @@ # Example of embedding CEF Python browser using PyGObject/PyGI (GTK 3). # -# Mac note: This example crashes on Mac with error message: -# > _createMenuRef called with existing principal MenuRef.. -# Reported as Issue #310. -# -# Linux note: This example is currently broken in v54+ on Linux (Issue #261). -# It works fine with cefpython v53. -# # Tested configurations: -# - GTK 3.18 on Windows -# - GTK 3.10 on Linux -# - CEF Python v53.1+ +# - GTK 3.18 on Windows (cefpython v53.1+) +# - GTK 3.10 on Linux (works with cefpython v53.1 and v57.0+) +# +# Mac crash: This example crashes on Mac with error message: +# > _createMenuRef called with existing principal MenuRef.. +# Reported as Issue #310. + from cefpython3 import cefpython as cef import ctypes diff --git a/examples/qt.py b/examples/qt.py index 47348a42f..37478844e 100644 --- a/examples/qt.py +++ b/examples/qt.py @@ -24,22 +24,29 @@ PYQT5 = False PYSIDE = False -# PyQt imports if "pyqt4" in sys.argv: PYQT4 = True + # noinspection PyUnresolvedReferences from PyQt4.QtGui import * + # noinspection PyUnresolvedReferences from PyQt4.QtCore import * elif "pyqt5" in sys.argv: PYQT5 = True + # noinspection PyUnresolvedReferences from PyQt5.QtGui import * + # noinspection PyUnresolvedReferences from PyQt5.QtCore import * + # noinspection PyUnresolvedReferences from PyQt5.QtWidgets import * -# PySide imports elif "pyside" in sys.argv: PYSIDE = True + # noinspection PyUnresolvedReferences import PySide + # noinspection PyUnresolvedReferences from PySide import QtCore + # noinspection PyUnresolvedReferences from PySide.QtGui import * + # noinspection PyUnresolvedReferences from PySide.QtCore import * else: print("USAGE:") @@ -63,6 +70,7 @@ # OS differences CefWidgetParent = QWidget if LINUX and (PYQT4 or PYSIDE): + # noinspection PyUnresolvedReferences CefWidgetParent = QX11EmbedContainer @@ -98,6 +106,7 @@ def check_versions(): class MainWindow(QMainWindow): def __init__(self): + # noinspection PyArgumentList super(MainWindow, self).__init__(None) self.cef_widget = None self.navigation_bar = None @@ -115,12 +124,15 @@ def setupLayout(self): self.cef_widget = CefWidget(self) self.navigation_bar = NavigationBar(self.cef_widget) layout = QGridLayout() + # noinspection PyArgumentList layout.addWidget(self.navigation_bar, 0, 0) + # noinspection PyArgumentList layout.addWidget(self.cef_widget, 1, 0) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.setRowStretch(0, 0) layout.setRowStretch(1, 1) + # noinspection PyArgumentList frame = QFrame() frame.setLayout(layout) self.setCentralWidget(frame) @@ -140,8 +152,10 @@ def setupLayout(self): # a hidden window, embed CEF browser in it and then # create a container for that hidden window and replace # cef widget in the layout with the container. + # noinspection PyUnresolvedReferences, PyArgumentList self.container = QWidget.createWindowContainer( self.cef_widget.hidden_window, parent=self) + # noinspection PyArgumentList layout.addWidget(self.container, 1, 0) def closeEvent(self, event): @@ -158,6 +172,7 @@ def clear_browser_references(self): class CefWidget(CefWidgetParent): def __init__(self, parent=None): + # noinspection PyArgumentList super(CefWidget, self).__init__(parent) self.parent = parent self.browser = None @@ -180,6 +195,7 @@ def focusOutEvent(self, event): def embedBrowser(self): if PYQT5 and LINUX: + # noinspection PyUnresolvedReferences self.hidden_window = QWindow() window_info = cef.WindowInfo() rect = [0, 0, self.width(), self.height()] @@ -246,6 +262,7 @@ def __init__(self, args): def createTimer(self): timer = QTimer() + # noinspection PyUnresolvedReferences timer.timeout.connect(self.onTimer) timer.start(10) return timer @@ -301,6 +318,7 @@ def OnGotFocus(self, browser, **_): class NavigationBar(QFrame): def __init__(self, cef_widget): + # noinspection PyArgumentList super(NavigationBar, self).__init__() self.cef_widget = cef_widget @@ -311,22 +329,30 @@ def __init__(self, cef_widget): # Back button self.back = self.createButton("back") + # noinspection PyUnresolvedReferences self.back.clicked.connect(self.onBack) + # noinspection PyArgumentList layout.addWidget(self.back, 0, 0) # Forward button self.forward = self.createButton("forward") + # noinspection PyUnresolvedReferences self.forward.clicked.connect(self.onForward) + # noinspection PyArgumentList layout.addWidget(self.forward, 0, 1) # Reload button self.reload = self.createButton("reload") + # noinspection PyUnresolvedReferences self.reload.clicked.connect(self.onReload) + # noinspection PyArgumentList layout.addWidget(self.reload, 0, 2) # Url input self.url = QLineEdit("") + # noinspection PyUnresolvedReferences self.url.returnPressed.connect(self.onGoUrl) + # noinspection PyArgumentList layout.addWidget(self.url, 0, 3) # Layout From b1f53487e65300f44fd760ac5ba67b08e9f3b52e Mon Sep 17 00:00:00 2001 From: cztomczak Date: Sat, 22 Apr 2017 19:39:17 +0200 Subject: [PATCH 069/398] Add screenshot.py example and off-screen rendering section in Tutorial. Add screenshot.py, a simple example of off-screen rendering (#287). --- api/RenderHandler.md | 11 +- docs/Tutorial.md | 159 +++++++++++++++++++++++++-- examples/Examples-README.md | 20 ++-- examples/screenshot.py | 209 ++++++++++++++++++++++++++++++++++++ examples/tutorial.py | 6 +- 5 files changed, 386 insertions(+), 19 deletions(-) create mode 100644 examples/screenshot.py diff --git a/api/RenderHandler.md b/api/RenderHandler.md index 428120209..bb25ac01d 100644 --- a/api/RenderHandler.md +++ b/api/RenderHandler.md @@ -129,7 +129,7 @@ Called when the browser wants to move or resize the popup widget. | Parameter | Type | | --- | --- | | browser | [Browser](Browser.md) | -| element_type | int | +| element_type | PaintElementType | | dirty_rects | list[[x,y,width,height],[..]] | | paint_buffer | [PaintBuffer](PaintBuffer.md) | | width | int | @@ -145,9 +145,12 @@ of rectangles in pixel coordinates that need to be repainted. |buffer| will be |width|*|height|*4 bytes in size and represents a BGRA image with an upper-left origin. -`paintElementType` constants in the cefpython module: -* PET_VIEW -* PET_POPUP +**Important:** Do not keep reference to |paint_buffer| after this +method returns. + +`PaintElementType` enum: +* cef.PET_VIEW +* cef.PET_POPUP ### OnCursorChange diff --git a/docs/Tutorial.md b/docs/Tutorial.md index f52769b36..0065f7aa6 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -5,9 +5,10 @@ on Chromium in a Python application. You can also use it to create a HTML 5 based GUI in an application that can act as a replacement for standard GUI toolkits such as wxWidgets, Qt or GTK. With this tutorial you will learn CEF Python -basics. This tutorial will discuss the two basic examples: -[hello_world.py](../examples/hello_world.py) -and [tutorial.py](../examples/tutorial.py). There are many +basics. This tutorial will discuss the three featured examples: +[hello_world.py](../examples/hello_world.py), +[tutorial.py](../examples/tutorial.py) +and [screenshot.py](../examples/screenshot.py). There are many more examples that you can find in the [Examples-README.md](../examples/Examples-README.md) file, but these examples are out of scope for this tutorial. @@ -23,6 +24,7 @@ Table of contents: * [Javascript integration](#javascript-integration) * [Javascript exceptions and Python exceptions](#javascript-exceptions-and-python-exceptions) * [Plugins and Flash support](#plugins-and-flash-support) +* [Off-screen rendering](#off-screen-rendering) * [Build executable](#build-executable) * [Support and documentation](#support-and-documentation) @@ -46,9 +48,9 @@ The hello_world.py example's source code will be analyzed line by line in the next section of this Tutorial. This tutorial in its further sections will also reference the -tutorial.py example which will show how to use more advanced -CEF Python features. The tutorial.py example is also available -in the examples/ directory. +tutorial.py and screenshot.py examples which will show how to +use more advanced CEF Python features. All these examples are +available in the examples/ root directory. ## Hello world @@ -470,6 +472,151 @@ For the old CEF Python v31 release instructions for enabling Flash support are available on Wiki pages. +## Off-screen rendering + +Off-screen rendering, in short OSR, also known as windowless +rendering, is a method of rendering pages into a memory buffer +without creating an actual visible window. This method of +rendering has its uses, some pluses and some minuses. Its main +use is so that web page rendering can be integrated into apps +that have its own rendering systems and they can draw web browser +contents only if they are provided a pixel buffer to draw. CEF Python +provides a few examples of integrating CEF off-screen rendering +with frameworks such as Kivy, Panda3D and Pygame/PyOpenGl. + +In this tutorial it will be discussed [screenshot.py](../examples/screenshot.py) +example which is a very basic example of off-screen rendering. +This example creates a screenshot of a web page with viewport +size set to 800px width and 5000px height which is an equivalent +of scrolling down page multiple times, but you get all this in +one single screenshot. + +Before running this script you must install PIL image library: + +```text +pip install PIL +``` + +This example accepts optional arguments so that you can change +url and viewport size. Example usage: + +```text +python screenshot.py +python screenshot.py https://github.com/cztomczak/cefpython 1024 5000 +python screenshot.py https://www.google.com/ 800 600 +``` + +Let's discuss code in this example. + +To be able to use off-screen rendering mode in CEF you have to set +[windowless_rendering_enabled](../api/ApplicationSettings.md#windowless_rendering_enabled) +option to True, eg.: + +```Python +cef.Initialize(settings={"windowless_rendering_enabled": True}) +``` + +Do not enable this value if the application does not use off-screen +rendering as it may reduce rendering performance on some systems. + +Another thing that distincts windowed rendering from off-screen +rendering is that when creating browser you have to call SetAsOffscreen +method on the WindowInfo object. Code from the example: + +```Python +parent_window_handle = 0 +window_info = cef.WindowInfo() +window_info.SetAsOffscreen(parent_window_handle) +browser = cef.CreateBrowserSync(window_info=window_info, + url=URL) +``` + +Also after creating browser it is required to let CEF know that +viewport size is available and that OnPaint callback may be called +(this callback will be explained in a moment) by calling +WasResized method: + +```Python +browser.WasResized() +``` + +Off-screen rendering requires implementing [RenderHandler](../api/RenderHandler.md#renderhandler-interface) +which is one of client handlers and how to use them was +explained earlier in the tutorial in the [Client handlers](#client-handlers) +section. For basic off-screen rendering it is enough to +implement only two methods: [GetViewRect](../api/RenderHandler.md#getviewrect) +and [OnPaint](../api/RenderHandler.md#onpaint). In the GetViewRect +callback information on viewport size will be provided to CEF: + +```Python +def GetViewRect(self, rect_out, **_): + rect_out.extend([0, 0, VIEWPORT_SIZE[0], VIEWPORT_SIZE[1]]) + return True +``` + +In this callback viewport size is returned via |rect_out| which +is of type "list" and thus is passed by reference. Additionally +a True value is returned by function to notify CEF that rectangle +was provided. + +In the OnPaint callback CEF provides a [PaintBufer](../api/PaintBuffer.md#paintbuffer-object) object, which is a pixel buffer of the +browser view. This object has [GetIntPointer](../api/PaintBuffer.md#getintpointer) +and [GetString](../api/PaintBuffer.md#getstring) methods. In the +example the latter method is used which returns bytes. The method +name is a bit confusing for Python 3 users, but in Python 2 bytes +were strings and thus the name. Here is the code: + +```Python +def OnPaint(self, browser, element_type, paint_buffer, **_): + if element_type == cef.PET_VIEW: + buffer_string = paint_buffer.GetString(mode="rgba", + origin="top-left") + browser.SetUserData("OnPaint.buffer_string", buffer_string) +``` + +The |element_type| argument can be either of cef.PET_VIEW +(main view) or cef.PET_POPUP (for drawing popup widgets like +`