diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml new file mode 100644 index 000000000..360a3382c --- /dev/null +++ b/.github/workflows/ci-linux.yml @@ -0,0 +1,143 @@ +name: CI Linux + +on: + push: + branches: ["master", "cefpython147"] + pull_request: + branches: ["master"] + workflow_dispatch: + inputs: + bypass_cache: + description: "Bypass all caches for a clean run" + type: boolean + default: false + +jobs: + download-cef: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Read CEF version + id: cef-version + run: | + ver=$(grep -oP '(?<=#define CEF_VERSION ")[^"]+' src/version/cef_version_linux.h) + echo "value=$ver" >> $GITHUB_OUTPUT + + - name: Cache CEF binaries + uses: actions/cache@v4 + if: ${{ inputs.bypass_cache != true }} + with: + path: | + build/cef_binary_* + build/cef*_linux64 + key: cef-linux64-${{ steps.cef-version.outputs.value }} + + - name: Install build tools + run: python tools/requirements.py + + - name: Download CEF binaries + run: python tools/download_cef.py + + wheel: + needs: download-cef + runs-on: ubuntu-24.04 + timeout-minutes: 75 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Read CEF version + id: cef-version + run: | + ver=$(grep -oP '(?<=#define CEF_VERSION ")[^"]+' src/version/cef_version_linux.h) + echo "value=$ver" >> $GITHUB_OUTPUT + + - name: Restore CEF cache + uses: actions/cache/restore@v4 + if: ${{ inputs.bypass_cache != true }} + with: + path: | + build/cef_binary_* + build/cef*_linux64 + key: cef-linux64-${{ steps.cef-version.outputs.value }} + + - name: Install system dependencies + run: | + sudo apt-get update -q + sudo apt-get install -y --no-install-recommends \ + cmake ninja-build pkg-config \ + libgtk2.0-dev libgtk-3-dev \ + libglib2.0-dev libx11-dev \ + libnss3-dev libatk1.0-dev \ + libxcomposite-dev libxdamage-dev libxext-dev \ + libxfixes-dev libxrandr-dev libxrender-dev + + - name: Install build tools + run: python tools/requirements.py + + - name: Prepare prebuilt CEF + run: python tools/automate.py --prebuilt-cef + + - name: Configure CMake + run: cmake -S . -B build/_cmake_build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build/_cmake_build --parallel + + - name: Stage build outputs + run: | + mkdir -p build/artifacts + cp build/_cmake_build/cefpython_py*.so build/artifacts/ + cp build/_cmake_build/subprocess_build/subprocess build/artifacts/ + cef_dir=$(ls -d build/cef*_linux64 2>/dev/null | head -1) + find "$cef_dir/bin" -maxdepth 1 -mindepth 1 \ + ! -name 'cefclient*' ! -name 'cefsimple*' ! -name 'ceftests*' ! -name 'chrome-sandbox' \ + -exec cp -r {} build/artifacts/ \; + + - name: Install runtime dependencies + run: | + sudo apt-get install -y --no-install-recommends \ + libnss3 libatk1.0-0 libatk-bridge2.0-0 \ + libx11-6 libxcomposite1 libxdamage1 libxext6 \ + libxfixes3 libxrandr2 libxrender1 \ + libgtk2.0-0 libglib2.0-0 xvfb + + - name: Set up cefpython3 package + run: | + cp -r build/artifacts/. cefpython3/ + chmod +x cefpython3/subprocess + + - name: Resize /dev/shm for Chromium shared memory + run: sudo mount -o remount,size=512m /dev/shm + + - name: Run unit tests + run: xvfb-run python unittests/_test_runner.py + env: + PYTHONPATH: ${{ github.workspace }} + + - name: Build wheel + run: python tools/build_distrib.py + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: cefpython3-py${{ matrix.python-version }}-linux64 + path: build/dist/*.whl diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml new file mode 100644 index 000000000..7bb7d060e --- /dev/null +++ b/.github/workflows/ci-macos.yml @@ -0,0 +1,237 @@ +name: CI macOS ARM + +on: + push: + branches: ["master", "cefpython147"] + pull_request: + branches: ["master"] + workflow_dispatch: + inputs: + bypass_cache: + description: "Bypass all caches for a clean run" + type: boolean + default: false + +jobs: + download-cef: + runs-on: macos-14 + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Read CEF version + id: cef-version + run: | + ver=$(python3 -c " + import re, sys + h = open('src/version/cef_version_macarm64.h').read() + m = re.search(r'#define CEF_VERSION \"([^\"]+)\"', h) + print(m.group(1)) + ") + echo "value=$ver" >> $GITHUB_OUTPUT + + - name: Cache CEF binaries + uses: actions/cache@v4 + if: ${{ inputs.bypass_cache != true }} + with: + path: | + build/cef_binary_* + build/cef*_macarm64 + key: cef-macosarm64-v3-${{ steps.cef-version.outputs.value }} + + - name: Install build tools + run: python tools/requirements.py + + - name: Download CEF binaries + run: python tools/download_cef.py + + compile: + needs: download-cef + runs-on: macos-14 + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Read CEF version + id: cef-version + run: | + ver=$(python3 -c " + import re, sys + h = open('src/version/cef_version_macarm64.h').read() + m = re.search(r'#define CEF_VERSION \"([^\"]+)\"', h) + print(m.group(1)) + ") + echo "value=$ver" >> $GITHUB_OUTPUT + + - name: Restore CEF cache + uses: actions/cache/restore@v4 + if: ${{ inputs.bypass_cache != true }} + with: + path: | + build/cef_binary_* + build/cef*_macarm64 + key: cef-macosarm64-v3-${{ steps.cef-version.outputs.value }} + + - name: Install build tools + run: python tools/requirements.py + + - name: Prepare prebuilt CEF + run: python tools/automate.py --prebuilt-cef + + - name: Verify CEF architecture + run: | + wrapper=$(ls build/cef*_macarm64/lib/libcef_dll_wrapper.a 2>/dev/null | head -1) + if [ -z "$wrapper" ]; then echo "libcef_dll_wrapper.a not found"; exit 1; fi + archs=$(lipo -info "$wrapper" 2>&1) + echo "$archs" + echo "$archs" | grep -q arm64 || { echo "ERROR: libcef_dll_wrapper.a is not arm64"; exit 1; } + + - name: Configure CMake + run: cmake -S . -B build/_cmake_build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build/_cmake_build --parallel + + - name: Stage build outputs + run: | + mkdir -p build/artifacts + cp build/_cmake_build/cefpython_py*.so build/artifacts/ + cp build/_cmake_build/subprocess_build/subprocess build/artifacts/ + cef_dir=$(ls -d build/cef*_macarm64 2>/dev/null | head -1) + find "$cef_dir/bin" -maxdepth 1 -mindepth 1 \ + ! -name 'cefclient*' ! -name 'cefsimple*' ! -name 'ceftests*' \ + -exec cp -r {} build/artifacts/ \; + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-py${{ matrix.python-version }}-macosarm64 + path: build/artifacts/ + retention-days: 1 + + test: + needs: compile + runs-on: macos-14 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-py${{ matrix.python-version }}-macosarm64 + path: build/artifacts/ + + - name: Set up cefpython3 package for testing + run: | + cp -r build/artifacts/. cefpython3/ + chmod +x cefpython3/subprocess + # Ad-hoc sign our compiled binaries so macOS allows them to run. + # (Our freshly built binaries have no signature; unsigned binaries + # are blocked on macOS 14+.) + codesign --force --sign - cefpython3/subprocess + for f in cefpython3/cefpython_py*.so; do codesign --force --sign - "$f"; done + + - name: Run unit tests + run: | + # Create a minimal app bundle so Python has a CFBundleIdentifier. + # CEF 130+ forms the MachPortRendezvousServer bootstrap service name + # as BaseBundleID()+".MachPortRendezvousServer."+pid. Without a + # bundle ID the name starts with "." which bootstrap_register rejects, + # causing renderer subprocesses to crash. Setting CFProcessPath + # before Python starts makes CFBundleGetMainBundle() return this + # bundle, giving BaseBundleID() a valid value before CefInitialize(). + BUNDLE_DIR="${{ github.workspace }}/CEFPython.app" + mkdir -p "$BUNDLE_DIR/Contents/MacOS" + ln -sf "$(which python)" "$BUNDLE_DIR/Contents/MacOS/python" + cat > "$BUNDLE_DIR/Contents/Info.plist" << 'PLIST' + + + + + CFBundleIdentifier + org.cefpython + CFBundleName + CEFPython + CFBundleExecutable + python + + + PLIST + PYTHONPATH="${{ github.workspace }}" \ + CFProcessPath="$BUNDLE_DIR/Contents/MacOS/python" \ + python unittests/_test_runner.py + + wheel: + needs: test + runs-on: macos-14 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-py${{ matrix.python-version }}-macosarm64 + path: build/artifacts/ + + - name: Set up cefpython3 package + run: cp -r build/artifacts/. cefpython3/ + + - name: Codesign binaries for distribution + run: | + # Ad-hoc sign the subprocess binary so macOS 14+ Gatekeeper allows + # it to execute when users install the wheel. Without signing, + # macOS blocks the unsigned binary and the GPU process (which uses + # the same binary with --type=gpu-process) exits with code 1003 + # (GPU_DEAD_ON_ARRIVAL), causing a fatal crash. + codesign --force --sign - cefpython3/subprocess + for f in cefpython3/cefpython_py*.so; do codesign --force --sign - "$f"; done + + - name: Build wheel + run: python tools/build_distrib.py + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: cefpython3-py${{ matrix.python-version }}-macosarm64 + path: build/dist/*.whl diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml new file mode 100644 index 000000000..399ad0f98 --- /dev/null +++ b/.github/workflows/ci-windows.yml @@ -0,0 +1,123 @@ +name: CI Windows + +on: + push: + branches: [ "master", "cefpython147" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + inputs: + bypass_cache: + description: "Bypass all caches for a clean run" + type: boolean + default: false + +jobs: + download-cef: + runs-on: windows-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Read CEF version + id: cef-version + run: | + $ver = (Select-String -Path src/version/cef_version_win.h ` + -Pattern '#define CEF_VERSION "([^"]+)"').Matches[0].Groups[1].Value + echo "value=$ver" >> $env:GITHUB_OUTPUT + + - name: Cache CEF binaries + uses: actions/cache@v4 + if: ${{ inputs.bypass_cache != true }} + with: + path: | + build/cef_binary_* + build/cef*_win64 + key: cef-windows64-${{ steps.cef-version.outputs.value }} + + - name: Install build tools + run: python tools/requirements.py + + - name: Download CEF binaries + run: python tools/download_cef.py + + wheel: + needs: download-cef + runs-on: windows-latest + timeout-minutes: 75 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Read CEF version + id: cef-version + run: | + $ver = (Select-String -Path src/version/cef_version_win.h ` + -Pattern '#define CEF_VERSION "([^"]+)"').Matches[0].Groups[1].Value + echo "value=$ver" >> $env:GITHUB_OUTPUT + + - name: Restore CEF cache + uses: actions/cache/restore@v4 + if: ${{ inputs.bypass_cache != true }} + with: + path: | + build/cef_binary_* + build/cef*_win64 + key: cef-windows64-${{ steps.cef-version.outputs.value }} + + - name: Install build tools + run: python tools/requirements.py + + - name: Prepare prebuilt CEF + run: python tools/automate.py --prebuilt-cef + + - name: Configure CMake + run: cmake -S . -B build/_cmake_build -A x64 + + - name: Build + run: cmake --build build/_cmake_build --config Release --parallel + + - name: Stage build outputs + run: | + New-Item -ItemType Directory -Force build/artifacts + Copy-Item build/_cmake_build/Release/cefpython_py*.pyd build/artifacts/ + Copy-Item build/_cmake_build/subprocess_build/Release/subprocess.exe build/artifacts/ + $cefBin = (Get-ChildItem build/cef*_win64 -Directory | Select-Object -First 1).FullName + "/bin" + Get-ChildItem $cefBin | Where-Object { $_.Name -notmatch '^(cefclient|cefsimple|ceftests)' } | + ForEach-Object { Copy-Item $_.FullName build/artifacts/ -Recurse -Force } + + - name: Set up cefpython3 package + run: | + Get-ChildItem build/artifacts/ | ForEach-Object { + Copy-Item $_.FullName cefpython3/ -Recurse -Force + } + + - name: Run unit tests + run: python unittests/_test_runner.py + env: + PYTHONPATH: ${{ github.workspace }} + + - name: Build wheel + run: python tools/build_distrib.py + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: cefpython3-py${{ matrix.python-version }}-win64 + path: build/dist/*.whl diff --git a/.gitignore b/.gitignore index b786188bc..e377f35ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,22 @@ .idea/ build/ dist/ +cefpython3/*.pyd +cefpython3/*.dll +cefpython3/*.exe +cefpython3/*.so +cefpython3/*.so.* +cefpython3/subprocess +cefpython3/chrome_crashpad_handler +cefpython3/Chromium Embedded Framework.framework/ +cefpython3/*.pak +cefpython3/*.dat +cefpython3/*.bin +cefpython3/*.json +cefpython3/locales/ +_cmake_test/ +_skbuild/ +venv/ *.log __pycache__/ *.pyc @@ -16,3 +32,6 @@ unittests/GPUCache/ unittests/blob_storage/ unittests/webrtc_event_logs/ .DS_Store +MediaDeviceSalts +MediaDeviceSalts-journal +.claude/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..777e19134 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,285 @@ +cmake_minimum_required(VERSION 3.21) + +# Read CEF major version from the Windows header (used on all platforms at +# configure time; the per-platform header values are identical for this field). +file(STRINGS "src/version/cef_version_win.h" _ver_line + REGEX "^#define CHROME_VERSION_MAJOR ") +string(REGEX REPLACE "^#define CHROME_VERSION_MAJOR ([0-9]+).*" "\\1" _chrome_major "${_ver_line}") + +project(cefpython3 VERSION "${_chrome_major}.0" LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# ---- Python ----------------------------------------------------------------- +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + +# "310" for Python 3.10, "314" for Python 3.14, etc. +string(CONCAT PYVERSION "${Python_VERSION_MAJOR}" "${Python_VERSION_MINOR}") +set(MODULE_NAME "cefpython_py${PYVERSION}") +message(STATUS "Python ${Python_VERSION_MAJOR}.${Python_VERSION_MINOR} -> module: ${MODULE_NAME}") + +# ---- Cython ----------------------------------------------------------------- +find_program(CYTHON_EXECUTABLE NAMES cython cython3 REQUIRED) +message(STATUS "Cython: ${CYTHON_EXECUTABLE}") + +option(ENABLE_PROFILING "Cython: enable cProfile instrumentation (profile=True)" OFF) +option(ENABLE_LINE_TRACING "Cython: enable line-level tracing/coverage (linetrace=True)" OFF) + +set(_cython_directive_args "") +if(ENABLE_PROFILING) + list(APPEND _cython_directive_args --directive profile=True) +endif() +if(ENABLE_LINE_TRACING) + list(APPEND _cython_directive_args --directive linetrace=True) +endif() + +# ---- CEF location ----------------------------------------------------------- +if(NOT CEF_ROOT) + # Auto-detect: build/cef*_win64 (versioned dir created by automate.py) + if(WIN32) + set(_cef_glob_pat "${CMAKE_SOURCE_DIR}/build/cef*_win64") + elseif(APPLE) + set(_cef_glob_pat "${CMAKE_SOURCE_DIR}/build/cef*_macarm64") + else() + set(_cef_glob_pat "${CMAKE_SOURCE_DIR}/build/cef*_linux64") + endif() + file(GLOB _cef_candidates LIST_DIRECTORIES true "${_cef_glob_pat}") + # Prefer the versioned directory over cef_ fallback + foreach(_d IN LISTS _cef_candidates) + if(IS_DIRECTORY "${_d}") + set(CEF_ROOT "${_d}") + break() + endif() + endforeach() + if(NOT CEF_ROOT) + message(FATAL_ERROR + "CEF_ROOT not set and cannot be auto-detected. " + "Download and prepare CEF first (python tools/download_cef.py && " + "python tools/automate.py --prebuilt-cef), or pass -DCEF_ROOT=.") + endif() +endif() +message(STATUS "CEF_ROOT: ${CEF_ROOT}") + +set(SRC_DIR "${CMAKE_SOURCE_DIR}/src") +set(PYX_STAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/pyx_stage") + +# ---- Prepare .pyx files ----------------------------------------------------- +# Copies src/*.pyx + src/handlers/*.pyx to PYX_STAGE_DIR, flattening the +# include paths and injecting version variables into the main file. +if(WIN32) + set(_cef_ver_header "${SRC_DIR}/version/cef_version_win.h") +elseif(APPLE) + set(_cef_ver_header "${SRC_DIR}/version/cef_version_macarm64.h") +else() + set(_cef_ver_header "${SRC_DIR}/version/cef_version_linux.h") +endif() + +file(GLOB _pyx_src_files + "${SRC_DIR}/*.pyx" + "${SRC_DIR}/handlers/*.pyx" +) +set(MAIN_PYX "${PYX_STAGE_DIR}/${MODULE_NAME}.pyx") + +add_custom_command( + OUTPUT "${MAIN_PYX}" + COMMAND "${Python_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/tools/cmake_prepare_pyx.py" + --src "${SRC_DIR}" + --out "${PYX_STAGE_DIR}" + --pyversion "${PYVERSION}" + --cef-version-header "${_cef_ver_header}" + DEPENDS + ${_pyx_src_files} + "${CMAKE_SOURCE_DIR}/tools/cmake_prepare_pyx.py" + COMMENT "Preparing .pyx files -> ${PYX_STAGE_DIR}" + VERBATIM +) +add_custom_target(cefpython_pyx DEPENDS "${MAIN_PYX}") + +# ---- Transpile pyx -> cpp ---------------------------------------------------- +set(CEFPYTHON_CPP "${PYX_STAGE_DIR}/${MODULE_NAME}.cpp") +set(CEFPYTHON_H "${PYX_STAGE_DIR}/${MODULE_NAME}.h") + +set(_cython_inc_args + -I "${SRC_DIR}" + -I "${SRC_DIR}/common" + -I "${SRC_DIR}/extern" + -I "${SRC_DIR}/extern/cef" +) + +add_custom_command( + OUTPUT "${CEFPYTHON_CPP}" "${CEFPYTHON_H}" + COMMAND "${CYTHON_EXECUTABLE}" + --cplus --3str + ${_cython_inc_args} + ${_cython_directive_args} + -o "${CEFPYTHON_CPP}" + "${MAIN_PYX}" + DEPENDS "${MAIN_PYX}" + COMMENT "Cython: ${MODULE_NAME}.pyx -> C++" + VERBATIM +) + +# ---- Fix Cython API header -------------------------------------------------- +# Generates: +# cefpython_pyXX_fixed.h – original .h with #pragma warning(disable:4190) +# cefpython_api_fixed.h – stable single-name wrapper for cefpython_public_api.h +set(CEFPYTHON_H_FIXED "${PYX_STAGE_DIR}/${MODULE_NAME}_fixed.h") +set(CEF_API_FIXED_H "${PYX_STAGE_DIR}/cefpython_api_fixed.h") + +add_custom_command( + OUTPUT "${CEFPYTHON_H_FIXED}" "${CEF_API_FIXED_H}" + COMMAND "${Python_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/tools/cmake_fix_header.py" + "${CEFPYTHON_H}" + "${CEFPYTHON_H_FIXED}" + "${CEF_API_FIXED_H}" + "${MODULE_NAME}" + DEPENDS "${CEFPYTHON_H}" "${CMAKE_SOURCE_DIR}/tools/cmake_fix_header.py" + COMMENT "Generating ${MODULE_NAME}_fixed.h + cefpython_api_fixed.h" + VERBATIM +) +add_custom_target(cefpython_headers + DEPENDS "${CEFPYTHON_H_FIXED}" "${CEF_API_FIXED_H}") + +# ---- Static C++ libraries --------------------------------------------------- +# All three libs share these settings so pass them via cache variables that +# the subdirectory CMakeLists files can read. +set(CEFPYTHON_PYX_STAGE_DIR "${PYX_STAGE_DIR}" CACHE INTERNAL "") +set(CEFPYTHON_CEF_ROOT "${CEF_ROOT}" CACHE INTERNAL "") +set(CEFPYTHON_SRC_DIR "${SRC_DIR}" CACHE INTERNAL "") + +add_subdirectory("${SRC_DIR}/client_handler" + "${CMAKE_CURRENT_BINARY_DIR}/client_handler") +add_subdirectory("${SRC_DIR}/subprocess" + "${CMAKE_CURRENT_BINARY_DIR}/subprocess_build") +add_subdirectory("${SRC_DIR}/cpp_utils" + "${CMAKE_CURRENT_BINARY_DIR}/cpp_utils") + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig REQUIRED) + pkg_check_modules(GTK3 REQUIRED gtk+-3.0) +endif() + +# ---- Python extension ------------------------------------------------------- +Python_add_library(${MODULE_NAME} MODULE "${CEFPYTHON_CPP}") +add_dependencies(${MODULE_NAME} cefpython_headers) + +target_include_directories(${MODULE_NAME} PRIVATE + "${SRC_DIR}" + "${SRC_DIR}/common" + "${SRC_DIR}/extern" + "${SRC_DIR}/extern/cef" + "${PYX_STAGE_DIR}" +) + +if(ENABLE_LINE_TRACING) + target_compile_definitions(${MODULE_NAME} PRIVATE CYTHON_TRACE=1) +endif() + +if(WIN32) + target_include_directories(${MODULE_NAME} PRIVATE "${SRC_DIR}/windows") + target_compile_options(${MODULE_NAME} PRIVATE /EHsc /wd4305 /wd4190) + target_compile_definitions(${MODULE_NAME} PRIVATE + WIN32 _WIN32 _WINDOWS + NTDDI_VERSION=0x06010000 WINVER=0x0601 _WIN32_WINNT=0x0601 + NDEBUG _NDEBUG _CRT_SECURE_NO_WARNINGS NOMINMAX + BROWSER_PROCESS + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h" + ) + target_link_libraries(${MODULE_NAME} PRIVATE + cefpython_app + client_handler + cpp_utils + "${CEF_ROOT}/lib/libcef.lib" + "${CEF_ROOT}/lib/VS2015/libcef_dll_wrapper_MD.lib" + User32 + ) + target_link_options(${MODULE_NAME} PRIVATE /ignore:4217) +elseif(APPLE) + target_compile_options(${MODULE_NAME} PRIVATE + -DNDEBUG -O3 + -Wno-return-type-c-linkage -Wno-constant-logical-operand + -stdlib=libc++ -fno-strict-aliasing -fno-rtti + -fno-threadsafe-statics -fobjc-call-cxx-cdtors + -fvisibility=hidden -fvisibility-inlines-hidden + ) + target_compile_definitions(${MODULE_NAME} PRIVATE + BROWSER_PROCESS + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h" + ) + target_link_options(${MODULE_NAME} PRIVATE + -mmacosx-version-min=11.0 + -Wl,-search_paths_first -Wl,-dead_strip + "-F${CEF_ROOT}/bin" "-framework" "Chromium Embedded Framework" + "-Wl,-rpath,@loader_path/" + ) + target_link_libraries(${MODULE_NAME} PRIVATE + c++ c++abi + cefpython_app client_handler cpp_utils + "${CEF_ROOT}/lib/libcef_dll_wrapper.a" + ) +else() + target_compile_options(${MODULE_NAME} PRIVATE + -DNDEBUG -O3 + -flto -fdata-sections -ffunction-sections + -Wno-stringop-overflow + ) + target_compile_definitions(${MODULE_NAME} PRIVATE + BROWSER_PROCESS + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h" + ) + target_include_directories(${MODULE_NAME} PRIVATE + ${GTK3_INCLUDE_DIRS} + ) + target_link_options(${MODULE_NAME} PRIVATE + -flto -Wl,--gc-sections + -Wno-stringop-overflow + ) + target_link_libraries(${MODULE_NAME} PRIVATE + X11 ${GTK3_LIBRARIES} + cefpython_app client_handler cpp_utils + "${CEF_ROOT}/lib/libcef_dll_wrapper.a" + ) +endif() + +# ---- Install ---------------------------------------------------------------- +# The extension module itself +install(TARGETS ${MODULE_NAME} + LIBRARY DESTINATION "cefpython3" + RUNTIME DESTINATION "cefpython3" +) + +# subprocess executable (OUTPUT_NAME "subprocess" is set in src/subprocess/CMakeLists.txt) +install(TARGETS cefpython_subprocess + RUNTIME DESTINATION "cefpython3" +) + +# CEF runtime files (DLLs, .pak, .dat, locales/, etc.) +# Sample apps (cefclient, cefsimple, ceftests) are excluded. +if(WIN32) + install(DIRECTORY "${CEF_ROOT}/bin/" + DESTINATION "cefpython3" + PATTERN "cefclient*" EXCLUDE + PATTERN "cefsimple*" EXCLUDE + PATTERN "ceftests*" EXCLUDE + PATTERN "chrome-sandbox" EXCLUDE + ) +elseif(APPLE) + install(DIRECTORY "${CEF_ROOT}/bin/" + DESTINATION "cefpython3" + PATTERN "cefclient*" EXCLUDE + PATTERN "cefsimple*" EXCLUDE + PATTERN "ceftests*" EXCLUDE + ) +else() + install(DIRECTORY "${CEF_ROOT}/bin/" + DESTINATION "cefpython3" + PATTERN "cefclient*" EXCLUDE + PATTERN "cefsimple*" EXCLUDE + PATTERN "ceftests*" EXCLUDE + PATTERN "chrome-sandbox" EXCLUDE + ) +endif() diff --git a/README.md b/README.md index 4ff688b09..4fa42ac40 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,11 @@ You can also download packages for offline installation available on the [GitHub Below is a table with supported platforms, python versions and architectures. -OS | Py2 | Py3 | 32bit | 64bit | Requirements ---- | --- | --- | --- | --- | --- -Windows | 2.7 | 3.4 / 3.5 / 3.6 / 3.7 / 3.8 / 3.9 | Yes | Yes | Windows 7+ (Note that Python 3.9 supports Windows 8.1+) -Linux | 2.7 | 3.4 / 3.5 / 3.6 / 3.7 | Yes | Yes | Debian 8+, Ubuntu 14.04+,
Fedora 24+, openSUSE 13.3+ -Mac | 2.7 | 3.4 / 3.5 / 3.6 / 3.7 | No | Yes | MacOS 10.9+ +OS | Python | 32bit | 64bit | Requirements +--- | --- | --- | --- | --- +Windows | 3.10 / 3.11 / 3.12 / 3.13 / 3.14 | No | Yes | Windows 10+ +Linux | 3.10 / 3.11 / 3.12 / 3.13 / 3.14 | No | Yes | Ubuntu 20.04+, Debian 11+ +Mac | 3.10 / 3.11 / 3.12 / 3.13 / 3.14 | No | Yes | macOS 10.15+ ## Examples @@ -87,7 +87,7 @@ Mac | 2.7 | 3.4 / 3.5 / 3.6 / 3.7 | No | Yes | MacOS 10.9+ [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 side pane. + "Markdown" in the side pane ## Support development @@ -195,7 +195,6 @@ at this moment. * [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 @@ -230,7 +229,6 @@ at this moment. * [_OnAccessibilityTreeChange](api/AccessibilityHandler.md#_onaccessibilitytreechange) * [_OnAccessibilityLocationChange](api/AccessibilityHandler.md#_onaccessibilitylocationchange) * [Application settings](api/ApplicationSettings.md#application-settings) - * [accept_language_list](api/ApplicationSettings.md#accept_language_list) * [app_user_model_id](api/ApplicationSettings.md#app_user_model_id) * [auto_zooming](api/ApplicationSettings.md#auto_zooming) * [background_color](api/ApplicationSettings.md#background_color) @@ -241,7 +239,6 @@ at this moment. * [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) @@ -261,7 +258,6 @@ at this moment. * [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) @@ -308,7 +304,6 @@ at this moment. * [Invalidate](api/Browser.md#invalidate) * [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) @@ -326,13 +321,11 @@ at this moment. * [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) * [SetAccessibilityState](api/Browser.md#setaccessibilitystate) * [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) @@ -346,27 +339,22 @@ at this moment. * [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) * [inherit_client_handlers_for_popups](api/BrowserSettings.md#inherit_client_handlers_for_popups) * [image_load_disabled](api/BrowserSettings.md#image_load_disabled) * [javascript_disabled](api/BrowserSettings.md#javascript_disabled) * [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) @@ -427,9 +415,6 @@ at this moment. * [GetExpires](api/Cookie.md#getexpires) * [CookieManager (class)](api/CookieManager.md#cookiemanager-class) * [GetGlobalManager](api/CookieManager.md#getglobalmanager) - * [GetBlockingManager](api/CookieManager.md#getblockingmanager) - * [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) @@ -449,7 +434,6 @@ at this moment. * [DownloadHandler](api/DownloadHandler.md#downloadhandler) * [DpiAware (class)](api/DpiAware.md#dpiaware-class) * [CalculateWindowSize](api/DpiAware.md#calculatewindowsize) - * [EnableHighDpiSupport](api/DpiAware.md#enablehighdpisupport) * [GetSystemDpi](api/DpiAware.md#getsystemdpi) * [IsProcessDpiAware](api/DpiAware.md#isprocessdpiaware) * [SetProcessDpiAware](api/DpiAware.md#setprocessdpiaware) @@ -616,10 +600,7 @@ at this moment. * [GetResourceType](api/Request.md#getresourcetype) * [GetTransitionType](api/Request.md#gettransitiontype) * [RequestHandler (interface)](api/RequestHandler.md#requesthandler-interface) - * [CanGetCookies](api/RequestHandler.md#cangetcookies) - * [CanSetCookie](api/RequestHandler.md#cansetcookie) * [GetAuthCredentials](api/RequestHandler.md#getauthcredentials) - * [GetCookieManager](api/RequestHandler.md#getcookiemanager) * [GetResourceHandler](api/RequestHandler.md#getresourcehandler) * [OnBeforeBrowse](api/RequestHandler.md#onbeforebrowse) * [_OnBeforePluginLoad](api/RequestHandler.md#_onbeforepluginload) @@ -657,11 +638,6 @@ at this moment. * [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) diff --git a/api/API-categories.md b/api/API-categories.md index d1d7320e8..024694cd6 100644 --- a/api/API-categories.md +++ b/api/API-categories.md @@ -30,7 +30,6 @@ * [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 diff --git a/api/API-index.md b/api/API-index.md index 9535cb5b7..f69893db4 100644 --- a/api/API-index.md +++ b/api/API-index.md @@ -6,7 +6,6 @@ * [_OnAccessibilityTreeChange](AccessibilityHandler.md#_onaccessibilitytreechange) * [_OnAccessibilityLocationChange](AccessibilityHandler.md#_onaccessibilitylocationchange) * [Application settings](ApplicationSettings.md#application-settings) - * [accept_language_list](ApplicationSettings.md#accept_language_list) * [app_user_model_id](ApplicationSettings.md#app_user_model_id) * [auto_zooming](ApplicationSettings.md#auto_zooming) * [background_color](ApplicationSettings.md#background_color) @@ -17,7 +16,6 @@ * [downloads_enabled](ApplicationSettings.md#downloads_enabled) * [external_message_pump](ApplicationSettings.md#external_message_pump) * [framework_dir_path](ApplicationSettings.md#framework_dir_path) - * [ignore_certificate_errors](ApplicationSettings.md#ignore_certificate_errors) * [javascript_flags](ApplicationSettings.md#javascript_flags) * [locale](ApplicationSettings.md#locale) * [locales_dir_path](ApplicationSettings.md#locales_dir_path) @@ -37,7 +35,6 @@ * [uncaught_exception_stack_size](ApplicationSettings.md#uncaught_exception_stack_size) * [unique_request_context_per_browser](ApplicationSettings.md#unique_request_context_per_browser) * [user_agent](ApplicationSettings.md#user_agent) - * [user_data_path](ApplicationSettings.md#user_data_path) * [windowless_rendering_enabled](ApplicationSettings.md#windowless_rendering_enabled) * [Browser (object)](Browser.md#browser-object) * [AddWordToDictionary](Browser.md#addwordtodictionary) @@ -84,7 +81,6 @@ * [Invalidate](Browser.md#invalidate) * [IsFullscreen](Browser.md#isfullscreen) * [IsLoading](Browser.md#isloading) - * [IsMouseCursorChangeDisabled](Browser.md#ismousecursorchangedisabled) * [IsPopup](Browser.md#ispopup) * [IsWindowRenderingDisabled](Browser.md#iswindowrenderingdisabled) * [LoadUrl](Browser.md#loadurl) @@ -102,13 +98,11 @@ * [SendMouseClickEvent](Browser.md#sendmouseclickevent) * [SendMouseMoveEvent](Browser.md#sendmousemoveevent) * [SendMouseWheelEvent](Browser.md#sendmousewheelevent) - * [SendFocusEvent](Browser.md#sendfocusevent) * [SendCaptureLostEvent](Browser.md#sendcapturelostevent) * [SetAccessibilityState](Browser.md#setaccessibilitystate) * [SetClientCallback](Browser.md#setclientcallback) * [SetClientHandler](Browser.md#setclienthandler) * [SetFocus](Browser.md#setfocus) - * [SetMouseCursorChangeDisabled](Browser.md#setmousecursorchangedisabled) * [SetJavascriptBindings](Browser.md#setjavascriptbindings) * [SetUserData](Browser.md#setuserdata) * [SetZoomLevel](Browser.md#setzoomlevel) @@ -122,27 +116,22 @@ * [WasHidden](Browser.md#washidden) * [Browser settings](BrowserSettings.md#browser-settings) * [Font settings](BrowserSettings.md#font-settings) - * [accept_language_list](BrowserSettings.md#accept_language_list) * [application_cache_disabled](BrowserSettings.md#application_cache_disabled) * [background_color](BrowserSettings.md#background_color) * [databases_disabled](BrowserSettings.md#databases_disabled) * [default_encoding](BrowserSettings.md#default_encoding) * [dom_paste_disabled](BrowserSettings.md#dom_paste_disabled) - * [file_access_from_file_urls_allowed](BrowserSettings.md#file_access_from_file_urls_allowed) * [inherit_client_handlers_for_popups](BrowserSettings.md#inherit_client_handlers_for_popups) * [image_load_disabled](BrowserSettings.md#image_load_disabled) * [javascript_disabled](BrowserSettings.md#javascript_disabled) * [javascript_close_windows_disallowed](BrowserSettings.md#javascript_close_windows_disallowed) * [javascript_access_clipboard_disallowed](BrowserSettings.md#javascript_access_clipboard_disallowed) * [local_storage_disabled](BrowserSettings.md#local_storage_disabled) - * [plugins_disabled](BrowserSettings.md#plugins_disabled) * [remote_fonts](BrowserSettings.md#remote_fonts) * [shrink_standalone_images_to_fit](BrowserSettings.md#shrink_standalone_images_to_fit) * [tab_to_links_disabled](BrowserSettings.md#tab_to_links_disabled) * [text_area_resize_disabled](BrowserSettings.md#text_area_resize_disabled) - * [universal_access_from_file_urls_allowed](BrowserSettings.md#universal_access_from_file_urls_allowed) * [user_style_sheet_location](BrowserSettings.md#user_style_sheet_location) - * [web_security_disabled](BrowserSettings.md#web_security_disabled) * [webgl_disabled](BrowserSettings.md#webgl_disabled) * [windowless_frame_rate](BrowserSettings.md#windowless_frame_rate) * [Callback (object)](Callback.md#callback-object) @@ -203,9 +192,6 @@ * [GetExpires](Cookie.md#getexpires) * [CookieManager (class)](CookieManager.md#cookiemanager-class) * [GetGlobalManager](CookieManager.md#getglobalmanager) - * [GetBlockingManager](CookieManager.md#getblockingmanager) - * [CreateManager](CookieManager.md#createmanager) - * [SetSupportedSchemes](CookieManager.md#setsupportedschemes) * [VisitAllCookies](CookieManager.md#visitallcookies) * [VisitUrlCookies](CookieManager.md#visiturlcookies) * [SetCookie](CookieManager.md#setcookie) @@ -225,7 +211,6 @@ * [DownloadHandler](DownloadHandler.md#downloadhandler) * [DpiAware (class)](DpiAware.md#dpiaware-class) * [CalculateWindowSize](DpiAware.md#calculatewindowsize) - * [EnableHighDpiSupport](DpiAware.md#enablehighdpisupport) * [GetSystemDpi](DpiAware.md#getsystemdpi) * [IsProcessDpiAware](DpiAware.md#isprocessdpiaware) * [SetProcessDpiAware](DpiAware.md#setprocessdpiaware) @@ -392,10 +377,7 @@ * [GetResourceType](Request.md#getresourcetype) * [GetTransitionType](Request.md#gettransitiontype) * [RequestHandler (interface)](RequestHandler.md#requesthandler-interface) - * [CanGetCookies](RequestHandler.md#cangetcookies) - * [CanSetCookie](RequestHandler.md#cansetcookie) * [GetAuthCredentials](RequestHandler.md#getauthcredentials) - * [GetCookieManager](RequestHandler.md#getcookiemanager) * [GetResourceHandler](RequestHandler.md#getresourcehandler) * [OnBeforeBrowse](RequestHandler.md#onbeforebrowse) * [_OnBeforePluginLoad](RequestHandler.md#_onbeforepluginload) @@ -433,11 +415,6 @@ * [OnContextCreated](V8ContextHandler.md#oncontextcreated) * [OnContextReleased](V8ContextHandler.md#oncontextreleased) * [Virtual Key codes](VirtualKey.md#virtual-key-codes) -* [WebPluginInfo (object)](WebPluginInfo.md#webplugininfo-object) - * [GetName](WebPluginInfo.md#getname) - * [GetPath](WebPluginInfo.md#getpath) - * [GetVersion](WebPluginInfo.md#getversion) - * [GetDescription](WebPluginInfo.md#getdescription) * [WebRequest (class)](WebRequest.md#webrequest-class) * [Create](WebRequest.md#create) * [GetRequest](WebRequest.md#getrequest) diff --git a/api/ApplicationSettings.md b/api/ApplicationSettings.md index a9ad20f1b..d07327765 100644 --- a/api/ApplicationSettings.md +++ b/api/ApplicationSettings.md @@ -7,7 +7,6 @@ Table of contents: * [Introduction](#introduction) * [Settings](#settings) - * [accept_language_list](#accept_language_list) * [app_user_model_id](#app_user_model_id) * [auto_zooming](#auto_zooming) * [background_color](#background_color) @@ -38,7 +37,6 @@ Table of contents: * [uncaught_exception_stack_size](#uncaught_exception_stack_size) * [unique_request_context_per_browser](#unique_request_context_per_browser) * [user_agent](#user_agent) - * [user_data_path](#user_data_path) * [windowless_rendering_enabled](#windowless_rendering_enabled) @@ -57,18 +55,6 @@ to Chromium Preferences. ## Settings -### accept_language_list - -(string) -Comma delimited ordered list of language codes without any -whitespace that -will be used in the "Accept-Language" HTTP header. May be overridden on a -per-browser basis using the CefBrowserSettings.accept_language_list value. -If both values are empty then "en-US,en" will be used. Can be overridden -for individual CefRequestContext instances via the -CefRequestContextSettings.accept_language_list value. - - ### app_user_model_id This is setting is applied only on Windows. @@ -231,40 +217,6 @@ the "framework-dir-path" command-line switch. See also [Issue #304](../../../issues/304). -### ignore_certificate_errors - -(bool) -Set to true (1) to ignore errors related to invalid SSL certificates. -Enabling this setting can lead to potential security vulnerabilities like -"man in the middle" attacks. Applications that load content from the -internet should not enable this setting. Also configurable using the -"ignore-certificate-errors" [command-line switch](CommandLineSwitches.md). -Can be overridden for individual CefRequestContext instances via the -CefRequestContextSettings.ignore_certificate_errors value. - -__IMPORTANT__: This option not only ignores all certificate errors, -but it also enables caching of content due to custom patch being -applied (read more in "NOTE ON CACHING" further down). If you don't -want this caching feature of insecure content then alternatively you -can ignore certificate errors using the -RequestHandler.[_OnCertificateError()](#_oncertificateerror) -callback. Note that disk caching is enabled only when the "cache_path" -option is set. - -__NOTE ON CACHING__: Chromium by default disallows caching of -content when there is certificate error. There is a issue125.patch -in the patches/ directory that can be enabled when doing a custom -CEF build. This patch changes the caching behavior on sites with SSL -certificate errors when used with this setting. This patch can be -applied Chromium sources to allow for caching even when there is -certificate error, but only when the "ignore_certificate_errors" -option is set to True. -When it's set to False then the Chromium's caching behavior does not -change. Enabling caching with certificate errors is useful on local -private networks that use self-signed SSL certificates. See the -referenced CEF topic in [Issue #125](../../../issues/125) for more details. - - ### javascript_flags (string) @@ -485,17 +437,6 @@ default User-Agent string will be used. Also configurable using the "user-agent" [command-line switch](CommandLineSwitches.md). -### user_data_path - -(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 -profile directory on Windows). - - ### windowless_rendering_enabled (bool) diff --git a/api/Browser.md b/api/Browser.md index b34b90e2f..c97b2d04f 100644 --- a/api/Browser.md +++ b/api/Browser.md @@ -36,16 +36,13 @@ Table of contents: * [GetClientCallback](#getclientcallback) * [GetClientCallbacksDict](#getclientcallbacksdict) * [GetFocusedFrame](#getfocusedframe) - * [GetFrame](#getframe) * [GetFrameByIdentifier](#getframebyidentifier) - * [GetFrames](#getframes) - * [GetFrameCount](#getframecount) - * [GetFrameIdentifiers](#getframeidentifiers) + * [GetFrameByName](#getframebyname) * [GetFrameNames](#getframenames) + * [GetFrames](#getframes) * [GetImage](#getimage) * [GetJavascriptBindings](#getjavascriptbindings) * [GetMainFrame](#getmainframe) - * [GetNSTextInputContext](#getnstextinputcontext) * [GetOpenerWindowHandle](#getopenerwindowhandle) * [GetOuterWindowHandle](#getouterwindowhandle) * [GetSetting](#getsetting) @@ -56,14 +53,10 @@ Table of contents: * [GetZoomLevel](#getzoomlevel) * [GoBack](#goback) * [GoForward](#goforward) - * [HandleKeyEventAfterTextInputClient](#handlekeyeventaftertextinputclient) - * [HandleKeyEventBeforeTextInputClient](#handlekeyeventbeforetextinputclient) * [HasDevTools](#hasdevtools) * [HasDocument](#hasdocument) * [Invalidate](#invalidate) * [IsFullscreen](#isfullscreen) - * [IsLoading](#isloading) - * [IsMouseCursorChangeDisabled](#ismousecursorchangedisabled) * [IsPopup](#ispopup) * [IsWindowRenderingDisabled](#iswindowrenderingdisabled) * [LoadUrl](#loadurl) @@ -81,13 +74,11 @@ Table of contents: * [SendMouseClickEvent](#sendmouseclickevent) * [SendMouseMoveEvent](#sendmousemoveevent) * [SendMouseWheelEvent](#sendmousewheelevent) - * [SendFocusEvent](#sendfocusevent) * [SendCaptureLostEvent](#sendcapturelostevent) * [SetAccessibilityState](#setaccessibilitystate) * [SetClientCallback](#setclientcallback) * [SetClientHandler](#setclienthandler) * [SetFocus](#setfocus) - * [SetMouseCursorChangeDisabled](#setmousecursorchangedisabled) * [SetJavascriptBindings](#setjavascriptbindings) * [SetUserData](#setuserdata) * [SetZoomLevel](#setzoomlevel) @@ -110,6 +101,12 @@ Methods available in upstream CEF which were not yet exposed in CEF Python * ImeCommitText * ImeFinishComposingText * ImeCancelComposition +* GetFrameCount +* GetFrameIdentifiers +* GetNSTextInputContext (Mac, OSR) +* HandleKeyEventAfterTextInputClient (Mac, OSR) +* HandleKeyEventBeforeTextInputClient (Mac, OSR) +* IsLoading There are some edge cases when after the OnBeforeClose event browser objects are no more globally referenced thus a new instance is created that @@ -172,7 +169,7 @@ information. | | | | --- | --- | -| __Return__ | bool | +| __Return__ | void | Explicitly close the associated DevTools browser, if any. @@ -322,24 +319,17 @@ Calling javascript from native code synchronously is not possible in CEF 3. It i | Parameter | Type | | --- | --- | -| searchId | int | | searchText | string | | forward | bool | | matchCase | bool | | findNext | bool | | __Return__ | void | -Description from upstream CEF: - -> Search for |searchText|. |identifier| must be a unique ID and these IDs -> must strictly increase so that newer requests always have greater IDs than -> older requests. If |identifier| is zero or less than the previous ID value -> then it will be automatically assigned a new valid ID. |forward| indicates -> whether to search forward or backward within the page. |matchCase| -> indicates whether the search should be case-sensitive. |findNext| indicates -> whether this is the first request or a follow-up. The CefFindHandler -> instance, if any, returned via CefClient::GetFindHandler will be called to -> report find results. +Search for |searchText|. |forward| indicates whether to search forward or +backward within the page. |matchCase| indicates whether the search should be +case-sensitive. |findNext| indicates whether this is the first request or a +follow-up. The CefFindHandler instance, if any, returned via +CefClient::GetFindHandler will be called to report find results. ### GetClientCallback @@ -369,64 +359,46 @@ Get client callbacks as a dictionary. Returns the focused [Frame](Frame.md) for the browser window. -### GetFrame +### GetFrameByIdentifier | Parameter | Type | | --- | --- | -| name | string | +| identifier | string | | __Return__ | Frame | -Returns the [Frame](Frame.md) with the specified name, or NULL if not found. +Returns the [Frame](Frame.md) with the specified identifier, or None if not found. -### GetFrameByIdentifier +### GetFrameByName | Parameter | Type | | --- | --- | -| identifier | long | +| name | string | | __Return__ | Frame | -Available only in CEF 3. Returns the [Frame](Frame.md) with the specified identifier, or None if not found. - - -### GetFrames - -| | | -| --- | --- | -| __Return__ | list | - -Get all frames. This is an internal CEF Python implementation that uses GetFrameNames() and GetFrame() methods to list through all frames. The main frame is not included in that list. - - -### GetFrameCount - -| | | -| --- | --- | -| __Return__ | int | - -Available only in CEF 3. Not yet implemented. - -Returns the number of frames that currently exist. +Returns the [Frame](Frame.md) with the specified name, or None if not found. +This method should only be called on the UI thread. -### GetFrameIdentifiers +### GetFrameNames | | | | --- | --- | -| __Return__ | void | - -Available only in CEF 3. Not yet implemented. +| __Return__ | string[] | -Returns the identifiers of all existing frames. +Returns the names of all existing frames. This list does not include the main frame. +This method should only be called on the UI thread. -### GetFrameNames +### GetFrames | | | | --- | --- | -| __Return__ | string[] | +| __Return__ | list | -Returns the names of all existing frames. This list does not include the main frame. +Get all frames. This is a CEF Python helper that calls GetFrameNames() and +GetFrameByName() to return a list of [Frame](Frame.md) objects. The main frame +is not included in that list. ### GetImage @@ -470,18 +442,6 @@ Returns the [JavascriptBindings](JavascriptBindings.md) object that was passed t Returns the main (top-level) [Frame](Frame.md) for the browser window. -### GetNSTextInputContext - -| | | -| --- | --- | -| __Return__ | TextInputContext | - -Not yet ported. Available only in CEF 3. - -Get the NSTextInputContext implementation for enabling IME on Mac when -window rendering is disabled. - - ### GetOpenerWindowHandle | | | @@ -576,30 +536,6 @@ Navigate backwards. Navigate forwards. -### HandleKeyEventAfterTextInputClient - -| Parameter | Type | -| --- | --- | -| keyEvent | eventHandle | -| __Return__ | void | - -Available only in CEF 3. Not yet implemented. - -Performs any additional actions after NSTextInputClient handles the event. - - -### HandleKeyEventBeforeTextInputClient - -| | | -| --- | --- | -| __Return__ | void | - -Available only in CEF 3. Not yet implemented. - -Handles a keyDown event prior to passing it through the NSTextInputClient -machinery. - - ### HasDevTools | | | @@ -639,35 +575,11 @@ Description from upstream CEF: ### IsFullscreen -| | | -| --- | --- | -| __Return__ | void | - -Whether in fullscreen mode, see ToggleFullscreen(). - -This function is Windows-only. - - -### IsLoading - | | | | --- | --- | | __Return__ | bool | -Available only in CEF 3. Not yet implemented. - -Returns true if the browser is currently loading. - - -### IsMouseCursorChangeDisabled - -| | | -| --- | --- | -| __Return__ | bool | - -Available only in CEF 3. - -Returns true if mouse cursor change is disabled. +Whether in fullscreen mode, see ToggleFullscreen(). Windows-only. ### IsPopup @@ -700,9 +612,8 @@ Load url in the main frame. If the url is a local path it needs to start with the `file://` prefix. If the url contains special characters it may need proper handling. Starting with v66.1+ it is required for the app code to encode the url -properly. You can use the `pathlib.PurePath.as_uri` in Python 3 -or `urllib.pathname2url` in Python 2 (`urllib.request.pathname2url` -in Python 3) depending on your case. +properly. You can use `pathlib.PurePath.as_uri` or +`urllib.request.pathname2url` depending on your case. ### Navigate @@ -795,7 +706,7 @@ this method will replace it with the specified |word|. | --- | --- | | enabled | bool | | min_size | list[width, height] | -| max_size | list[width, heifght] | +| max_size | list[width, height] | | __Return__ | void | Description from upstream CEF: @@ -894,16 +805,6 @@ flags see SendMouseClickEvent(). Send a mouse wheel event to the browser. The |x| and |y| coordinates are relative to the upper-left corner of the view. The |deltaX| and |deltaY| values represent the movement delta in the X and Y directions respectively. In order to scroll inside select popups with window rendering disabled [RenderHandler](RenderHandler.md).GetScreenPoint() should be implemented properly. For a list of modifiers flags see SendMouseClickEvent(). -### SendFocusEvent - -| Parameter | Type | -| --- | --- | -| setFocus | bool | -| __Return__ | void | - -Send a focus event to the browser. - - ### SendCaptureLostEvent | | | @@ -988,16 +889,6 @@ LifespanHandler etc. Set whether the browser is focused. -### SetMouseCursorChangeDisabled - -| Parameter | Type | -| --- | --- | -| disabled | bool | -| __Return__ | void | - -Set whether mouse cursor change is disabled. - - ### SetJavascriptBindings | Parameter | Type | diff --git a/api/BrowserSettings.md b/api/BrowserSettings.md index ed2b2e50d..15ad4bd74 100644 --- a/api/BrowserSettings.md +++ b/api/BrowserSettings.md @@ -8,27 +8,23 @@ Table of contents: * [Introduction](#introduction) * [Settings](#settings) * [Font settings](#font-settings) - * [accept_language_list](#accept_language_list) * [application_cache_disabled](#application_cache_disabled) * [background_color](#background_color) * [databases_disabled](#databases_disabled) * [default_encoding](#default_encoding) * [dom_paste_disabled](#dom_paste_disabled) - * [file_access_from_file_urls_allowed](#file_access_from_file_urls_allowed) * [inherit_client_handlers_for_popups](#inherit_client_handlers_for_popups) * [image_load_disabled](#image_load_disabled) * [javascript_disabled](#javascript_disabled) * [javascript_close_windows_disallowed](#javascript_close_windows_disallowed) * [javascript_access_clipboard_disallowed](#javascript_access_clipboard_disallowed) * [local_storage_disabled](#local_storage_disabled) - * [plugins_disabled](#plugins_disabled) * [remote_fonts](#remote_fonts) * [shrink_standalone_images_to_fit](#shrink_standalone_images_to_fit) * [tab_to_links_disabled](#tab_to_links_disabled) * [text_area_resize_disabled](#text_area_resize_disabled) * [universal_access_from_file_urls_allowed](#universal_access_from_file_urls_allowed) * [user_style_sheet_location](#user_style_sheet_location) - * [web_security_disabled](#web_security_disabled) * [webgl_disabled](#webgl_disabled) * [windowless_frame_rate](#windowless_frame_rate) @@ -60,15 +56,6 @@ In some cases, the default values of settings that are suggested by its name may * minimum_logical_font_size (int) -### accept_language_list - -(string) -Comma delimited ordered list of language codes without any whitespace that -will be used in the "Accept-Language" HTTP header. May be set globally -using the CefBrowserSettings.accept_language_list value. If both values are -empty then "en-US,en" will be used. - - ### application_cache_disabled (bool) Controls whether the application cache can be used. Also configurable using the --disable-application-cache switch. @@ -106,11 +93,6 @@ in a known order. Equivalent to the `SkColor` type in Chromium. (bool) Controls whether DOM pasting is supported in the editor via `execCommand("paste")`. The |javascript_access_clipboard_disallowed| setting must also be set (to true or false). Also configurable using the --disable-javascript-dom-paste switch. -### file_access_from_file_urls_allowed - -(bool) Controls whether file URLs will have access to other file URLs. Also configurable using the --allow-access-from-files switch. Other similar switches are: --allow-file-access and --allow-file-access-from-files. - - ### inherit_client_handlers_for_popups @@ -155,11 +137,6 @@ switch. (bool) Controls whether local storage can be used. Also configurable using the --disable-local-storage switch. -### plugins_disabled - -(bool) Controls whether any plugins will be loaded. Also configurable using the --disable-plugins switch. - - ### remote_fonts (bool) Controls the loading of fonts from remote sources. Also configurable using the --disable-remote-fonts switch. @@ -180,11 +157,6 @@ switch. (bool) Controls whether text areas can be resized. Also configurable using the --disable-text-area-resize switch. -### universal_access_from_file_urls_allowed - -(bool) Controls whether file URLs will have access to all URLs. Also configurable using the --allow-universal-access-from-files switch. Other similar switches are --allow-file-access and --allow-file-access-from-files. - - ### user_style_sheet_location (string) Location of the user style sheet that will be used for all pages. This must be a data URL of the form `data:text/css;charset=utf-8;base64,content` where "content" is the base64 encoded contents of the CSS file. Also configurable using the "user-style-sheet-location" command-line switch. @@ -192,11 +164,6 @@ switch. This setting was removed in Chrome 33. Soon it will be removed from cefpython as well. -### web_security_disabled - -(bool) Controls whether web security restrictions (same-origin policy) will be enforced. Disabling this setting is not recommend as it will allow risky security behavior such as cross-site scripting (XSS). Also configurable using the --disable-web-security switch. - - ### webgl_disabled (bool) Controls whether WebGL can be used. Note that WebGL requires hardware support and may not work on all systems even when enabled. Also configurable using the --disable-webgl switch. diff --git a/api/Cookie.md b/api/Cookie.md index 0a46e99b0..ffa399fe2 100644 --- a/api/Cookie.md +++ b/api/Cookie.md @@ -31,6 +31,10 @@ Table of contents: * [GetHasExpires](#gethasexpires) * [SetExpires](#setexpires) * [GetExpires](#getexpires) + * [SetSameSite](#setsamesite) + * [GetSameSite](#getsamesite) + * [SetPriority](#setpriority) + * [GetPriority](#getpriority) ## Methods @@ -56,6 +60,8 @@ The cookie may have the following keys: - lastAccess (datetime.datetime) - hasExpires (bool) - expires (datetime.datetime) +- sameSite (int) +- priority (int) ### Get @@ -261,3 +267,48 @@ Set the cookie expiration date. You should also call SetHasExpires(). | __Return__ | datetime.datetime | Get the expires property. + + +### SetSameSite + +| Parameter | Type | +| --- | --- | +| sameSite | int | +| __Return__ | void | + +Set the cookie SameSite attribute. Use the `CEF_COOKIE_SAME_SITE_*` constants: +- `CEF_COOKIE_SAME_SITE_UNSPECIFIED` (0) +- `CEF_COOKIE_SAME_SITE_NO_RESTRICTION` (1) +- `CEF_COOKIE_SAME_SITE_LAX_MODE` (2) +- `CEF_COOKIE_SAME_SITE_STRICT_MODE` (3) + + +### GetSameSite + +| | | +| --- | --- | +| __Return__ | int | + +Get the cookie SameSite attribute. See SetSameSite() for possible values. + + +### SetPriority + +| Parameter | Type | +| --- | --- | +| priority | int | +| __Return__ | void | + +Set the cookie priority. Use the `CEF_COOKIE_PRIORITY_*` constants: +- `CEF_COOKIE_PRIORITY_LOW` (-1) +- `CEF_COOKIE_PRIORITY_MEDIUM` (0) +- `CEF_COOKIE_PRIORITY_HIGH` (1) + + +### GetPriority + +| | | +| --- | --- | +| __Return__ | int | + +Get the cookie priority. See SetPriority() for possible values. diff --git a/api/CookieManager.md b/api/CookieManager.md index b0d06a911..c947a3f47 100644 --- a/api/CookieManager.md +++ b/api/CookieManager.md @@ -18,9 +18,6 @@ also have an OnComplete callback. Table of contents: * [Methods](#methods) * [GetGlobalManager](#getglobalmanager) - * [GetBlockingManager](#getblockingmanager) - * [CreateManager](#createmanager) - * [SetSupportedSchemes](#setsupportedschemes) * [VisitAllCookies](#visitallcookies) * [VisitUrlCookies](#visiturlcookies) * [SetCookie](#setcookie) @@ -49,52 +46,6 @@ Description from upstream CEF: > calling CefRequestContext::GetGlobalContext()->GetDefaultCookieManager() -### GetBlockingManager - -| | | -| --- | --- | -| __Return__ | [CookieManager](CookieManager.md) | - -Description from upstream CEF: -> Returns a cookie manager that neither stores nor retrieves cookies. All -> usage of cookies will be blocked including cookies accessed via the network -> (request/response headers), via JavaScript (document.cookie), and via -> CefCookieManager methods. No cookies will be displayed in DevTools. If you -> wish to only block cookies sent via the network use the CefRequestHandler -> CanGetCookies and CanSetCookie methods instead. - - -### CreateManager - -| Parameter | Type | -| --- | --- | -| path | string | -| persistSessionCookies=False | bool | -| __Return__ | [CookieManager](CookieManager.md) | - -Creates a new cookie manager. Otherwise, data will be stored at the -specified |path|. To persist session cookies (cookies without an expiry -date or validity interval) set |persistSessionCookies| -to true. If using global manager then see the [ApplicationSettings](ApplicationSettings.md).`persist_session_cookies` -option. Session cookies are generally intended to be transient and most -Web browsers do not persist them. Returns None if creation fails. - -You can have a separate cookie manager for each browser, -see [RequestHandler](RequestHandler.md).GetCookieManager(). - - -### SetSupportedSchemes - -| Parameter | Type | -| --- | --- | -| schemes | list | -| __Return__ | void | - -Set the schemes supported by this manager. The default schemes ("http", -"https", "ws" and "wss") will always be supported. Must be called before -any cookies are accessed. - - ### VisitAllCookies | Parameter | Type | diff --git a/api/DpiAware.md b/api/DpiAware.md index 5a335b216..a93e3e39e 100644 --- a/api/DpiAware.md +++ b/api/DpiAware.md @@ -12,7 +12,6 @@ Table of contents: * [Introduction](#introduction) * [Static methods](#static-methods) * [CalculateWindowSize](#calculatewindowsize) - * [EnableHighDpiSupport](#enablehighdpisupport) * [GetSystemDpi](#getsystemdpi) * [IsProcessDpiAware](#isprocessdpiaware) * [SetProcessDpiAware](#setprocessdpiaware) @@ -46,21 +45,6 @@ OS DPI settings. For 800/600 with Win7 DPI settings being set to "Larger 150%" will return 1200/900. -### EnableHighDpiSupport - -| | | -| --- | --- | -| __Return__ | void | - -Calling this function will set current process and subprocesses -to be DPI aware. - -Description from upstream CEF: -> Call during process startup to enable High-DPI support on Windows 7 or newer. -> Older versions of Windows should be left DPI-unaware because they do not -> support DirectWrite and GDI fonts are kerned very badly. - - ### GetSystemDpi | | | diff --git a/api/Frame.md b/api/Frame.md index 057129122..284f9b177 100644 --- a/api/Frame.md +++ b/api/Frame.md @@ -25,7 +25,6 @@ Table of contents: * [ExecuteFunction](#executefunction) * [ExecuteJavascript](#executejavascript) * [GetBrowser](#getbrowser) - * [GetParent](#getparent) * [GetIdentifier](#getidentifier) * [GetBrowserIdentifier](#getbrowseridentifier) * [GetName](#getname) @@ -36,7 +35,6 @@ Table of contents: * [IsFocused](#isfocused) * [IsMain](#ismain) * [IsValid](#isvalid) - * [LoadString](#loadstring) * [LoadUrl](#loadurl) * [Paste](#paste) * [Redo](#redo) @@ -107,15 +105,6 @@ Execute a string of JavaScript code in this frame. The sciptUrl parameter is the Returns the browser that this frame belongs to. -### GetParent - -| | | -| --- | --- | -| __Return__ | [Frame](Frame.md) | - -Returns the parent of this frame or None if this is the main (top-level) frame. - - ### GetIdentifier | | | @@ -213,32 +202,6 @@ Returns true if this is the main (top-level) frame. True if this object is currently attached to a valid frame. -### LoadString - -| Parameter | Type | -| --- | --- | -| value | string | -| url | string | -| __Return__ | void | - -NOTE: LoadString is problematic due to the multi-process model and the need -to create a render process (which does not happen with LoadString). It is -best to use instead LoadUrl with a data uri, e.g. `LoadUrl("data:text/html,some+html+code+here")`. -Take also a look at a [custom resource handler](ResourceHandler.md). - -Load the contents of |value| with the specified dummy |url|. |url| -should have a standard scheme (for example, http scheme) or behaviors like -link clicks and web security restrictions may not behave as expected. -LoadString() can be called only after the Renderer process has been created. - -If the url is a local path it needs to start with the `file://` prefix. -If the url contains special characters it may need proper handling. -Starting with v66.1+ it is required for the app code to encode the url -properly. You can use the `pathlib.PurePath.as_uri` in Python 3 -or `urllib.pathname2url` in Python 2 (`urllib.request.pathname2url` -in Python 3) depending on your case. - - ### LoadUrl | Parameter | Type | @@ -246,7 +209,9 @@ in Python 3) depending on your case. | url | string | | __Return__ | void | -Load the specified |url|. +Load the specified |url|. To load HTML content directly use a data URI, +e.g. `LoadUrl("data:text/html,some+html+code+here")`. See also +[ResourceHandler](ResourceHandler.md) for a custom resource handler approach. ### Paste diff --git a/api/LifespanHandler.md b/api/LifespanHandler.md index 1794d62d7..3edd4e677 100644 --- a/api/LifespanHandler.md +++ b/api/LifespanHandler.md @@ -82,42 +82,48 @@ additional usage information. | window_info_out | list[[WindowInfo](WindowInfo.md)] | | client | None | | browser_settings_out | list[[BrowserSettings](BrowserSettings.md)] | +| extra_info_out | dict | | no_javascript_access_out | list[bool] | | __Return__ | bool | Description from upstream CEF: > Called on the UI thread before a new popup browser is created. The -> |browser| and |frame| values represent the source of the popup request. The -> |target_url| and |target_frame_name| values indicate where the popup -> browser should navigate and may be empty if not specified with the request. -> The |target_disposition| value indicates where the user intended to open -> the popup (e.g. current tab, new tab, etc). The |user_gesture| value will -> be true if the popup was opened via explicit user gesture (e.g. clicking a -> link) or false if the popup opened automatically (e.g. via the -> DomContentLoaded event). The |popup_features| structure contains additional +> |browser| and |frame| values represent the source of the popup request. +> The |target_url| and |target_frame_name| values indicate where the popup +> browser should navigate and may be empty if not specified with the +> request. The |target_disposition| value indicates where the user intended +> to open the popup (e.g. current tab, new tab, etc). The |user_gesture| +> value will be true if the popup was opened via explicit user gesture (e.g. +> clicking a link) or false if the popup opened automatically (e.g. via the +> DomContentLoaded event). The |popupFeatures| structure contains additional > information about the requested popup window. To allow creation of the -> popup browser optionally modify |windowInfo|, |client|, |browserSettings| and +> popup browser optionally modify |windowInfo|, |client|, |settings| and > |no_javascript_access| and return false. To cancel creation of the popup -> browser return true. The |client| and |settings| values will default to the -> source browser's values. If the |no_javascript_access| value is set to +> browser return true. The |client| and |settings| values will default to +> the source browser's values. If the |no_javascript_access| value is set to > false the new browser will not be scriptable and may not be hosted in the > same renderer process as the source browser. Any modifications to -> |window_info| will be ignored if the parent browser is wrapped in a +> |windowInfo| will be ignored if the parent browser is wrapped in a > CefBrowserView. Popup browser creation will be canceled if the parent -> browser is destroyed before the popup browser creation completes (indicated -> by a call to OnAfterCreated for the popup browser). +> browser is destroyed before the popup browser creation completes +> (indicated by a call to OnAfterCreated for the popup browser). The +> |extra_info| parameter provides an opportunity to specify extra +> information specific to the created popup browser that will be passed to +> CefRenderProcessHandler::OnBrowserCreated() in the render process. `WindowOpenDisposition` constants in the cefpython module: -* WOD_UNKNOWN, -* WOD_CURRENT_TAB, -* WOD_SINGLETON_TAB, -* WOD_NEW_FOREGROUND_TAB, -* WOD_NEW_BACKGROUND_TAB, -* WOD_NEW_POPUP, -* WOD_NEW_WINDOW, -* WOD_SAVE_TO_DISK, -* WOD_OFF_THE_RECORD, -* WOD_IGNORE_ACTION +* CEF_WOD_UNKNOWN, +* CEF_WOD_CURRENT_TAB, +* CEF_WOD_SINGLETON_TAB, +* CEF_WOD_NEW_FOREGROUND_TAB, +* CEF_WOD_NEW_BACKGROUND_TAB, +* CEF_WOD_NEW_POPUP, +* CEF_WOD_NEW_WINDOW, +* CEF_WOD_SAVE_TO_DISK, +* CEF_WOD_OFF_THE_RECORD, +* CEF_WOD_IGNORE_ACTION, +* CEF_WOD_SWITCH_TO_TAB, +* CEF_WOD_NEW_PICTURE_IN_PICTURE 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. diff --git a/api/PaintBuffer.md b/api/PaintBuffer.md index c6509db21..838d803bb 100644 --- a/api/PaintBuffer.md +++ b/api/PaintBuffer.md @@ -36,7 +36,7 @@ Description from upstream CEF: | origin="top-left" | string | | __Return__ | object | -Converts the `void*` buffer to string. In Py2 returns 'str' type, in Py3 returns 'bytes' type. +Converts the `void*` buffer to bytes. `origin` may be one of: "top-left", "bottom-left". diff --git a/api/Request.md b/api/Request.md index d9d0ece38..6658036b9 100644 --- a/api/Request.md +++ b/api/Request.md @@ -9,7 +9,6 @@ Object of this class is used in [RequestHandler](RequestHandler.md).OnBeforeBrow Table of contents: * [Methods](#methods) * [CreateRequest](#createrequest) - * [IsReadOnly](#isreadonly) * [GetUrl](#geturl) * [SetUrl](#seturl) * [GetMethod](#getmethod) @@ -24,8 +23,6 @@ Table of contents: * [SetFlags](#setflags) * [GetFirstPartyForCookies](#getfirstpartyforcookies) * [SetFirstPartyForCookies](#setfirstpartyforcookies) - * [GetResourceType](#getresourcetype) - * [GetTransitionType](#gettransitiontype) ## Methods @@ -41,15 +38,6 @@ You cannot instantiate `Request` class directly, use this static method instead by calling `cefpython.Request.CreateRequest()`. -### IsReadOnly - -| | | -| --- | --- | -| __Return__ | bool | - -Returns true if this object is read-only. - - ### GetUrl | | | @@ -169,7 +157,7 @@ Get the flags used in combination with WebRequest. Available flags below. Can be accessed via `cefpython.Request.Flags["xxx"]`. These flags are also defined as constants starting with "UR_FLAG_" -in the cefpython module.requ +in the cefpython module. * **None** - Default behavior. * **SkipCache** - If set the cache will be skipped when handling the request. Setting this value is equivalent to specifying the "Cache-Control: no-cache" request header. Setting this value in combination with UR_FLAG_ONLY_FROM_CACHE will cause the request to fail. @@ -215,27 +203,3 @@ Set the url to the first party for cookies used in combination with WebRequest. -### GetResourceType - -| | | -| --- | --- | -| __Return__ | int | - -Not yet implemented in CEF Python. - -Get the resource type for this request. Only available in the browser -process. - - -### GetTransitionType - -| | | -| --- | --- | -| __Return__ | int | - -Not yet implemented in CEF Python. - -Get the transition type for this request. Only available in the browser -process and only applies to requests that represent a main frame or -sub-frame navigation. - diff --git a/api/RequestHandler.md b/api/RequestHandler.md index f1ef8bf72..28c2dc22b 100644 --- a/api/RequestHandler.md +++ b/api/RequestHandler.md @@ -16,13 +16,9 @@ Available in upstream CEF, but not yet exposed to CEF Python: Table of contents: * [Callbacks](#callbacks) - * [CanGetCookies](#cangetcookies) - * [CanSetCookie](#cansetcookie) * [GetAuthCredentials](#getauthcredentials) - * [GetCookieManager](#getcookiemanager) * [GetResourceHandler](#getresourcehandler) * [OnBeforeBrowse](#onbeforebrowse) - * [_OnBeforePluginLoad](#_onbeforepluginload) * [OnBeforeResourceLoad](#onbeforeresourceload) * [_OnCertificateError](#_oncertificateerror) * [OnQuotaRequest](#onquotarequest) @@ -36,39 +32,6 @@ Table of contents: ## Callbacks -### CanGetCookies - -| Parameter | Type | -| --- | --- | -| browser | [Browser](Browser.md) | -| frame | [Frame](Frame.md) | -| request | [Request](Request.md) | -| __Return__ | bool | - -Description from upstream CEF: -> Called on the IO thread before sending a network request with a "Cookie" -> request header. Return true to allow cookies to be included in the network -> request or false to block cookies. The |request| object should not be -> modified in this callback. - - -### CanSetCookie - -| Parameter | Type | -| --- | --- | -| browser | [Browser](Browser.md) | -| frame | [Frame](Frame.md) | -| request | [Request](Request.md) | -| cookie | [Cookie](Cookie.md) | -| __Return__ | bool | - -Description from upstream CEF: -> Called on the IO thread when receiving a network request with a -> "Set-Cookie" response header value represented by |cookie|. Return true to -> allow the cookie to be stored or false to block the cookie. The |request| -> object should not be modified in this callback. - - ### GetAuthCredentials | Parameter | Type | @@ -106,44 +69,6 @@ Example implementations: [[3]](https://github.com/cztomczak/cefpython/blob/cefpython31/cefpython/http_authentication_win.pyx). -### GetCookieManager - -| Parameter | Type | -| --- | --- | -| browser | None or [Browser](Browser.md) | -| main_url | string | -| __Return__ | [CookieManager](CookieManager.md) | - -Called on the IO thread to retrieve the cookie manager. |main_url| -is the URL of the top-level frame. Cookies managers can be unique -per browser or shared across multiple browsers. The global cookie -manager will be used if this method returns None. - -**IMPORTANT**: In some cases this callback is not called due to a -race condition. See Issue [#429](../../../issues/429) for details. - -To successfully implement separate cookie manager per browser session, -you have to set ApplicationSettings.`unique_request_context_per_browser` -to True. Otherwise the browser param passed to this callback will -always be the same first browser that was created using -[cefpython](cefpython.md).`CreateBrowserSync`. - -**NOTE**: If implementing custom cookie managers you will encounter -problems similar to [Issue #365](../../../issues/365) ("Cookies not -flushed to disk when closing app immediately"). To resolve -it you have to call CookieManager.[FlushStore](CookieManager.md#flushstore) -method when closing associated browser. - -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 old v31 wxpython -example does that. - - ### GetResourceHandler | Parameter | Type | @@ -190,48 +115,6 @@ Description from upstream CEF: > navigated automatically (e.g. via the DomContentLoaded event). -### _OnBeforePluginLoad - -| Parameter | Type | -| --- | --- | -| browser | [Browser](Browser.md) | -| mime_type | string | -| plugin_url | string | -| is_main_frame | bool | -| top_origin_url | string | -| plugin_info | [WebPluginInfo](WebPluginInfo.md) | -| __Return__ | bool | - -Description from upstream CEF: -> Called on multiple browser process threads before a plugin instance is -> loaded. |mime_type| is the mime type of the plugin that will be loaded. -> |plugin_url| is the content URL that the plugin will load and may be empty. -> |is_main_frame| will be true if the plugin is being loaded in the main -> (top-level) frame, |top_origin_url| is the URL for the top-level frame that -> contains the plugin when loading a specific plugin instance or empty when -> building the initial list of enabled plugins for 'navigator.plugins' -> JavaScript state. |plugin_info| includes additional information about the -> plugin that will be loaded. |plugin_policy| is the recommended policy. -> Modify |plugin_policy| and return true to change the policy. Return false -> to use the recommended policy. The default plugin policy can be set at -> runtime using the `--plugin-policy=[allow|detect|block]` command-line flag. -> Decisions to mark a plugin as disabled by setting |plugin_policy| to -> PLUGIN_POLICY_DISABLED may be cached when |top_origin_url| is empty. To -> purge the plugin list cache and potentially trigger new calls to this -> method call CefRequestContext::PurgePluginListCache. - -Return True to block loading of the plugin. - -This callback will be executed during browser creation, thus you must -call [cefpython](cefpython.md).SetGlobalClientCallback() to use it. -The callback name was prefixed with "`_`" to distinguish this special -behavior. - -Plugins are loaded on demand, only when website requires it. -This callback is called every time the page tries to load a plugin -(perhaps even multiple times per plugin). - - ### OnBeforeResourceLoad | Parameter | Type | diff --git a/api/Response.md b/api/Response.md index ec742ae9a..1eb113d13 100644 --- a/api/Response.md +++ b/api/Response.md @@ -15,7 +15,7 @@ Table of contents: * [SetStatusText](#setstatustext) * [GetMimeType](#getmimetype) * [SetMimeType](#setmimetype) - * [GetHeader](#getheader) + * [GetHeaderByName](#getheaderbyname) * [GetHeaderMap](#getheadermap) * [GetHeaderMultimap](#getheadermultimap) * [SetHeaderMap](#setheadermap) @@ -91,7 +91,7 @@ Get the response mime type. Set the response mime type. -### GetHeader +### GetHeaderByName | Parameter | Type | | --- | --- | diff --git a/api/WebPluginInfo.md b/api/WebPluginInfo.md deleted file mode 100644 index ad4396d35..000000000 --- a/api/WebPluginInfo.md +++ /dev/null @@ -1,63 +0,0 @@ -[API categories](API-categories.md) | [API index](API-index.md) - - -# WebPluginInfo (object) - -See also [RequestHandler](RequestHandler.md)._OnBeforePluginLoad(). - -Web Plugin API available in upstream CEF, but not yet exposed in CEF Python -(see src/include/cef_web_plugin.h): - -* CefRegisterCdmCallback -* CefRegisterWidevineCdm -* CefIsWebPluginUnstable -* CefRegisterWebPluginCrash -* CefUnregisterInternalWebPlugin -* CefRefreshWebPlugins -* CefVisitWebPluginInfo - - -Table of contents: -* [Methods](#methods) - * [GetName](#getname) - * [GetPath](#getpath) - * [GetVersion](#getversion) - * [GetDescription](#getdescription) - -## Methods - - -### GetName - -| | | -| --- | --- | -| __Return__ | string | - -Returns the plugin name (i.e. Flash). - - -### GetPath - -| | | -| --- | --- | -| __Return__ | string | - -Returns the plugin file path (DLL/bundle/library). - - -### GetVersion - -| | | -| --- | --- | -| __Return__ | string | - -Returns the version of the plugin (may be OS-specific). - - -### GetDescription - -| | | -| --- | --- | -| __Return__ | string | - -Returns a description of the plugin from the version information. diff --git a/api/cefpython.md b/api/cefpython.md index ae7d255c3..5d508c824 100644 --- a/api/cefpython.md +++ b/api/cefpython.md @@ -62,9 +62,8 @@ This function can only be called on the UI thread. If the url is a local path it needs to start with the `file://` prefix. If the url contains special characters it may need proper handling. Starting with v66.1+ it is required for the app code to encode the url -properly. You can use the `pathlib.PurePath.as_uri` in Python 3 -or `urllib.pathname2url` in Python 2 (`urllib.request.pathname2url` -in Python 3) depending on your case. +properly. You can use `pathlib.PurePath.as_uri` or +`urllib.request.pathname2url` depending on your case. The "window_title" parameter will be used only when parent window provided in window_info was set to 0. This is for use diff --git a/cefpython3/__init__.py b/cefpython3/__init__.py new file mode 100644 index 000000000..8a1043d8f --- /dev/null +++ b/cefpython3/__init__.py @@ -0,0 +1,34 @@ +import ctypes +import importlib +import os +import platform +import sys + +__all__ = ["cefpython"] +__author__ = "The CEF Python authors" + +try: + from importlib.metadata import version as _pkg_version + __version__ = _pkg_version("cefpython3") +except Exception: + __version__ = "unknown" + +package_dir = os.path.dirname(os.path.abspath(__file__)) + +# Let subprocess and the C extension find CEF DLLs / .so files next to this +# __init__.py regardless of the working directory. +os.environ["CEFPYTHON3_PATH"] = package_dir + +if platform.system() == "Linux": + # Force GDK to use X11/XWayland backend before libcef.so loads and + # initialises GDK. Without this, GDK picks the Wayland backend on + # GNOME/Wayland sessions, making gdk_x11_window_get_xid() return 0. + os.environ.setdefault("GDK_BACKEND", "x11") + _ld = os.environ.get("LD_LIBRARY_PATH", "") + os.environ["LD_LIBRARY_PATH"] = ( + package_dir + os.pathsep + _ld if _ld else package_dir + ) + ctypes.CDLL(os.path.join(package_dir, "libcef.so"), ctypes.RTLD_GLOBAL) + +_pyver = "{0}{1}".format(*sys.version_info[:2]) +cefpython = importlib.import_module(".cefpython_py{}".format(_pyver), package=__name__) diff --git a/docs/Build-instructions.md b/docs/Build-instructions.md index 9ca4d6e99..be90140bc 100644 --- a/docs/Build-instructions.md +++ b/docs/Build-instructions.md @@ -48,8 +48,8 @@ Before you can build CEF Python or CEF you must satisfy ## Quick build instructions for Windows -Complete steps for building CEF Python v50+ with Python 2.7 using -prebuilt binaries and libraries from GitHub Releases. +Complete steps for building CEF Python v50+ using prebuilt binaries +and libraries from GitHub Releases. When cloning repository you should checkout a stable branch which are named "cefpythonXX" where XX is Chromium version number. @@ -62,15 +62,7 @@ are named "cefpythonXX" where XX is Chromium version number. 3) Download [cmake](https://cmake.org/download/) and add it to PATH. -4) For Python 2.7 Install "Visual C++ Compiler for Python 2.7" - from [here](https://www.microsoft.com/en-us/download/details.aspx?id=44266) - -5) For Python 2.7 and when using using "Visual C++ compiler for Python 2.7" - you have to install "Visual C++ 2008 Redistributable Package" - from [here](https://www.microsoft.com/en-us/download/details.aspx?id=29) - and [here](https://www.microsoft.com/en-us/download/details.aspx?id=15336) - -6) Clone cefpython, checkout for example "cefpython57" branch +4) Clone cefpython, checkout for example "cefpython57" branch that includes Chromium v57, then create a build/ directory and enter it: ``` git clone https://github.com/cztomczak/cefpython.git @@ -94,9 +86,9 @@ pip install --upgrade -r ../tools/requirements.txt 8) Extract the archive in the "build/" directory. -9) Build cefpython and run examples (xx.x is version number): +9) Build cefpython and run examples: ``` -python ../tools/build.py xx.x +python ../tools/build.py ``` @@ -143,9 +135,9 @@ sudo pip install --upgrade -r ../tools/requirements.txt 7) Extract the archive in the "build/" directory. -8) Build cefpython and run examples (xx.x is version number): +8) Build cefpython and run examples: ``` -python ../tools/build.py xx.x +python ../tools/build.py ``` @@ -160,29 +152,13 @@ requirements common for all platforms. * Download [ninja](https://github.com/ninja-build/ninja) 1.7.2 or later and add it to PATH. * Download [cmake](https://cmake.org/download/) and add it to PATH. -* Install an appropriate MS compiler for a specific Python version: - https://wiki.python.org/moin/WindowsCompilers - * For Python 2.7 install "Microsoft Visual C++ Compiler for Python 2.7" - from [here](https://www.microsoft.com/en-us/download/details.aspx?id=44266) - * When using "Visual C++ compiler for Python 2.7" you have to install - "Microsoft Visual C++ 2008 Redistributable Package" from - [here](https://www.microsoft.com/en-us/download/details.aspx?id=29) and - [here](https://www.microsoft.com/en-us/download/details.aspx?id=15336) - * For Python 2.7 copy "cefpython/src/windows/py27/stdint.h" to - "%LocalAppData%\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\include\" - 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). - * 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 - directory or find this file on the web. This is a Microsoft issue. +* Install Visual Studio 2022 or later with C++ workload (required for + Python 3.10+). See https://wiki.python.org/moin/WindowsCompilers * To build CEF from sources: * Use Win7 x64 or later. 32-bit OS'es are not supported. For more details see [here](https://www.chromium.org/developers/how-tos/build-instructions-windows). - * For CEF branch >= 2704 install VS2015 Update 2 or later. Use the - Custom Install option, see details [here](https://chromium.googlesource.com/chromium/src/+/master/docs/windows_build_instructions.md#Open-source-contributors). + * Install Visual Studio 2022 with C++ workload. Use the Custom Install + option, see details [here](https://chromium.googlesource.com/chromium/src/+/master/docs/windows_build_instructions.md#Open-source-contributors). * Install [CMake](https://cmake.org/) 2.8.12.1 or newer and add cmake.exe to PATH * Install [ninja](http://martine.github.io/ninja/) and add ninja.exe @@ -260,9 +236,9 @@ cd build/ 3) Extract the downloaded archive eg. "cef55_3.2883.1553.g80bd606_win32.zip" in the "build/" directory (using "extract here" option) -4) Run the build.py tool (xx.x is version number): +4) Run the build.py tool: ``` -python ../tools/build.py xx.x +python ../tools/build.py ``` @@ -281,22 +257,36 @@ mkdir build/ cd build/ ``` -2) Download CEF binaries from [Spotify Automated Builds](http://opensource.spotify.com/cefbuilds/index.html). - The version of the binaries must match exactly the CEF version - from the "cefpython/src/version/" directory (look for CEF_VERSION - constant in .h file). +2) Download and extract CEF binaries automatically using the + download_cef.py tool. It reads the required CEF version from + "cefpython/src/version/", queries the Spotify CDN index, downloads + the standard distribution, verifies the SHA1 checksum, and extracts + it to the build/ directory: +``` +python ../tools/download_cef.py +``` -3) Extract the downloaded archive eg. - "cef_binary_3.2883.1553.g80bd606_windows32.tar.bz2" - in the build/ directory (using "extract here" option) + Alternatively, download manually from + [Spotify Automated Builds](https://cef-builds.spotifycdn.com/index.html). + The version must match exactly the CEF version from + "cefpython/src/version/" (look for CEF_VERSION constant in .h file). + Extract the archive eg. "cef_binary_3.2883.1553.g80bd606_windows32.tar.bz2" + in the build/ directory (using "extract here" option). -4) Run the automate.py tool. After it completes you should see a new - directory eg. "cef55_3.2883.1553.g80bd606_win32/". +3) Build libcef_dll_wrapper and prepare the CEF binaries directory using + the automate.py tool. This processes the downloaded "cef_binary_*" + directory and creates a new directory eg. "cef55_3.2883.1553.g80bd606_win32/" + that build.py requires. Note: this step requires cmake and ninja on PATH. ``` python ../tools/automate.py --prebuilt-cef ``` -5) Run the build.py tool (xx.x is version number): +4) Run the build.py tool. The version number is optional and defaults to + {CHROME_VERSION_MAJOR}.0 read from "cefpython/src/version/": +``` +python ../tools/build.py +``` + To override the patch version (eg. for a second release off the same CEF): ``` python ../tools/build.py xx.x ``` @@ -460,10 +450,8 @@ ls ``` 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 - for each Python version) +* `--python-tag cp310` to generate a Python 3.10 only package + (replace `310` with the appropriate version tag, e.g. `cp311`, `cp312`) CEF Python binaries are build using similar configuration as described on the ["Automated Build Setup"](https://bitbucket.org/chromiumembedded/cef/wiki/AutomatedBuildSetup.md#markdown-header-platform-build-configurations) wiki page in upstream CEF. The automate.py tool incorporates most of diff --git a/docs/Knowledge-Base.md b/docs/Knowledge-Base.md index c68f4cc06..d60f8e7be 100644 --- a/docs/Knowledge-Base.md +++ b/docs/Knowledge-Base.md @@ -3,7 +3,6 @@ Table of contents: * [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) * [How to enable debug information in examples?](#how-to-enable-debug-information-in-examples) * [Remote debugging with Google Chrome instance](#remote-debugging-with-google-chrome-instance) * [Debugging using various chrome:// protocol uris](#debugging-using-various-chrome-protocol-uris) @@ -45,13 +44,6 @@ to hardcode the cefpython version string. If for example using PIP's following format if using e.g. cefpython v57.0: `cefpython3 == 57.0`. -## Differences between Python 2 and Python 3 - -In Python 2 all cefpython strings are byte strings, but in Python 3 -they are all unicode strings. Be aware of this when porting cefpython -based apps to Python 3, as it may cause issues. - - ## How to enable debug information in examples? You can pass "--debug" command line flag to any of CEF Python @@ -180,7 +172,7 @@ CEF framework and in the cefpython module. Here are the default settings: ``` cefpython_package/ - cefpython_py27.so + cefpython_py3XX.so rpath=@loader_path/ load:@rpath/Chromium Embedded Framework.framework/Chromium Embedded Framework Chromium Embedded Framework.framework/ diff --git a/docs/Migration-guide.md b/docs/Migration-guide.md index 5ad3d4384..03a3744c8 100644 --- a/docs/Migration-guide.md +++ b/docs/Migration-guide.md @@ -493,8 +493,7 @@ See Issue [#442](../../../issues/442) for more details on the issues. [Issue #384](../../../issues/384) fixes problems with browser failing to load urls containing certain characters by not encoding the url anymore. From now on it is required for the app code to encode the url properly. You can use -the `pathlib.PurePath.as_uri` in Python 3 or `urllib.pathname2url` in -Python 2 (`urllib.request.pathname2url` in Python 3) depending on your case. +`pathlib.PurePath.as_uri` or `urllib.request.pathname2url` depending on your case. The `cef.GetNavigateUrl` function was removed from the cefpython3 module. diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 11cf5db71..88f43fefa 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -149,14 +149,14 @@ a line that overwrites the default exception handler in Python: sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error ``` -See Python docs for [sys.excepthook](https://docs.python.org/2/library/sys.html#sys.excepthook). +See Python docs for [sys.excepthook](https://docs.python.org/3/library/sys.html#sys.excepthook). The cef.ExceptHook helper function does the following: 1. Writes exception to "error.log" file 2. Prints exception 3. Calls cef.[QuitMessageLoop](../api/cefpython.md#quitmessageloop) 4. Calls cef.[Shutdown](../api/cefpython.md#shutdown) -5. Calls [os._exit(1)](https://docs.python.org/2/library/os.html#os._exit) - +5. Calls [os._exit(1)](https://docs.python.org/3/library/os.html#os._exit) - which exits the process with status 1, without calling cleanup handlers, flushing stdio buffers, etc. @@ -410,8 +410,8 @@ html_to_data_uri("test", js_callback_1); **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. In upstream CEF there is available a fast built-in +by running an internal web-server. See for example +[http.server](https://docs.python.org/3/library/http.server.html) in Python docs. In upstream CEF there is available a fast built-in web server and [Issue #445](../../../issues/445) is to expose its API. With http requests it is possible for synchronous @@ -612,8 +612,7 @@ In the OnPaint callback CEF provides a [PaintBufer](../api/PaintBuffer.md#paintb 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: +name is a bit confusing since it returns bytes, not a string. Here is the code: ```Python def OnPaint(self, browser, element_type, paint_buffer, **_): diff --git a/examples/hello_world.py b/examples/hello_world.py index 789f4666e..75d59a122 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -8,15 +8,17 @@ # Setting DPI awareness programmatically via a call to cef.DpiAware.EnableHighDpiSupport # is problematic in Python, may not work and can cause display glitches. +import sys + from cefpython3 import cefpython as cef import platform -import sys def main(): check_versions() - sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error - cef.Initialize() + sys.excepthook = cef.ExceptHook # shut down all CEF processes on error + settings = {} + cef.Initialize(settings=settings) cef.CreateBrowserSync(url="https://www.google.com/", window_title="Hello World!") cef.MessageLoop() @@ -31,7 +33,8 @@ def check_versions(): print("[hello_world.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) - assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (57, 0), \ + "CEF Python v57.0+ required to run this" if __name__ == '__main__': diff --git a/examples/pysdl2.py b/examples/pysdl2.py index 97f9293dc..dfce311d2 100644 --- a/examples/pysdl2.py +++ b/examples/pysdl2.py @@ -127,6 +127,11 @@ def main(): dest='renderer', choices=['software', 'hardware'] ) + parser.add_argument( + '--debug', + help='debug app', + action='store_true' + ) args = parser.parse_args() logLevel = logging.INFO if args.verbose: @@ -135,7 +140,7 @@ def main(): format='[%(filename)s %(levelname)s]: %(message)s', level=logLevel ) - logging.info("Using PySDL2 %s" % sdl2.__version__) + logging.info("Using PySDL2 %s", sdl2.__version__) version = sdl2.SDL_version() sdl2.SDL_GetVersion(version) logging.info( @@ -224,7 +229,7 @@ def main(): browser.SetClientHandler(renderHandler) # Must call WasResized at least once to let know CEF that # viewport size is available and that OnPaint may be called. - browser.SendFocusEvent(True) + browser.SetFocus(True) browser.WasResized() # Begin the main rendering loop diff --git a/examples/pywin32.py b/examples/pywin32.py index bf8d2b9be..e261dfab4 100644 --- a/examples/pywin32.py +++ b/examples/pywin32.py @@ -108,7 +108,7 @@ def check_versions(): pywin32_version = fp.read().strip() print("[pywin32.py] pywin32 {ver}".format(ver=pywin32_version)) - assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (57, 0), "CEF Python v57.0+ required to run this" def create_browser(window_info, settings, url): diff --git a/examples/qt.py b/examples/qt.py index efb1b381f..c2efce944 100644 --- a/examples/qt.py +++ b/examples/qt.py @@ -1,37 +1,24 @@ -# Example of embedding CEF browser using PyQt4, PyQt5 and -# PySide libraries. This example has two widgets: a navigation -# bar and a browser. +# Example of embedding CEF browser using PyQt5, PyQt6 and PySide6 libraries. +# This example has two widgets: a navigation bar and a browser. # # Tested configurations: # - PyQt 5.8.2 (qt 5.8.0) on Windows/Linux/Mac -# - PyQt 4.10.4 / 4.11.4 (qt 4.8.6 / 4.8.7) on Windows/Linux -# - PySide 1.2.1 (qt 4.8.6) on Windows/Linux/Mac -# - PySide2 5.6.0, 5.11.2 (qt 5.6.2, 5.11.2) on Windows/Linux/Mac +# - PyQt6 on Linux +# - PySide6 on Linux # - CEF Python v55.4+ -# -# Issues with PySide 1.2: -# - Mac: Keyboard focus issues when switching between controls (Issue #284) -# - Mac: Mouse cursor never changes when hovering over links (Issue #311) from cefpython3 import cefpython as cef -import ctypes import os import platform +import subprocess import sys # GLOBALS -PYQT4 = False PYQT5 = False -PYSIDE = False -PYSIDE2 = False +PYQT6 = False +PYSIDE6 = False -if "pyqt4" in sys.argv: - PYQT4 = True - # noinspection PyUnresolvedReferences - from PyQt4.QtGui import * - # noinspection PyUnresolvedReferences - from PyQt4.QtCore import * -elif "pyqt5" in sys.argv: +if "pyqt5" in sys.argv: PYQT5 = True # noinspection PyUnresolvedReferences from PyQt5.QtGui import * @@ -39,34 +26,31 @@ from PyQt5.QtCore import * # noinspection PyUnresolvedReferences from PyQt5.QtWidgets import * -elif "pyside" in sys.argv: - PYSIDE = True +elif "pyqt6" in sys.argv: + PYQT6 = True # noinspection PyUnresolvedReferences - import PySide + from PyQt6.QtGui import * # noinspection PyUnresolvedReferences - from PySide import QtCore + from PyQt6.QtCore import * # noinspection PyUnresolvedReferences - from PySide.QtGui import * + from PyQt6.QtWidgets import * +elif "pyside6" in sys.argv: + PYSIDE6 = True # noinspection PyUnresolvedReferences - from PySide.QtCore import * -elif "pyside2" in sys.argv: - PYSIDE2 = True + import PySide6 # noinspection PyUnresolvedReferences - import PySide2 + from PySide6 import QtCore # noinspection PyUnresolvedReferences - from PySide2 import QtCore + from PySide6.QtGui import * # noinspection PyUnresolvedReferences - from PySide2.QtGui import * + from PySide6.QtCore import * # noinspection PyUnresolvedReferences - from PySide2.QtCore import * - # noinspection PyUnresolvedReferences - from PySide2.QtWidgets import * + from PySide6.QtWidgets import * else: print("USAGE:") - print(" qt.py pyqt4") print(" qt.py pyqt5") - print(" qt.py pyside") - print(" qt.py pyside2") + print(" qt.py pyqt6") + print(" qt.py pyside6") sys.exit(1) # Fix for PyCharm hints warnings when using static methods @@ -77,16 +61,37 @@ LINUX = (platform.system() == "Linux") MAC = (platform.system() == "Darwin") +# CEF only supports X11 on Linux. Force Qt onto the xcb (X11/XWayland) +# backend for all bindings so that winId() returns a real X11 window ID +# that CEF can embed into. Wayland desktops (e.g. KDE Plasma on Kubuntu) +# often pre-set QT_QPA_PLATFORM=wayland in the session environment, so a +# hard override is needed — setdefault would not override a pre-set value. +# Must be set before creating QApplication. +if LINUX: + os.environ["QT_QPA_PLATFORM"] = "xcb" + +# On Linux, query the X11 pointer button mask directly to detect outside-clicks +# on the context menu. XQueryPointer returns real button state even while CEF +# holds an X11 grab (grabs only affect event *delivery*, not state queries). +if LINUX: + try: + from Xlib import display as _xlib_display_mod + _XLIB_DPY = _xlib_display_mod.Display() + def _x11_button_state(): + try: + r = _XLIB_DPY.screen().root.query_pointer() + return r.mask & 0x1F00 # Button1Mask(256)..Button5Mask(4096) + except Exception: + return 0 + except ImportError: + _XLIB_DPY = None + def _x11_button_state(): + return int(QApplication.mouseButtons()) + # Configuration WIDTH = 800 HEIGHT = 600 -# OS differences -CefWidgetParent = QWidget -if LINUX and (PYQT4 or PYSIDE): - # noinspection PyUnresolvedReferences - CefWidgetParent = QX11EmbedContainer - def main(): check_versions() @@ -97,6 +102,14 @@ def main(): # in Qt example. Calling cef.DoMessageLoopWork in a timer # doesn't work anymore. settings["external_message_pump"] = True + settings["context_menu"] = { + "enabled": True, + "navigation": True, + "print": True, + "view_source": True, + "external_browser": True, + "devtools": True, + } cef.Initialize(settings) app = CefApplication(sys.argv) @@ -104,8 +117,11 @@ def main(): main_window.show() main_window.activateWindow() main_window.raise_() - app.exec_() - if not cef.GetAppSetting("external_message_pump"): + if PYQT6 or PYSIDE6: + app.exec() + else: + app.exec_() + if not cef.GetAppSetting("external_message_pump") or LINUX: app.stopTimer() del main_window # Just to be safe, similarly to "del app" del app # Must destroy app object before calling Shutdown @@ -116,37 +132,32 @@ def check_versions(): 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: + if PYQT5 or PYQT6: print("[qt.py] PyQt {v1} (qt {v2})".format( v1=PYQT_VERSION_STR, v2=qVersion())) - elif PYSIDE: - print("[qt.py] PySide {v1} (qt {v2})".format( - v1=PySide.__version__, v2=QtCore.__version__)) - elif PYSIDE2: - print("[qt.py] PySide2 {v1} (qt {v2})".format( - v1=PySide2.__version__, v2=QtCore.__version__)) + elif PYSIDE6: + print("[qt.py] PySide6 {v1} (qt {v2})".format( + v1=PySide6.__version__, v2=QtCore.__version__)) # CEF Python version requirement - assert cef.__version__ >= "55.4", "CEF Python v55.4+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (55, 4), "CEF Python v55.4+ required to run this" class MainWindow(QMainWindow): def __init__(self): # noinspection PyArgumentList super(MainWindow, self).__init__(None) - # Avoids crash when shutting down CEF (issue #360) - if PYSIDE: - self.setAttribute(Qt.WA_DeleteOnClose, True) self.cef_widget = None self.navigation_bar = None - if PYQT4: - self.setWindowTitle("PyQt4 example") - elif PYQT5: + if PYQT5: self.setWindowTitle("PyQt5 example") - elif PYSIDE: - self.setWindowTitle("PySide example") - elif PYSIDE2: - self.setWindowTitle("PySide2 example") - self.setFocusPolicy(Qt.StrongFocus) + elif PYQT6: + self.setWindowTitle("PyQt6 example") + elif PYSIDE6: + self.setWindowTitle("PySide6 example") + if PYQT6 or PYSIDE6: + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + else: + self.setFocusPolicy(Qt.StrongFocus) self.setupLayout() def setupLayout(self): @@ -167,7 +178,7 @@ def setupLayout(self): frame.setLayout(layout) self.setCentralWidget(frame) - if (PYSIDE2 or PYQT5) and WINDOWS: + if 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. @@ -176,19 +187,25 @@ def setupLayout(self): # Browser can be embedded only after layout was set up self.cef_widget.embedBrowser() - if (PYSIDE2 or 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. + if LINUX and PYQT5: + # On Linux with PyQt5 QX11EmbedContainer is no longer available. + # The equivalent is to embed CEF in a QWindow (hidden_window) and + # wrap it in a createWindowContainer widget. # noinspection PyUnresolvedReferences, PyArgumentList self.container = QWidget.createWindowContainer( self.cef_widget.hidden_window, parent=self) # noinspection PyArgumentList layout.addWidget(self.container, 1, 0) + # The container displaces cef_widget in the layout, so + # cef_widget.resizeEvent never fires. Drive SetBounds from the + # container's resize events via this event filter. + self.container.installEventFilter(self.cef_widget) def closeEvent(self, event): + # Dismiss any open context menu before CloseBrowser so the callback + # is released before CEF destroys the browser's menu-manager state. + if LINUX and ContextMenuHandler._active_menu is not None: + ContextMenuHandler._active_menu.hide() # Close browser (force=True) and free CEF reference if self.cef_widget.browser: self.cef_widget.browser.CloseBrowser(True) @@ -200,15 +217,25 @@ def clear_browser_references(self): self.cef_widget.browser = None -class CefWidget(CefWidgetParent): +class CefWidget(QWidget): def __init__(self, parent=None): # noinspection PyArgumentList super(CefWidget, self).__init__(parent) self.parent = parent self.browser = None self.hidden_window = None # Required for PyQt5 on Linux + self.x = 0 + self.y = 0 self.show() + def eventFilter(self, obj, event): + # Only installed on PyQt5/Linux where the container displaces cef_widget. + if event.type() == QEvent.Resize and self.browser: + size = event.size() + self.browser.SetBounds(self.x, self.y, size.width(), size.height()) + self.browser.NotifyMoveOrResizeStarted() + return False + def focusInEvent(self, event): # This event seems to never get called on Linux, as CEF is # stealing all focus due to Issue #284. @@ -228,43 +255,54 @@ def focusOutEvent(self, event): self.browser.SetFocus(False) def embedBrowser(self): - if (PYSIDE2 or PYQT5) and LINUX: + if LINUX and PYQT5: + # PyQt5 uses GDK/Xlib while CEF uses XCB. Creating an XCB child + # under a GDK/Xlib window triggers a cross-client MatchError on + # Xwayland. The workaround is to create CEF under root first and + # then reparent into hidden_window via a GLib timer + # (_linux_schedule_xembed / _linux_embed_info mechanism). # noinspection PyUnresolvedReferences self.hidden_window = QWindow() + # For PyQt6/PySide6 on Linux, cef_widget already has WA_PaintOnScreen + # which forces a real X11 native window. CEF can be created directly + # as a child of cef_widget.winId() — no hidden_window or deferred + # reparent needed. window_info = cef.WindowInfo() - rect = [0, 0, self.width(), self.height()] + rect = [0, 0, self._phys(self.width()), self._phys(self.height())] window_info.SetAsChild(self.getHandle(), rect) + if (PYQT6 or PYSIDE6) and LINUX: + # SetAsChild() substituted root as CEF's parent (Xwayland workaround). + # Qt6 uses XCB (same as CEF) so a direct parent/child relationship + # works. Restore cef_widget as the parent and disable the GLib-timer + # reparent by clearing _linux_embed_info. + window_info.parentWindowHandle = self.getHandle() + window_info._linux_embed_info = None self.browser = cef.CreateBrowserSync(window_info, url="https://www.google.com/") - self.browser.SetClientHandler(LoadHandler(self.parent.navigation_bar)) - self.browser.SetClientHandler(FocusHandler(self)) + if self.browser: + self.browser.SetClientHandler(LoadHandler(self.parent.navigation_bar)) + self.browser.SetClientHandler(FocusHandler(self)) + if LINUX: + self.browser.SetClientHandler(ContextMenuHandler(self)) + if WINDOWS: + # Sync browser size to actual HWND client rect using device pixels. + # PyQt6 high-DPI scaling means self.width()/height() may be smaller + # than the real client rect, leaving content in a smaller area. + WindowUtils.OnSize(self.getHandle(), 0, 0, 0) + + def _phys(self, n): + # Qt6 enables AA_EnableHighDpiScaling by default, so width()/height() + # return logical pixels. CEF expects physical pixels. Multiply by + # devicePixelRatio() for PyQt6/PySide6 on Linux; PyQt5 uses the + # hidden_window/XReparentWindow path where X11 geometry drives sizing. + if LINUX and (PYQT6 or PYSIDE6): + return int(n * self.devicePixelRatio()) + return n def getHandle(self): if self.hidden_window: - # PyQt5 on Linux 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 = ( - ctypes.c_void_p) - ctypes.pythonapi.PyCObject_AsVoidPtr.argtypes = ( - [ctypes.py_object]) - return ctypes.pythonapi.PyCObject_AsVoidPtr(self.winId()) - else: - # Python 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) + return int(self.winId()) def moveEvent(self, _): self.x = 0 @@ -274,7 +312,8 @@ def moveEvent(self, _): WindowUtils.OnSize(self.getHandle(), 0, 0, 0) elif LINUX: self.browser.SetBounds(self.x, self.y, - self.width(), self.height()) + self._phys(self.width()), + self._phys(self.height())) self.browser.NotifyMoveOrResizeStarted() def resizeEvent(self, event): @@ -284,14 +323,15 @@ def resizeEvent(self, event): WindowUtils.OnSize(self.getHandle(), 0, 0, 0) elif LINUX: self.browser.SetBounds(self.x, self.y, - size.width(), size.height()) + self._phys(size.width()), + self._phys(size.height())) self.browser.NotifyMoveOrResizeStarted() class CefApplication(QApplication): def __init__(self, args): super(CefApplication, self).__init__(args) - if not cef.GetAppSetting("external_message_pump"): + if not cef.GetAppSetting("external_message_pump") or LINUX: self.timer = self.createTimer() self.setupIcon() @@ -326,6 +366,11 @@ def OnLoadingStateChange(self, **_): def OnLoadStart(self, browser, **_): self.navigation_bar.url.setText(browser.GetUrl()) + # Dismiss any open context menu before CEF tears down its menu state + # during navigation — holding the callback alive past this point + # triggers an observers_.empty() assertion in base/observer_list.h. + if LINUX and ContextMenuHandler._active_menu is not None: + ContextMenuHandler._active_menu.hide() if self.initial_app_loading: self.navigation_bar.cef_widget.setFocus() # Temporary fix no. 2 for focus issue on Linux (Issue #284) @@ -352,9 +397,173 @@ def OnGotFocus(self, browser, **_): if cef.GetAppSetting("debug"): print("[qt.py] FocusHandler.OnGotFocus") self.cef_widget.setFocus() - # Temporary fix no. 1 for focus issues on Linux (Issue #284) + # Temporary fix no. 1 for focus issues on Linux (Issue #284). + # Do NOT call browser.SetFocus(True) here on Linux: it calls + # XSetInputFocus which steals keyboard focus from any context-menu + # popup that is currently shown, causing the menu to close instantly. if LINUX: - browser.SetFocus(True) + if ContextMenuHandler._active_menu is not None: + # Fast path: button still down when OnGotFocus fires. + if _x11_button_state(): + menu = ContextMenuHandler._active_menu + if not menu.geometry().contains(QCursor.pos()): + menu.hide() + return + # Start (or restart) the poll. Covers: + # - hover via focus-follows-mouse (button not pressed yet) + # - fast click where button was released before OnGotFocus fired + ContextMenuHandler._start_focus_poll() + + +class ContextMenuHandler(object): + """Show a Qt context menu instead of CEF's native Aura/Ozone menu. + + CEF's native context menu on Linux/Xwayland fails to display correctly + when the browser window has been reparented (embedded). Qt's own QMenu + always appears at the right position because it uses QCursor.pos(). + + In CEF Chrome style (116+), CefRunContextMenuCallback::Continue() does + not execute commands — it silently does nothing. All commands must be + dispatched directly through Python browser APIs. + """ + SEPARATOR = None + _active_menu = None + _focus_poll = None # QTimer: polls for button press after hover-outside + + # CEF standard menu command IDs (cef_types.h cef_menu_id_t) + _CMD_BACK = 100 + _CMD_FORWARD = 101 + _CMD_RELOAD = 102 + _CMD_RELOAD_NOCACHE = 103 + _CMD_STOPLOAD = 104 + _CMD_PRINT = 131 + _CMD_VIEW_SOURCE = 132 + # Custom IDs added by context_menu_handler.cpp (MENU_ID_USER_FIRST = 26500) + _CMD_DEVTOOLS = 26501 + _CMD_RELOAD_PAGE = 26502 + _CMD_OPEN_EXTERNAL = 26503 + _CMD_OPEN_FRAME = 26504 + + def __init__(self, cef_widget=None): + self._cef_widget = cef_widget + + @staticmethod + def _stop_focus_poll(): + if ContextMenuHandler._focus_poll is not None: + ContextMenuHandler._focus_poll.stop() + ContextMenuHandler._focus_poll = None + + @staticmethod + def _start_focus_poll(): + """Start a 5ms poll that hides the menu on an outside click.""" + ContextMenuHandler._stop_focus_poll() + timer = QTimer() + def _check(): + if ContextMenuHandler._active_menu is None: + ContextMenuHandler._stop_focus_poll() + return + if _x11_button_state(): + menu = ContextMenuHandler._active_menu + # Only dismiss if cursor is outside the menu. A button press + # inside the menu means the user selected an item — exec_() + # handles that; don't interfere. + if not menu.geometry().contains(QCursor.pos()): + menu.hide() + # Stop poll either way once a button press is detected. + ContextMenuHandler._stop_focus_poll() + timer.timeout.connect(_check) + timer.start(5) + ContextMenuHandler._focus_poll = timer + + @staticmethod + def _exec_cmd(browser, cmd_id, page_url): + if cmd_id == ContextMenuHandler._CMD_BACK: + browser.GoBack() + elif cmd_id == ContextMenuHandler._CMD_FORWARD: + browser.GoForward() + elif cmd_id in (ContextMenuHandler._CMD_RELOAD, + ContextMenuHandler._CMD_RELOAD_NOCACHE, + ContextMenuHandler._CMD_RELOAD_PAGE): + browser.ReloadIgnoreCache() + elif cmd_id == ContextMenuHandler._CMD_STOPLOAD: + browser.StopLoad() + elif cmd_id == ContextMenuHandler._CMD_PRINT: + browser.Print() + elif cmd_id == ContextMenuHandler._CMD_VIEW_SOURCE: + browser.LoadUrl("view-source:" + page_url) + elif cmd_id == ContextMenuHandler._CMD_DEVTOOLS: + browser.ShowDevTools() + elif cmd_id in (ContextMenuHandler._CMD_OPEN_EXTERNAL, + ContextMenuHandler._CMD_OPEN_FRAME): + if page_url: + subprocess.Popen(["xdg-open", page_url]) + # Editing and spellcheck commands (cut/copy/paste/select-all/…) + # are not yet handled — they silently do nothing. + + def RunContextMenu(self, browser, model, callback, **_): + if ContextMenuHandler.SEPARATOR is None: + ContextMenuHandler.SEPARATOR = cef.MENUITEMTYPE_SEPARATOR + sep_type = ContextMenuHandler.SEPARATOR + + page_url = browser.GetUrl() + cef_widget = self._cef_widget + + # Snapshot the model (valid only during this call). + items = [] + for i in range(model.GetCount()): + if model.GetTypeAt(i) == sep_type: + items.append(None) + else: + items.append((model.GetLabelAt(i).replace("&", ""), + model.GetCommandIdAt(i), + model.IsEnabledAt(i))) + + def show_menu(): + # If a previous menu is still open (user right-clicked twice quickly + # before the first was dismissed), hide it now. Without this the + # second exec_() runs inside the first's event loop and both menus + # appear on screen simultaneously. + if ContextMenuHandler._active_menu is not None: + ContextMenuHandler._active_menu.hide() + ContextMenuHandler._stop_focus_poll() + + # Cancel CEF's native context menu before displaying ours. + callback.Cancel() + + menu = QMenu() + text_to_cmd = {} + for item in items: + if item is None: + menu.addSeparator() + else: + label, cmd_id, enabled = item + act = menu.addAction(label) + act.setEnabled(enabled) + text_to_cmd[label] = cmd_id + + ContextMenuHandler._active_menu = menu + # Stop the focus-poll whenever the menu hides for any reason + # (item click, outside-click via poll, navigation, window close). + menu.aboutToHide.connect(ContextMenuHandler._stop_focus_poll) + # exec_() runs a nested event loop; CEF's 10ms timer keeps firing. + if PYQT6 or PYSIDE6: + act = menu.exec(QCursor.pos()) + else: + act = menu.exec_(QCursor.pos()) + ContextMenuHandler._active_menu = None + ContextMenuHandler._stop_focus_poll() + + if act is not None: + cmd_id = text_to_cmd.get(act.text()) + if cmd_id is not None: + b = cef_widget.browser if cef_widget else None + if b: + ContextMenuHandler._exec_cmd(b, cmd_id, page_url) + + # Defer the QMenu to the next event-loop tick so that this call + # returns to CEF before any Qt event-loop work runs. + QTimer.singleShot(0, show_menu) + return True class NavigationBar(QFrame): diff --git a/examples/screenshot.py b/examples/screenshot.py index 5ca8d4913..727cd775f 100644 --- a/examples/screenshot.py +++ b/examples/screenshot.py @@ -73,11 +73,26 @@ def main(): # it using these Chromium switches (Issue #240 and #463) "disable-gpu": "", "disable-gpu-compositing": "", - # Tweaking OSR performance by setting the same Chromium flags - # as in upstream cefclient (Issue #240). - "enable-begin-frame-scheduling": "", - "disable-surfaces": "", # This is required for PDF ext to work } + if sys.platform.startswith("darwin"): + # Suppress macOS keychain authorization dialogs in headless use. + switches["use-mock-keychain"] = "" + # MachPortRendezvousServer bootstrap name requires a bundle ID. + # Without one, renderer subprocess bootstrap_look_up fails. + # --single-process runs the renderer in-process, avoiding the lookup. + switches["single-process"] = "" + # --single-process puts V8 in the browser process and requires a large + # contiguous CodeRange; --jitless disables JIT to remove that need. + switches["js-flags"] = "--jitless" + # Run network service in-process to avoid Mach port rendezvous + # failures for utility subprocesses on macOS. + switches["enable-features"] = "NetworkServiceInProcess2" + else: + # Tweaking OSR performance (Issue #240). On macOS ARM the viz + # Surfaces API is required for OSR browser creation, so these + # switches must not be passed on macOS. + switches["enable-begin-frame-scheduling"] = "" + switches["disable-surfaces"] = "" # This is required for PDF ext to work browser_settings = { # Tweaking OSR performance (Issue #240) "windowless_frame_rate": 30, # Default frame rate in CEF is 30 @@ -99,7 +114,7 @@ def check_versions(): ver=platform.python_version(), arch=platform.architecture()[0])) print("[screenshot.py] Pillow {ver}".format(ver=PILLOW_VERSION)) - assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (57, 0), "CEF Python v57.0+ required to run this" def command_line_arguments(): @@ -139,9 +154,10 @@ def create_browser(settings): browser = cef.CreateBrowserSync(window_info=window_info, settings=settings, url=URL) + print('created browser ', browser) browser.SetClientHandler(LoadHandler()) browser.SetClientHandler(RenderHandler()) - browser.SendFocusEvent(True) + browser.SetFocus(True) # You must call WasResized at least once to let know CEF that # viewport size is available and that OnPaint may be called. browser.WasResized() diff --git a/examples/snippets/README-snippets.md b/examples/snippets/README-snippets.md index 0f41e4f3f..746013895 100644 --- a/examples/snippets/README-snippets.md +++ b/examples/snippets/README-snippets.md @@ -28,6 +28,10 @@ directory. If looking for non-trivial examples then see the - [cookies.py](cookies.py) - Shows how to fetch all cookies, all cookies for a given url and how to delete a specific cookie. +- [crossdomain_bindings.py](crossdomain_bindings.py) - Test Javascript + bindings across a cross-domain navigation flow. Simulates an SSO/auth + redirect (app page → auth domain → back to app) and demonstrates that + bindings only fire for the intended target domain. - [javascript_bindings.py](javascript_bindings.py) - Communicate between Python and Javascript asynchronously using inter-process messaging with the use of Javascript Bindings. @@ -41,8 +45,9 @@ directory. If looking for non-trivial examples then see the to execute custom code before browser window closes. - [ondomready.py](ondomready.py) - Execute custom Python code on a web page as soon as DOM is ready. -- [onpagecomplete.py](onpagecomplete.py) - Execute custom - Python code on a web page when page loading is complete. +- [onpagecomplete.py](onpagecomplete.py) - Execute custom Python + code on a web page after all visible content is loaded and painted, + using window.load and requestAnimationFrame. - [setcookie.py](setcookie.py) - Shows how to set a cookie - [window_size.py](window_size.py) - Set initial window size without use of any third party GUI framework. diff --git a/examples/snippets/cookies.py b/examples/snippets/cookies.py index bd732a78a..d2a0d8e5d 100644 --- a/examples/snippets/cookies.py +++ b/examples/snippets/cookies.py @@ -9,7 +9,7 @@ def main(): cef.Initialize() browser = cef.CreateBrowserSync( - url="http://www.html-kit.com/tools/cookietester/", + url="https://www.google.com/", window_title="Cookies") browser.SetClientHandler(LoadHandler()) cef.MessageLoop() @@ -19,7 +19,7 @@ def main(): class LoadHandler(object): def OnLoadingStateChange(self, browser, is_loading, **_): - if is_loading: + if not is_loading: print("Page loading complete - start visiting cookies") manager = cef.CookieManager.GetGlobalManager() # Must keep a strong reference to the CookieVisitor object @@ -32,7 +32,7 @@ def OnLoadingStateChange(self, browser, is_loading, **_): # To visit cookies only for a given url uncomment the # code below. """ - url = "http://www.html-kit.com/tools/cookietester/" + url = "https://www.google.com/" http_only_cookies = False result = manager.VisitUrlCookies(url, http_only_cookies, self.cookie_visitor) diff --git a/examples/snippets/crossdomain_bindings.py b/examples/snippets/crossdomain_bindings.py new file mode 100644 index 000000000..9f3878339 --- /dev/null +++ b/examples/snippets/crossdomain_bindings.py @@ -0,0 +1,127 @@ +""" +Test JavaScript bindings across a cross-domain navigation flow. + +Simulates an SSO/auth redirect scenario: + Step 1 - Target app page loads (example.com) + OnContextCreated detects target domain -> injects JS -> binding fires + Step 2 - Simulated redirect to auth/login domain (python.org) + OnContextCreated detects non-target domain -> skips injection -> no callback + Step 3 - Simulated auth complete, return to target app page (example.com) + OnContextCreated detects target domain again -> binding fires again + +Key points demonstrated: +- JS bindings (V8 globals) are registered for every page in the browser instance, + including intermediate auth/login pages on other domains. +- It is the user's responsibility to filter in OnContextCreated by URL/domain before + injecting JS so that callbacks only fire for the intended landing page. +- Each domain's V8 context is isolated: globals registered for example.com are not + accessible to python.org and vice versa. +""" + +import threading +from cefpython3 import cefpython as cef + +TARGET_HOST = "example.com" +AUTH_URL = "https://www.python.org/" # visually distinct: simulates Okta/login page +TARGET_URL = "https://example.com/" +NAV_DELAY_SEC = 3.0 + +# Navigation state machine: avoid re-triggering on multiple OnLoadEnd fires +STATE_INIT = "init" +STATE_ON_TARGET_1 = "on_target_1" # first example.com load complete +STATE_GOING_AUTH = "going_auth" # timer fired, navigating to auth +STATE_ON_AUTH = "on_auth" # auth page load complete +STATE_GOING_TARGET = "going_target" # timer fired, navigating back +STATE_ON_TARGET_2 = "on_target_2" # final example.com load complete + + +def main(): + print(__doc__) + cef.Initialize() + browser = cef.CreateBrowserSync(url=TARGET_URL, + window_title="Cross-domain JS binding test") + handler = CrossDomainHandler(browser) + browser.SetClientHandler(handler) + bindings = cef.JavascriptBindings() + bindings.SetFunction("OnTargetPageReady", handler["_OnTargetPageReady"]) + browser.SetJavascriptBindings(bindings) + cef.MessageLoop() + del handler + del browser + cef.Shutdown() + + +class CrossDomainHandler(object): + def __init__(self, browser): + self.browser = browser + self._state = STATE_INIT + + def __getitem__(self, key): + return getattr(self, key) + + def OnContextCreated(self, browser, frame, **_): + if not frame.IsMain(): + return + url = frame.GetUrl() + if not url or url == "about:blank": + return + if TARGET_HOST in url: + print("[OnContextCreated] Target domain — injecting JS binding: %s" % url) + browser.ExecuteJavascript(""" + if (document.readyState === "complete" + || document.readyState === "interactive") { + requestAnimationFrame(function() { OnTargetPageReady(); }); + } else { + window.addEventListener("load", function() { + requestAnimationFrame(function() { OnTargetPageReady(); }); + }); + } + """) + else: + print("[OnContextCreated] Non-target domain — skipping JS: %s" % url) + + def OnLoadEnd(self, browser, frame, http_code, **_): + if not frame.IsMain(): + return + url = frame.GetUrl() + if not url or url == "about:blank": + return + print("[OnLoadEnd] state=%s url=%s" % (self._state, url)) + + if self._state == STATE_INIT and TARGET_HOST in url: + self._state = STATE_ON_TARGET_1 + print("[OnLoadEnd] Step 1 done. Redirecting to auth in %gs..." % NAV_DELAY_SEC) + threading.Timer(NAV_DELAY_SEC, self._navigate_auth).start() + + elif self._state == STATE_GOING_AUTH and TARGET_HOST not in url: + self._state = STATE_ON_AUTH + print("[OnLoadEnd] Step 2 done (auth page). Returning to app in %gs..." % NAV_DELAY_SEC) + threading.Timer(NAV_DELAY_SEC, self._navigate_target).start() + + elif self._state == STATE_GOING_TARGET and TARGET_HOST in url: + self._state = STATE_ON_TARGET_2 + print("[OnLoadEnd] Step 3 done. Close window to exit.") + + def _navigate_auth(self): + self._state = STATE_GOING_AUTH + print("[Nav] Navigating to auth domain: %s" % AUTH_URL) + self.browser.GetMainFrame().LoadUrl(AUTH_URL) + + def _navigate_target(self): + self._state = STATE_GOING_TARGET + print("[Nav] Navigating back to target: %s" % TARGET_URL) + self.browser.GetMainFrame().LoadUrl(TARGET_URL) + + def _OnTargetPageReady(self): + url = self.browser.GetMainFrame().GetUrl() + print("[Python callback] OnTargetPageReady fired! state=%s url=%s" + % (self._state, url)) + self.browser.ExecuteJavascript( + 'setTimeout(function(){' + ' alert("JS binding works!\\nState: %s\\nURL: " + window.location.href);' + ' }, 0);' % self._state + ) + + +if __name__ == "__main__": + main() diff --git a/examples/snippets/network_cookies.py b/examples/snippets/network_cookies.py index c3631cff5..424a227ad 100644 --- a/examples/snippets/network_cookies.py +++ b/examples/snippets/network_cookies.py @@ -1,5 +1,5 @@ """ -Implement RequestHandler.CanGetCookies and CanSetCookie +Implement RequestHandler.CanSendCookie and CanSaveCookie to block or allow cookies over network requests. """ @@ -9,7 +9,7 @@ def main(): cef.Initialize() browser = cef.CreateBrowserSync( - url="http://www.html-kit.com/tools/cookietester/", + url="https://www.google.com/", window_title="Network cookies") browser.SetClientHandler(RequestHandler()) cef.MessageLoop() @@ -22,23 +22,23 @@ def __init__(self): self.getcount = 0 self.setcount = 0 - def CanGetCookies(self, frame, request, **_): + def CanSendCookie(self, browser, frame, request, cookie): # There are multiple iframes on that website, let's log # cookies only for the main frame. if frame.IsMain(): self.getcount += 1 - print("-- CanGetCookies #"+str(self.getcount)) + print("-- CanSendCookie #"+str(self.getcount)) print("url="+request.GetUrl()[0:80]) print("") # Return True to allow reading cookies or False to block return True - def CanSetCookie(self, frame, request, cookie, **_): + def CanSaveCookie(self, browser, frame, request, response, cookie): # There are multiple iframes on that website, let's log # cookies only for the main frame. if frame.IsMain(): self.setcount += 1 - print("-- CanSetCookie @"+str(self.setcount)) + print("-- CanSaveCookie @"+str(self.setcount)) print("url="+request.GetUrl()[0:80]) print("Name="+cookie.GetName()) print("Value="+cookie.GetValue()) @@ -48,4 +48,4 @@ def CanSetCookie(self, frame, request, cookie, **_): if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/examples/snippets/ondomready.py b/examples/snippets/ondomready.py index 8775129c7..1b7fb1a36 100644 --- a/examples/snippets/ondomready.py +++ b/examples/snippets/ondomready.py @@ -1,6 +1,6 @@ """ Execute custom Python code on a web page as soon as DOM is ready. -Implements a custom "_OnDomReady" event in the LoadHandler object. +Implements a custom "_OnDomReady" event using the OnContextCreated callback. """ from cefpython3 import cefpython as cef @@ -8,34 +8,37 @@ def main(): cef.Initialize() - browser = cef.CreateBrowserSync(url="https://www.google.com/", + browser = cef.CreateBrowserSync(url="https://example.com/", window_title="_OnDomReady event") - load_handler = LoadHandler(browser) - browser.SetClientHandler(load_handler) + handler = DomReadyHandler(browser) + browser.SetClientHandler(handler) bindings = cef.JavascriptBindings() bindings.SetFunction("LoadHandler_OnDomReady", - load_handler["_OnDomReady"]) + handler["_OnDomReady"]) browser.SetJavascriptBindings(bindings) cef.MessageLoop() - del load_handler + del handler del browser cef.Shutdown() -class LoadHandler(object): +class DomReadyHandler(object): def __init__(self, browser): self.browser = browser def __getitem__(self, key): return getattr(self, key) - def OnLoadStart(self, browser, **_): + def OnContextCreated(self, browser, frame, **_): + if not frame.IsMain(): + return browser.ExecuteJavascript(""" - if (document.readyState === "complete") { - LoadHandler_OnDomReady(); + if (document.readyState === "complete" + || document.readyState === "interactive") { + setTimeout(function(){ LoadHandler_OnDomReady(); }, 0); } else { document.addEventListener("DOMContentLoaded", function() { - LoadHandler_OnDomReady(); + setTimeout(function(){ LoadHandler_OnDomReady(); }, 0); }); } """) diff --git a/examples/snippets/onpagecomplete.py b/examples/snippets/onpagecomplete.py index e118e8e7c..debade470 100644 --- a/examples/snippets/onpagecomplete.py +++ b/examples/snippets/onpagecomplete.py @@ -1,6 +1,7 @@ """ -Execute custom Python code on a web page when page loading is complete. -Implements a custom "_OnPageComplete" event in the LoadHandler object. +Execute custom Python code on a web page when all visible content is loaded. +Implements a custom "_OnPageComplete" event that fires after window.load and +a browser paint frame, ensuring content is fully rendered before notifying. """ from cefpython3 import cefpython as cef @@ -10,25 +11,43 @@ def main(): cef.Initialize() browser = cef.CreateBrowserSync(url="https://www.google.com/", window_title="_OnPageComplete event") - browser.SetClientHandler(LoadHandler()) + handler = PageCompleteHandler(browser) + browser.SetClientHandler(handler) + bindings = cef.JavascriptBindings() + bindings.SetFunction("LoadHandler_OnPageComplete", + handler["_OnPageComplete"]) + browser.SetJavascriptBindings(bindings) cef.MessageLoop() + del handler del browser cef.Shutdown() -class LoadHandler(object): - def OnLoadingStateChange(self, browser, is_loading, **_): - """For detecting if page loading has ended it is recommended - to use OnLoadingStateChange which is most reliable. The OnLoadEnd - callback also available in LoadHandler can sometimes fail in - some cases e.g. when image loading hangs.""" - if not is_loading: - self._OnPageComplete(browser) +class PageCompleteHandler(object): + def __init__(self, browser): + self.browser = browser - def _OnPageComplete(self, browser): + def __getitem__(self, key): + return getattr(self, key) + + def OnContextCreated(self, browser, frame, **_): + if not frame.IsMain(): + return + browser.ExecuteJavascript(""" + window.addEventListener("load", function() { + requestAnimationFrame(function() { + LoadHandler_OnPageComplete(); + }); + }); + """) + + def _OnPageComplete(self): print("Page loading is complete!") - browser.ExecuteFunction("alert", "Message from Python: Page loading" - " is complete!") + self.browser.ExecuteJavascript( + 'setTimeout(function(){' + ' alert("Message from Python: Page loading is complete!");' + ' }, 0);' + ) if __name__ == '__main__': diff --git a/examples/snippets/setcookie.py b/examples/snippets/setcookie.py index 04a99508e..1ad1e5ae9 100644 --- a/examples/snippets/setcookie.py +++ b/examples/snippets/setcookie.py @@ -6,28 +6,56 @@ import datetime +class LoadHandler(object): + def OnLoadEnd(self, browser, frame, http_code, **_): + if not frame.IsMain(): + return + manager = cef.CookieManager.GetGlobalManager() + cookie = cef.Cookie() + cookie.Set({ + "name": "my_cookie", + "value": "my_value", + # Make sure domain is a valid value otherwise it crashes + # app (Issue #459) + "domain": ".google.com", + "path": "/", + "secure": True, + "httpOnly": False, + "creation": datetime.datetime(2018, 8, 22), + "lastAccess": datetime.datetime(2018, 8, 22), + "hasExpires": True, + "expires": datetime.datetime(2028, 12, 31, 23, 59, 59), + }) + manager.SetCookie("https://www.google.com/", cookie) + print("Cookie set: my_cookie=my_value") + + # Delay so SetCookie (async on IO thread) completes before visiting + cef.PostDelayedTask(cef.TID_UI, 200, visit_cookies) + + +def visit_cookies(): + manager = cef.CookieManager.GetGlobalManager() + manager.VisitUrlCookies( + "https://www.google.com/", + False, + CookieVisitor()) + + +class CookieVisitor(object): + def Visit(self, cookie, count, total, delete_cookie_out): + print("Cookie[%d/%d]: %s=%s (domain=%s)" % ( + count + 1, total, + cookie.Get("name"), cookie.Get("value"), + cookie.Get("domain"))) + return True # continue visiting + + def main(): cef.Initialize() - cef.CreateBrowserSync( - url="http://www.html-kit.com/tools/cookietester/", + browser = cef.CreateBrowserSync( + url="https://www.google.com/", window_title="Set a cookie") - manager = cef.CookieManager.GetGlobalManager() - cookie = cef.Cookie() - cookie.Set({ - "name": "my_cookie", - "value": "my_value", - # Make sure domain is a valid value otherwise it crashes - # app (Issue #459) - "domain": "www.html-kit.com", - "path": "/", - "secure": False, - "httpOnly": False, - "creation": datetime.datetime(2018, 8, 22), - "lastAccess": datetime.datetime(2018, 8, 22), - "hasExpires": True, - "expires": datetime.datetime(2028, 12, 31, 23, 59, 59), - }) - manager.SetCookie("http://www.html-kit.com/", cookie) + browser.SetClientHandler(LoadHandler()) cef.MessageLoop() cef.Shutdown() diff --git a/examples/tkinter_.py b/examples/tkinter_.py index 327f171fb..b126e224d 100644 --- a/examples/tkinter_.py +++ b/examples/tkinter_.py @@ -53,7 +53,7 @@ def main(): logger.info("Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) logger.info("Tk {ver}".format(ver=tk.Tcl().eval('info patchlevel'))) - assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (55, 3), "CEF Python v55.3+ required to run this" sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error # Tk must be initialized before CEF otherwise fatal error (Issue #306) root = tk.Tk() diff --git a/examples/tutorial.py b/examples/tutorial.py index 860bbe12c..7f7408130 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -85,7 +85,7 @@ def check_versions(): print("[tutorial.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) - assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (57, 0), "CEF Python v57.0+ required to run this" def html_to_data_uri(html, js_callback=None): @@ -188,6 +188,7 @@ def __init__(self, browser): def test_multiple_callbacks(self, js_callback): """Test both javascript and python callbacks.""" + print('in test_multiple_callbacks') js_print(self.browser, "Python", "test_multiple_callbacks", "Called from Javascript. Will call Javascript callback now.") diff --git a/examples/wxpython.py b/examples/wxpython.py index 92dd7d834..f09d07344 100644 --- a/examples/wxpython.py +++ b/examples/wxpython.py @@ -47,9 +47,6 @@ def main(): # the same time. This is an incorrect approach # and only a temporary fix. settings["external_message_pump"] = True - if WINDOWS: - # noinspection PyUnresolvedReferences, PyArgumentList - cef.DpiAware.EnableHighDpiSupport() cef.Initialize(settings=settings) app = CefApp(False) app.MainLoop() @@ -65,7 +62,7 @@ def check_versions(): ver=platform.python_version(), arch=platform.architecture()[0])) print("[wxpython.py] wxPython {ver}".format(ver=wx.version())) # CEF Python version requirement - assert cef.__version__ >= "66.0", "CEF Python v66.0+ required to run this" + assert tuple(int(x) for x in cef.__version__.split(".")) >= (66, 0), "CEF Python v66.0+ required to run this" def scale_window_size_for_high_dpi(width, height): @@ -195,7 +192,9 @@ def OnSize(self, _): elif LINUX: (x, y) = (0, 0) (width, height) = self.browser_panel.GetSize().Get() + self.browser.NotifyMoveOrResizeStarted() self.browser.SetBounds(x, y, width, height) + return self.browser.NotifyMoveOrResizeStarted() def OnClose(self, event): diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..39be9a656 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = [ + "scikit-build-core>=0.9", + "cython>=3.2", +] +build-backend = "scikit_build_core.build" + +[project] +name = "cefpython3" +dynamic = ["version"] +requires-python = ">=3.10" +license = { text = "BSD-3-Clause" } +description = "Python bindings for the Chromium Embedded Framework" +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "License :: OSI Approved :: BSD Software License", + "Operating System :: OS Independent", +] + +[tool.scikit-build] +cmake.build-type = "Release" +cmake.args = ["-A", "x64"] +wheel.packages = ["cefpython3"] diff --git a/src/app.pyx b/src/app.pyx index a00d58bf6..ce266616e 100644 --- a/src/app.pyx +++ b/src/app.pyx @@ -6,7 +6,7 @@ include "cefpython.pyx" cdef public void App_OnBeforeCommandLineProcessing_BrowserProcess( CefRefPtr[CefCommandLine] cefCommandLine - ) except * with gil: + ) noexcept with gil: try: AppendSwitchesToCommandLine(cefCommandLine, g_commandLineSwitches) Debug("App_OnBeforeCommandLineProcessing_BrowserProcess()") diff --git a/src/browser.pyx b/src/browser.pyx index c8cba03ad..1a6c9b5f1 100644 --- a/src/browser.pyx +++ b/src/browser.pyx @@ -5,9 +5,9 @@ include "cefpython.pyx" cimport cef_types +from libc.stdint cimport uint32_t, int64_t +from libcpp cimport nullptr from cef_types cimport cef_state_t -IF UNAME_SYSNAME == "Linux": - cimport x11 # cef_mouse_button_type_t, SendMouseClickEvent(). MOUSEBUTTON_LEFT = cef_types.MBT_LEFT @@ -59,7 +59,7 @@ cdef PyBrowser GetPyBrowser(CefRefPtr[CefBrowser] cefBrowser, global g_pyBrowsers - if cefBrowser == NULL or not cefBrowser.get(): + if not cefBrowser or not cefBrowser.get(): raise Exception("{caller}: CefBrowser reference is NULL" .format(caller=callerIdStr)) @@ -148,7 +148,7 @@ cdef void RemovePyBrowser(int browserId) except *: # noinspection PyUnresolvedReferences Debug("del g_pyBrowsers[%s]" % browserId) pyBrowser = g_pyBrowsers[browserId] - pyBrowser.cefBrowser.Assign(NULL) + pyBrowser.cefBrowser.Assign(nullptr) del pyBrowser del g_pyBrowsers[browserId] g_unreferenced_browsers.append(browserId) @@ -176,7 +176,7 @@ cpdef PyBrowser GetBrowserByIdentifier(int identifier): return None cdef public void PyBrowser_ShowDevTools(CefRefPtr[CefBrowser] cefBrowser - ) except * with gil: + ) noexcept with gil: # Called from ClientHandler::OnContextMenuCommand cdef PyBrowser pyBrowser try: @@ -207,7 +207,7 @@ cdef class PyBrowser: cdef void* imageBuffer cdef CefRefPtr[CefBrowser] GetCefBrowser(self) except *: - if self.cefBrowser != NULL and self.cefBrowser.get(): + if self.cefBrowser and self.cefBrowser.get(): return self.cefBrowser raise Exception("PyBrowser.GetCefBrowser() failed: CefBrowser " "was destroyed") @@ -215,7 +215,7 @@ cdef class PyBrowser: cdef CefRefPtr[CefBrowserHost] GetCefBrowserHost(self) except *: cdef CefRefPtr[CefBrowserHost] cefBrowserHost = ( self.GetCefBrowser().get().GetHost()) - if cefBrowserHost != NULL and cefBrowserHost.get(): + if cefBrowserHost and cefBrowserHost.get(): return cefBrowserHost raise Exception("PyBrowser.GetCefBrowserHost() failed: this " "method can only be called in the browser " @@ -230,7 +230,7 @@ cdef class PyBrowser: if self.imageBuffer: free(self.imageBuffer) - cpdef py_void SetClientCallback(self, py_string name, object callback): + cpdef py_void SetClientCallback(self, object name, object callback): if not self.allowedClientCallbacks: # DisplayHandler self.allowedClientCallbacks += [ @@ -246,9 +246,9 @@ cdef class PyBrowser: self.allowedClientCallbacks += ["OnBeforeResourceLoad", "OnResourceRedirect", "GetAuthCredentials", "OnQuotaRequest", "OnProtocolExecution", - "GetResourceHandler", - "OnBeforeBrowse", "OnRendererProcessTerminated", - "OnPluginCrashed", "CanGetCookies", "CanSetCookie"] + "GetResourceHandler", "OnBeforeBrowse", + "OnRendererProcessTerminated", + "CanSendCookie", "CanSaveCookie"] # RequestContextHandler self.allowedClientCallbacks += ["GetCookieManager"] # LoadHandler @@ -278,6 +278,8 @@ cdef class PyBrowser: # FocusHandler self.allowedClientCallbacks += ["OnTakeFocus", "OnSetFocus", "OnGotFocus"] + # ContextMenuHandler + self.allowedClientCallbacks += ["RunContextMenu"] if name not in self.allowedClientCallbacks: raise Exception("Browser.SetClientCallback() failed: unknown " @@ -289,7 +291,7 @@ cdef class PyBrowser: raise Exception("Browser.SetClientHandler() failed: __class__ " "attribute missing") cdef dict methods = {} - cdef py_string key + cdef object key cdef object method cdef tuple value for value in inspect.getmembers(clientHandler, @@ -299,7 +301,7 @@ cdef class PyBrowser: if key and key[0] != '_': self.SetClientCallback(key, method) - cpdef object GetClientCallback(self, py_string name): + cpdef object GetClientCallback(self, object name): if name in self.clientCallbacks: return self.clientCallbacks[name] @@ -345,7 +347,7 @@ cdef class PyBrowser: NonCriticalError("GetImage not implemented on this platform") return None - cpdef object GetSetting(self, py_string key): + cpdef object GetSetting(self, object key): cdef int browser_id = self.GetIdentifier() if browser_id in g_browser_settings: if key in g_browser_settings[browser_id]: @@ -356,7 +358,7 @@ cdef class PyBrowser: # CEF API. # -------------- - cpdef py_void AddWordToDictionary(self, py_string word): + cpdef py_void AddWordToDictionary(self, object word): cdef CefString cef_word PyToCefString(word, cef_word) self.GetCefBrowserHost().get().AddWordToDictionary(cef_word) @@ -394,9 +396,9 @@ cdef class PyBrowser: # If using GetCookieManager to implement custom cookie managers # then flushing of cookies would need to be handled manually. self.GetCefBrowserHost().get().GetRequestContext().get() \ - .GetDefaultCookieManager( - NULL) \ - .get().FlushStore(NULL) + .GetCookieManager( + nullptr) \ + .get().FlushStore(nullptr) cdef int browserId = self.GetCefBrowser().get().GetIdentifier() self.GetCefBrowserHost().get().CloseBrowser(bool(forceClose)) @@ -409,16 +411,18 @@ cdef class PyBrowser: def ExecuteFunction(self, *args): self.GetMainFrame().ExecuteFunction(*args) - cpdef py_void ExecuteJavascript(self, py_string jsCode, - py_string scriptUrl="", int startLine=1): + cpdef py_void ExecuteJavascript(self, object jsCode, + object scriptUrl=None, int startLine=1): + if scriptUrl is None: + scriptUrl = u"" self.GetMainFrame().ExecuteJavascript(jsCode, scriptUrl, startLine) - cpdef py_void Find(self, int searchId, py_string searchText, + cpdef py_void Find(self, object searchText, py_bool forward, py_bool matchCase, py_bool findNext): cdef CefString cefSearchText PyToCefString(searchText, cefSearchText) - self.GetCefBrowserHost().get().Find(searchId, cefSearchText, + self.GetCefBrowserHost().get().Find(cefSearchText, bool(forward), bool(matchCase), bool(findNext)) cpdef PyFrame GetFocusedFrame(self): @@ -426,16 +430,18 @@ cdef class PyBrowser: "Browser.GetFocusedFrame() may only be called on UI thread") return GetPyFrame(self.GetCefBrowser().get().GetFocusedFrame()) - cpdef PyFrame GetFrame(self, py_string name): + cpdef PyFrame GetFrameByName(self, object name): assert IsThread(TID_UI), ( - "Browser.GetFrame() may only be called on the UI thread") + "Browser.GetFrameByName() may only be called on the UI thread") cdef CefString cefName PyToCefString(name, cefName) - return GetPyFrame(self.GetCefBrowser().get().GetFrame(cefName)) + return GetPyFrame(self.GetCefBrowser().get().GetFrameByName(cefName)) cpdef object GetFrameByIdentifier(self, object identifier): - return GetPyFrame(self.GetCefBrowser().get().GetFrame( - identifier)) + cdef CefString cefIdentifier + PyToCefString(identifier, cefIdentifier) + return GetPyFrame(self.GetCefBrowser().get().GetFrameByIdentifier( + cefIdentifier)) cpdef list GetFrameNames(self): assert IsThread(TID_UI), ( @@ -456,7 +462,7 @@ cdef class PyBrowser: cdef PyFrame frame cdef list frames = [] for name in names: - frame = self.GetFrame(name) + frame = self.GetFrameByName(name) frames.append(frame) return frames @@ -478,7 +484,7 @@ cdef class PyBrowser: else: return self.GetWindowHandle() - cpdef py_string GetUrl(self): + cpdef object GetUrl(self): return self.GetMainFrame().GetUrl() cpdef object GetUserData(self, object key): @@ -521,10 +527,10 @@ cdef class PyBrowser: cpdef py_bool IsWindowRenderingDisabled(self): return self.GetCefBrowserHost().get().IsWindowRenderingDisabled() - cpdef py_string LoadUrl(self, py_string url): + cpdef object LoadUrl(self, object url): self.GetMainFrame().LoadUrl(url) - cpdef py_void Navigate(self, py_string url): + cpdef py_void Navigate(self, object url): self.LoadUrl(url) cpdef py_void NotifyMoveOrResizeStarted(self): @@ -539,7 +545,7 @@ cdef class PyBrowser: cpdef py_void ReloadIgnoreCache(self): self.GetCefBrowser().get().ReloadIgnoreCache() - cpdef py_void ReplaceMisspelling(self, py_string word): + cpdef py_void ReplaceMisspelling(self, object word): cdef CefString cef_word PyToCefString(word, cef_word) self.GetCefBrowserHost().get().ReplaceMisspelling(cef_word) @@ -581,7 +587,7 @@ cdef class PyBrowser: cdef CefBrowserSettings settings cdef CefPoint inspect_element_at self.GetCefBrowserHost().get().ShowDevTools( - window_info, NULL, settings, + window_info, nullptr, settings, inspect_element_at) cpdef py_void StopLoad(self): @@ -654,7 +660,7 @@ cdef class PyBrowser: right = monitorInfo.rcMonitor.right bottom = monitorInfo.rcMonitor.bottom # noinspection PyUnresolvedReferences - SetWindowPos(hwnd, NULL, + SetWindowPos(hwnd, nullptr, left, top, right-left, bottom-top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED) else: @@ -664,7 +670,7 @@ cdef class PyBrowser: if not for_metro: (left, top, right, bottom) = self.windowRect # noinspection PyUnresolvedReferences - SetWindowPos(hwnd, NULL, + SetWindowPos(hwnd, nullptr, int(left), int(top), int(right-left), int(bottom-top), SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED) @@ -679,7 +685,7 @@ cdef class PyBrowser: if "type" in pyEvent: cefEvent.type = int(pyEvent["type"]) if "modifiers" in pyEvent: - cefEvent.modifiers = pyEvent["modifiers"] + cefEvent.modifiers = pyEvent["modifiers"] # Always set CefKeyEvent.windows_key_code in SendKeyEvent, even on # Linux. When sending key event for 'backspace' on Linux and setting # "native_key_code", "character", "unmodified_character" it doesn't @@ -728,23 +734,17 @@ cdef class PyBrowser: self.GetCefBrowserHost().get().SendMouseWheelEvent(mouseEvent, deltaX, deltaY) + # for backward compatibility cpdef py_void SendFocusEvent(self, py_bool setFocus): - self.GetCefBrowserHost().get().SendFocusEvent(bool(setFocus)) + self.GetCefBrowserHost().get().SetFocus(setFocus) cpdef py_void SendCaptureLostEvent(self): self.GetCefBrowserHost().get().SendCaptureLostEvent() - cpdef py_void StartDownload(self, py_string url): + cpdef py_void StartDownload(self, object url): self.GetCefBrowserHost().get().StartDownload(PyToCefStringValue( url)) - cpdef py_void SetMouseCursorChangeDisabled(self, py_bool disabled): - self.GetCefBrowserHost().get().SetMouseCursorChangeDisabled( - bool(disabled)) - - cpdef py_bool IsMouseCursorChangeDisabled(self): - return self.GetCefBrowserHost().get().IsMouseCursorChangeDisabled() - cpdef py_bool TryCloseBrowser(self): return self.GetCefBrowserHost().get().TryCloseBrowser() @@ -757,35 +757,12 @@ cdef class PyBrowser: cpdef py_void NotifyScreenInfoChanged(self): self.GetCefBrowserHost().get().NotifyScreenInfoChanged() - cdef void SendProcessMessage(self, cef_process_id_t targetProcess, - object frameId, py_string messageName, list pyArguments - ) except *: - cdef CefRefPtr[CefProcessMessage] message = \ - CefProcessMessage_Create(PyToCefStringValue(messageName)) - # This does not work, no idea why, the CEF implementation - # seems not to allow it, both Assign() and swap() do not work: - # | message.get().GetArgumentList().Assign(arguments.get()) - # | message.get().GetArgumentList().swap(arguments) - cdef CefRefPtr[CefListValue] messageArguments = \ - message.get().GetArgumentList() - PyListToExistingCefListValue(self.GetIdentifier(), frameId, - pyArguments, messageArguments) - Debug("SendProcessMessage(): message=%s, arguments size=%d" % ( - messageName, - message.get().GetArgumentList().get().GetSize())) - cdef cpp_bool success = \ - self.GetCefBrowser().get().SendProcessMessage( - targetProcess, message) - if not success: - raise Exception("Browser.SendProcessMessage() failed: "\ - "messageName=%s" % messageName) - # ------------------------------------------------------------------------- # OSR drag & drop # ------------------------------------------------------------------------- cpdef py_void DragTargetDragEnter(self, DragData drag_data, int x, int y, - uint32 allowed_ops): + uint32_t allowed_ops): cdef CefMouseEvent mouse_event mouse_event.x = x mouse_event.y = y @@ -793,7 +770,7 @@ cdef class PyBrowser: drag_data.cef_drag_data, mouse_event, allowed_ops) - cpdef py_void DragTargetDragOver(self, int x, int y, uint32 allowed_ops): + cpdef py_void DragTargetDragOver(self, int x, int y, uint32_t allowed_ops): cdef CefMouseEvent mouse_event mouse_event.x = x mouse_event.y = y @@ -809,7 +786,7 @@ cdef class PyBrowser: mouse_event.y = y self.GetCefBrowserHost().get().DragTargetDrop(mouse_event) - cpdef py_void DragSourceEndedAt(self, int x, int y, uint32 operation): + cpdef py_void DragSourceEndedAt(self, int x, int y, uint32_t operation): self.GetCefBrowserHost().get().DragSourceEndedAt( x, y, operation) diff --git a/src/cefpython.pyx b/src/cefpython.pyx index c3b1e1a13..06902ba2a 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -137,21 +137,11 @@ import struct # noinspection PyUnresolvedReferences import base64 -# Must use compile-time condition instead of checking sys.version_info.major -# otherwise results in "ImportError: cannot import name urlencode" strange -# error in Python 3.6. -IF PY_MAJOR_VERSION == 2: - # noinspection PyUnresolvedReferences - import urlparse - # noinspection PyUnresolvedReferences - from urllib import urlencode as urllib_urlencode - from urllib import quote as urlparse_quote -ELSE: - # noinspection PyUnresolvedReferences - from urllib import parse as urlparse - from urllib.parse import quote as urlparse_quote - # noinspection PyUnresolvedReferences - from urllib.parse import urlencode as urllib_urlencode +# noinspection PyUnresolvedReferences +from urllib import parse as urlparse +from urllib.parse import quote as urlparse_quote +# noinspection PyUnresolvedReferences +from urllib.parse import urlencode as urllib_urlencode # noinspection PyUnresolvedReferences from cpython.version cimport PY_MAJOR_VERSION @@ -193,6 +183,8 @@ from libcpp.string cimport string as cpp_string # noinspection PyUnresolvedReferences from wstring cimport wstring as cpp_wstring # noinspection PyUnresolvedReferences +from libcpp.memory cimport unique_ptr +# noinspection PyUnresolvedReferences from libc.string cimport strlen # noinspection PyUnresolvedReferences from libc.string cimport memcpy @@ -225,20 +217,11 @@ ctypedef uintptr_t WindowHandle # noinspection PyUnresolvedReferences cimport ctime -IF UNAME_SYSNAME == "Windows": - from windows cimport * - from dpi_aware_win cimport * -ELIF UNAME_SYSNAME == "Linux": - from linux cimport * -ELIF UNAME_SYSNAME == "Darwin": - from mac cimport * +include "platform_cimports.pxi" from cpp_utils cimport * from task cimport * -IF UNAME_SYSNAME == "Linux": - cimport x11 - from cef_string cimport * cdef extern from *: # noinspection PyUnresolvedReferences @@ -250,16 +233,12 @@ from cef_types cimport ( CefSettings, CefBrowserSettings, CefRect, CefSize, CefPoint, CefKeyEvent, CefMouseEvent, CefScreenInfo, PathKey, PK_DIR_EXE, PK_DIR_MODULE, - int32, uint32, int64, uint64, cef_log_severity_t, ) # 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_app cimport * @@ -273,7 +252,6 @@ from cef_time cimport * from cef_values cimport * from cefpython_app cimport * from cef_process_message cimport * -from cef_web_plugin cimport * from cef_request_handler cimport * from cef_request cimport * from cef_cookie cimport * @@ -321,11 +299,18 @@ g_browser_settings = {} # noinspection PyUnresolvedReferences cdef CefRefPtr[CefRequestContext] g_shared_request_context -cdef scoped_ptr[MainMessageLoopExternalPump] g_external_message_pump +cdef unique_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 py_bool g_context_initialized = False +cdef list g_pending_browsers = [] + +IF UNAME_SYSNAME == "Linux": + # Keeps ctypes callback objects alive for the duration of gtk_main() and + # any pending one-shot GLib timers (Xwayland XReparentWindow scheduling). + g_linux_reparent_callbacks = [] cdef dict g_globalClientCallbacks = {} @@ -358,7 +343,6 @@ include "window_info.pyx" include "process_message_utils.pyx" include "javascript_callback.pyx" include "python_callback.pyx" -include "web_plugin_info.pyx" include "request.pyx" include "cookie.pyx" include "string_visitor.pyx" @@ -376,6 +360,8 @@ include "image.pyx" # Handlers include "handlers/accessibility_handler.pyx" include "handlers/browser_process_handler.pyx" +include "handlers/context_menu_handler.pyx" +include "handlers/cookie_access_filter.pyx" include "handlers/display_handler.pyx" include "handlers/focus_handler.pyx" include "handlers/javascript_dialog_handler.pyx" @@ -393,7 +379,7 @@ include "handlers/v8function_handler.pyx" cdef public void cefpython_GetDebugOptions( cpp_bool* debug - ) except * with gil: + ) noexcept with gil: # Called from subprocess/cefpython_app.cpp -> CefPythonApp constructor. try: debug[0] = bool(g_debug) @@ -404,15 +390,15 @@ cdef public void cefpython_GetDebugOptions( cdef public cpp_bool ApplicationSettings_GetBool(const char* key ) except * with gil: # Called from client_handler/client_handler.cpp for example - cdef py_string pyKey = CharToPyString(key) + cdef object pyKey = CharToPyString(key) if pyKey in g_applicationSettings: return bool(g_applicationSettings[pyKey]) return False cdef public cpp_bool ApplicationSettings_GetBoolFromDict(const char* key1, const char* key2) except * with gil: - cdef py_string pyKey1 = CharToPyString(key1) - cdef py_string pyKey2 = CharToPyString(key2) + cdef object pyKey1 = CharToPyString(key1) + cdef object pyKey2 = CharToPyString(key2) cdef object dictValue # Yet to be checked whether it is `dict` if pyKey1 in g_applicationSettings: dictValue = g_applicationSettings[pyKey1] @@ -424,14 +410,14 @@ cdef public cpp_bool ApplicationSettings_GetBoolFromDict(const char* key1, cdef public cpp_string ApplicationSettings_GetString(const char* key ) except * with gil: - cdef py_string pyKey = CharToPyString(key) + cdef object pyKey = CharToPyString(key) cdef cpp_string cppString if pyKey in g_applicationSettings: cppString = PyStringToChar(AnyToPyString(g_applicationSettings[pyKey])) return cppString cdef public int CommandLineSwitches_GetInt(const char* key) except * with gil: - cdef py_string pyKey = CharToPyString(key) + cdef object pyKey = CharToPyString(key) if pyKey in g_commandLineSwitches: return int(g_commandLineSwitches[pyKey]) return 0 @@ -472,16 +458,6 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): del command_line_switches del commandLineSwitches - IF UNAME_SYSNAME == "Linux": - # Fix Issue #231 - Discovery of the "icudtl.dat" file fails on Linux. - cdef str py_module_dir = GetModuleDirectory() - cdef CefString cef_module_dir - PyToCefString(py_module_dir, cef_module_dir) - CefOverridePath(PK_DIR_EXE, cef_module_dir)\ - or Debug("ERROR: CefOverridePath failed") - CefOverridePath(PK_DIR_MODULE, cef_module_dir)\ - or Debug("ERROR: CefOverridePath failed") - # END IF UNAME_SYSNAME == "Linux": if not application_settings: application_settings = {} @@ -581,8 +557,6 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): # ------------------------------------------------------------------------ if not "multi_threaded_message_loop" in application_settings: application_settings["multi_threaded_message_loop"] = False - if not "single_process" in application_settings: - application_settings["single_process"] = False # ------------------------------------------------------------------------ # ------------------------------------------------------------------------ @@ -593,6 +567,27 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): if not application_settings["cache_path"]: g_commandLineSwitches["disable-gpu-shader-disk-cache"] = "" + if sys.platform == "win32": + # CEF 146 / Chrome 130+ ANGLE D3D11 backend crashes with a CHECK + # failure (STATUS_BREAKPOINT / exit_code=-2147483645) during GPU + # process init, falling back to software rendering after 3 crashes. + # D3D9 avoids the crash but only supports ES 2.0 (ES 3.0 errors). + # OpenGL ANGLE supports ES 3.0 and has no crash. Users can override + # by passing {"use-angle": "d3d11"} in the switches dict. + if "use-angle" not in g_commandLineSwitches: + g_commandLineSwitches["use-angle"] = "gl" + + IF UNAME_SYSNAME == "Linux": + # Initialize GTK so GDK has a display connection before CefInitialize. + _linux_gtk_init() + # Auto-apply switches/settings required for CEF 146 on Linux/Xwayland. + # Uses setdefault so user-supplied values are never overwritten. + _linux_apply_initialize_defaults(application_settings, + g_commandLineSwitches) + # Pre-seed Chrome profile files to prevent the profile-picker keepalive + # from blocking OnContextInitialized (Chrome 146). + if application_settings.get("cache_path"): + _linux_setup_profile(application_settings["cache_path"]) cdef CefRefPtr[CefApp] cefApp = new CefPythonApp() @@ -600,8 +595,29 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): cdef HINSTANCE hInstance = GetModuleHandle(NULL) cdef CefMainArgs cefMainArgs = CefMainArgs(hInstance) ELIF UNAME_SYSNAME == "Linux": - # TODO: use the CefMainArgs(int argc, char** argv) constructor. - cdef CefMainArgs cefMainArgs + # Build a complete argv so the browser process sees all switches. + # OnBeforeChildProcessLaunch only reaches child processes; switches + # like --in-process-gpu and --single-process must be in the browser + # process's own command line to take effect. + _cefMainArgvPyList = [sys.executable.encode('utf-8')] + for _cefArgK, _cefArgV in g_commandLineSwitches.items(): + if _cefArgV: + _cefMainArgvPyList.append( + ("--{}={}".format(_cefArgK, _cefArgV)).encode('utf-8')) + else: + _cefMainArgvPyList.append( + ("--{}".format(_cefArgK)).encode('utf-8')) + cdef int _cefMainArgc = len(_cefMainArgvPyList) + cdef char** _cefMainArgvC = \ + malloc(_cefMainArgc * sizeof(char*)) + cdef bytes _cefMainArgvItem + cdef int _cefMainArgvI + for _cefMainArgvI in range(_cefMainArgc): + _cefMainArgvItem = _cefMainArgvPyList[_cefMainArgvI] + _cefMainArgvC[_cefMainArgvI] = _cefMainArgvItem + cdef CefMainArgs cefMainArgs = CefMainArgs(_cefMainArgc, _cefMainArgvC) + # _cefMainArgvPyList keeps the bytes alive; freed below after + # CefInitialize() has processed the command line. ELIF UNAME_SYSNAME == "Darwin": # TODO: use the CefMainArgs(int argc, char** argv) constructor. cdef CefMainArgs cefMainArgs @@ -617,23 +633,36 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): g_applicationSettings[key] = copy.deepcopy(application_settings[key]) cdef CefSettings cefApplicationSettings - # No sandboxing for the subprocesses - cefApplicationSettings.no_sandbox = 1 + IF UNAME_SYSNAME == "Linux": + # On Linux, leave no_sandbox=0 so Chrome's startup code registers the + # Mojo IPC bootstrap fd (GlobalDescriptors key 7) for every subprocess. + # Setting no_sandbox=1 would cause BasicStartupComplete() to append + # --no-sandbox before fd registration, causing all subprocesses to crash + # with "Failed global descriptor lookup: 7". Sandbox behaviour is instead + # controlled via --disable-setuid-sandbox / --disable-namespace-sandbox + # command-line switches passed by the caller. + pass + ELSE: + # On Windows/macOS the sandbox helper binary is not shipped with + # cefpython, so disable sandboxing entirely. + cefApplicationSettings.no_sandbox = 1 SetApplicationSettings(application_settings, &cefApplicationSettings) # External message pump if GetAppSetting("external_message_pump")\ and not g_external_message_pump.get(): Debug("Create external message pump") + global g_external_message_pump # Using .reset() here to assign new instance was causing # MainMessageLoopExternalPump destructor to be called. Strange. - g_external_message_pump.Assign( - MainMessageLoopExternalPump.Create()) + g_external_message_pump = MainMessageLoopExternalPump.Create() Debug("CefInitialize()") cdef cpp_bool ret with nogil: ret = CefInitialize(cefMainArgs, cefApplicationSettings, cefApp, NULL) + IF UNAME_SYSNAME == "Linux": + free(_cefMainArgvC) global g_cef_initialized g_cef_initialized = True @@ -641,10 +670,35 @@ def Initialize(applicationSettings=None, commandLineSwitches=None, **kwargs): if not ret: Debug("CefInitialize() failed") - IF UNAME_SYSNAME == "Linux": + # Pump the message loop until OnContextInitialized fires. This + # guarantees that CreateBrowserSync() can be called immediately after + # Initialize() without hitting the deferred-creation path or getting + # a null browser from CefBrowserHost::CreateBrowserSync(). + # Use a generous ceiling (30s) for CI environments where utility + # subprocesses (storage service) crash and delay context initialization. + if ret: + # On Linux, skip this pump entirely: the Ozone X11 backend needs + # gtk_main() (a blocking GLib main loop) running before + # OnContextInitialized can fire. The external caller (hello_world.py, + # test harnesses) must enter gtk_main() immediately after Initialize() + # and drive the loop via the GLib timer callback. + # On Windows/macOS, pump up to 30 s as before. + IF UNAME_SYSNAME != "Linux": + for _ in range(3000): + with nogil: + CefDoMessageLoopWork() + if g_context_initialized: + break + time.sleep(0.01) + if not g_context_initialized: + Debug("CefInitialize() WARNING: OnContextInitialized not received" + " within 30 seconds") + + if sys.platform.startswith("linux"): # Install by default. WindowUtils.InstallX11ErrorHandlers() + return ret def CreateBrowser(**kwargs): @@ -670,8 +724,33 @@ def CreateBrowserSync(windowInfo=None, raise Exception("Invalid argument: "+kwarg) Debug("CreateBrowserSync() called") - assert IsThread(TID_UI), ( - "cefpython.CreateBrowserSync() may only be called on the UI thread") + # CEF 146+: CefCurrentlyOn(TID_UI) returns false before MessageLoop starts, + # so skip the assert here and let CEF's own internal checks handle it. + + # Defer browser creation until OnContextInitialized fires inside MessageLoop. + # In CEF 123+, browser creation before OnContextInitialized causes + # blink.mojom.WidgetHost rejection and renderer shows no content. + # Initialize() pumps the loop for up to 30s; if still not initialized + # (e.g. slow CI), pump an additional 30s before giving up. + if not g_context_initialized: + Debug("CreateBrowserSync(): OnContextInitialized not yet received," + " pumping message loop") + IF UNAME_SYSNAME != "Linux": + for _ in range(3000): + with nogil: + CefDoMessageLoopWork() + if g_context_initialized: + break + time.sleep(0.01) + if not g_context_initialized: + Debug("CreateBrowserSync() deferred until OnContextInitialized") + g_pending_browsers.append({ + "windowInfo": windowInfo, + "browserSettings": browserSettings, + "navigateUrl": navigateUrl, + "window_title": window_title, + }) + return None """ # CEF views @@ -720,6 +799,18 @@ def CreateBrowserSync(windowInfo=None, elif not isinstance(windowInfo, WindowInfo): raise Exception("CreateBrowserSync() failed: windowInfo: invalid object") + # On Linux, when no parent window is given, auto-create a GTK toplevel + # so callers need no GTK-specific code (same API as Windows/Mac). + _linux_toplevel_state = None + IF UNAME_SYSNAME == "Linux": + if windowInfo.windowType == "child" and windowInfo.parentWindowHandle == 0: + _linux_toplevel_state = _linux_create_toplevel( + window_title or "CEF Browser") + windowInfo.SetAsChild( + _linux_toplevel_state['xid'], + [0, 0, _linux_toplevel_state['width'], + _linux_toplevel_state['height']]) + if window_title and windowInfo.parentWindowHandle == 0: windowInfo.windowName = window_title @@ -761,14 +852,16 @@ def CreateBrowserSync(windowInfo=None, else: cefRequestContext.Assign(g_shared_request_context.get()) + cdef CefRefPtr[CefDictionaryValue] extra_info + # CEF browser creation. with nogil: cefBrowser = cef_browser_static.CreateBrowserSync( cefWindowInfo, clientHandler, - cefNavigateUrl, cefBrowserSettings, + cefNavigateUrl, cefBrowserSettings, extra_info, cefRequestContext) - if cefBrowser == NULL or not cefBrowser.get(): + if not cefBrowser or not cefBrowser.get(): Debug("CefBrowser::CreateBrowserSync() failed") return None else: @@ -816,6 +909,12 @@ def CreateBrowserSync(windowInfo=None, MacSetWindowTitle(cefBrowser, PyStringToChar(windowInfo.windowName)) + IF UNAME_SYSNAME == "Linux": + if windowInfo._linux_embed_info: + _linux_schedule_xembed(pyBrowser, windowInfo._linux_embed_info) + if _linux_toplevel_state is not None: + _linux_register_window_callbacks(pyBrowser, _linux_toplevel_state) + return pyBrowser def MessageLoop(): @@ -825,8 +924,11 @@ def MessageLoop(): global g_MessageLoop_called g_MessageLoop_called = True - with nogil: - CefRunMessageLoop() + IF UNAME_SYSNAME == "Linux": + _linux_message_loop() + ELSE: + with nogil: + CefRunMessageLoop() def MessageLoopWork(): # Perform a single iteration of CEF message loop processing. @@ -852,6 +954,15 @@ def SingleMessageLoop(): def QuitMessageLoop(): Debug("QuitMessageLoop()") + IF UNAME_SYSNAME == "Linux": + import ctypes as _ct + _gtk = _ct.CDLL("libgtk-3.so.0") + # Only call gtk_main_quit() when a GTK main loop is actually running. + # _on_delete() may have already called it (via gtk_main_quit directly), + # and calling it a second time during the drain would generate a + # spurious "assertion 'main_loops != NULL' failed" warning. + if _gtk.gtk_main_level() > 0: + _gtk.gtk_main_quit() with nogil: CefQuitMessageLoop() @@ -961,17 +1072,18 @@ def Shutdown(): MacShutdown() def SetOsModalLoop(py_bool modalLoop): - cdef cpp_bool cefModalLoop = bool(modalLoop) - with nogil: - CefSetOSModalLoop(cefModalLoop) + IF UNAME_SYSNAME == "Windows": + cdef cpp_bool cefModalLoop = bool(modalLoop) + with nogil: + CefSetOSModalLoop(cefModalLoop) -cpdef py_void SetGlobalClientCallback(py_string name, object callback): +cpdef py_void SetGlobalClientCallback(object name, object callback): global g_globalClientCallbacks # Global callbacks are prefixed with "_" in documentation. # Accept both with and without a prefix. if name.startswith("_"): name = name[1:] - if name in ["OnCertificateError", "OnBeforePluginLoad", "OnAfterCreated", + if name in ["OnCertificateError", "OnAfterCreated", "OnAccessibilityTreeChange", "OnAccessibilityLocationChange"]: g_globalClientCallbacks[name] = callback else: @@ -983,7 +1095,7 @@ cpdef py_void SetGlobalClientHandler(object clientHandler): raise Exception("SetGlobalClientHandler() failed: __class__ " "attribute missing") cdef dict methods = {} - cdef py_string key + cdef object key cdef object method cdef tuple value for value in inspect.getmembers(clientHandler, @@ -993,14 +1105,14 @@ cpdef py_void SetGlobalClientHandler(object clientHandler): if key and key[0:2] != '__': SetGlobalClientCallback(key, method) -cpdef object GetGlobalClientCallback(py_string name): +cpdef object GetGlobalClientCallback(object name): global g_globalClientCallbacks if name in g_globalClientCallbacks: return g_globalClientCallbacks[name] else: return None -cpdef object GetAppSetting(py_string key): +cpdef object GetAppSetting(object key): global g_applicationSettings if key in g_applicationSettings: return g_applicationSettings[key] @@ -1019,7 +1131,7 @@ cpdef dict GetVersion(): cef_commit_number=__cef_commit_number__, ) -cpdef LoadCrlSetsFile(py_string path): +cpdef LoadCrlSetsFile(object path): CefLoadCRLSetsFile(PyToCefStringValue(path)) cpdef GetDataUrl(data, mediatype="html"): diff --git a/src/cefpython3.wx/chromectrl.py b/src/cefpython3.wx/chromectrl.py index cf06f82a2..0c0129d31 100644 --- a/src/cefpython3.wx/chromectrl.py +++ b/src/cefpython3.wx/chromectrl.py @@ -160,9 +160,6 @@ def __init__(self, parent, url="", useTimer=True, if not browserSettings: browserSettings = {} - # Disable plugins: - # | browserSettings["plugins_disabled"] = True - self.browser = cefpython.CreateBrowserSync(windowInfo, browserSettings=browserSettings, navigateUrl=url) diff --git a/src/client_handler/CMakeLists.txt b/src/client_handler/CMakeLists.txt new file mode 100644 index 000000000..354f7bb62 --- /dev/null +++ b/src/client_handler/CMakeLists.txt @@ -0,0 +1,63 @@ +file(GLOB _all_cpp "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +if(APPLE) + file(GLOB _mm_files "${CMAKE_CURRENT_SOURCE_DIR}/*.mm") + list(APPEND _all_cpp ${_mm_files}) +endif() + +set(_sources "") +foreach(_f IN LISTS _all_cpp) + get_filename_component(_name "${_f}" NAME) + set(_skip FALSE) + if(_name MATCHES "_win\\.cpp$" AND NOT WIN32) + set(_skip TRUE) + elseif(_name MATCHES "_linux\\.cpp$" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(_skip TRUE) + elseif(_name MATCHES "_mac\\.(cpp|mm)$" AND NOT APPLE) + set(_skip TRUE) + elseif(_name MATCHES "^x11\\.cpp$" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(_skip TRUE) + elseif(_name MATCHES "gtk" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(_skip TRUE) + endif() + if(NOT _skip) + list(APPEND _sources "${_f}") + endif() +endforeach() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig REQUIRED) + pkg_check_modules(GTK3 REQUIRED gtk+-3.0) +endif() + +add_library(client_handler STATIC ${_sources}) +add_dependencies(client_handler cefpython_headers) + +target_include_directories(client_handler PRIVATE + "${CEFPYTHON_SRC_DIR}" + "${CEFPYTHON_SRC_DIR}/common" + "${CEFPYTHON_PYX_STAGE_DIR}" + ${Python_INCLUDE_DIRS} +) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_include_directories(client_handler PRIVATE ${GTK3_INCLUDE_DIRS}) +endif() + +if(WIN32) + target_compile_options(client_handler PRIVATE /EHsc /wd4190) + target_compile_definitions(client_handler PRIVATE + WIN32 _WIN32 _WINDOWS + NTDDI_VERSION=0x06010000 WINVER=0x0601 _WIN32_WINNT=0x0601 + NDEBUG _NDEBUG _CRT_SECURE_NO_WARNINGS NOMINMAX + BROWSER_PROCESS + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h" + ) +elseif(APPLE) + target_compile_options(client_handler PRIVATE -DNDEBUG -O3) + target_compile_definitions(client_handler PRIVATE + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h") +else() + target_compile_options(client_handler PRIVATE -DNDEBUG -O3 -Wno-stringop-overflow) + target_compile_definitions(client_handler PRIVATE + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h") +endif() diff --git a/src/client_handler/Makefile b/src/client_handler/Makefile index 25e645ff7..84472c744 100644 --- a/src/client_handler/Makefile +++ b/src/client_handler/Makefile @@ -23,7 +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 accessibility_handler.cpp \ + cef_log.cpp accessibility_handler.cpp cookie_access_filter.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 daf9a4e59..02190eaa1 100644 --- a/src/client_handler/client_handler.cpp +++ b/src/client_handler/client_handler.cpp @@ -27,6 +27,7 @@ bool ClientHandler::OnProcessMessageReceived( CefRefPtr browser, + CefRefPtr frame, CefProcessId source_process, CefRefPtr message) { @@ -34,16 +35,14 @@ bool ClientHandler::OnProcessMessageReceived( if (source_process != PID_RENDERER) { return false; } - std::string messageName = message->GetName().ToString(); + const std::string& messageName = message->GetName(); std::string logMessage = "[Browser process] OnProcessMessageReceived(): "; logMessage.append(messageName.c_str()); LOG(INFO) << logMessage.c_str(); if (messageName == "OnContextCreated") { CefRefPtr arguments = message->GetArgumentList(); - if (arguments->GetSize() == 1 && arguments->GetType(0) == VTYPE_INT) { - int64 frameId = arguments->GetInt(0); - CefRefPtr frame = browser->GetFrame(frameId); - if (!frame.get()) { + if (arguments->GetSize() == 1 && arguments->GetType(0) == VTYPE_STRING) { + if (!frame.get()) { // Frame was already destroyed while IPC messaging was // executing. Issue #431. User callback will not be // executed in such case. @@ -60,9 +59,9 @@ bool ClientHandler::OnProcessMessageReceived( CefRefPtr arguments = message->GetArgumentList(); if (arguments->GetSize() == 2 \ && arguments->GetType(0) == VTYPE_INT \ - && arguments->GetType(1) == VTYPE_INT) { + && arguments->GetType(1) == VTYPE_STRING) { int browserId = arguments->GetInt(0); - int64 frameId = arguments->GetInt(1); + CefString frameId = arguments->GetString(1); // Even if frame was alrady destroyed (Issue #431) you still // want to call V8ContextHandler_OnContextReleased as it releases // some resources. Thus passing IDs instead of actual @@ -79,12 +78,12 @@ bool ClientHandler::OnProcessMessageReceived( CefRefPtr arguments = message->GetArgumentList(); if (arguments->GetSize() == 3 // frameId - && arguments->GetType(0) == VTYPE_INT + && arguments->GetType(0) == VTYPE_STRING // functionName && arguments->GetType(1) == VTYPE_STRING // functionArguments && arguments->GetType(2) == VTYPE_LIST) { - int64 frameId = arguments->GetInt(0); + CefString frameId = arguments->GetString(0); CefString functionName = arguments->GetString(1); CefRefPtr functionArguments = arguments->GetList(2); // Even if frame was already destroyed (Issue #431) you still diff --git a/src/client_handler/client_handler.h b/src/client_handler/client_handler.h index 3e9e3917b..e52d0b50c 100644 --- a/src/client_handler/client_handler.h +++ b/src/client_handler/client_handler.h @@ -87,6 +87,7 @@ class ClientHandler : public CefClient, } bool OnProcessMessageReceived(CefRefPtr browser, + CefRefPtr frame, CefProcessId source_process, CefRefPtr message ) override; diff --git a/src/client_handler/context_menu_handler.cpp b/src/client_handler/context_menu_handler.cpp index e6be3986b..5ee2b559d 100644 --- a/src/client_handler/context_menu_handler.cpp +++ b/src/client_handler/context_menu_handler.cpp @@ -75,6 +75,18 @@ void ContextMenuHandler::OnBeforeContextMenu( } +bool ContextMenuHandler::RunContextMenu( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr params, + CefRefPtr model, + CefRefPtr callback) +{ + REQUIRE_UI_THREAD(); + return ContextMenuHandler_RunContextMenu(browser, model, callback) != 0; +} + + bool ContextMenuHandler::OnContextMenuCommand( CefRefPtr browser, CefRefPtr frame, diff --git a/src/client_handler/context_menu_handler.h b/src/client_handler/context_menu_handler.h index e22f7541b..2eebcc7cd 100644 --- a/src/client_handler/context_menu_handler.h +++ b/src/client_handler/context_menu_handler.h @@ -21,6 +21,12 @@ class ContextMenuHandler : public CefContextMenuHandler CefRefPtr params, CefRefPtr model) override; + bool RunContextMenu(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr params, + CefRefPtr model, + CefRefPtr callback) override; + bool OnContextMenuCommand(CefRefPtr browser, CefRefPtr frame, CefRefPtr params, diff --git a/src/client_handler/cookie_access_filter.cpp b/src/client_handler/cookie_access_filter.cpp new file mode 100644 index 000000000..6eaeb70bf --- /dev/null +++ b/src/client_handler/cookie_access_filter.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2018 CEF Python, see the Authors file. +// All rights reserved. Licensed under BSD 3-clause license. +// Project website: https://github.com/cztomczak/cefpython + +#include "cookie_access_filter.h" +#include "common/cefpython_public_api.h" + + +bool CookieAccessFilter::CanSendCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + const CefCookie& cookie) { + REQUIRE_IO_THREAD(); + return CookieAccessFilter_CanSendCookie(browser, frame, request, cookie); +} + +bool CookieAccessFilter::CanSaveCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response, + const CefCookie& cookie) { + REQUIRE_IO_THREAD(); + return CookieAccessFilter_CanSaveCookie(browser, frame, request, response, cookie); +} diff --git a/src/client_handler/cookie_access_filter.h b/src/client_handler/cookie_access_filter.h new file mode 100644 index 000000000..e914314cf --- /dev/null +++ b/src/client_handler/cookie_access_filter.h @@ -0,0 +1,28 @@ +// Copyright (c) 2018 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_resource_request_handler.h" + + +class CookieAccessFilter : public CefCookieAccessFilter +{ +public: + CookieAccessFilter(){} + virtual ~CookieAccessFilter(){} + + virtual bool CanSendCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + const CefCookie& cookie) override; + virtual bool CanSaveCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr response, + const CefCookie& cookie) override; + +private: + IMPLEMENT_REFCOUNTING(CookieAccessFilter); +}; diff --git a/src/client_handler/cookie_visitor.cpp b/src/client_handler/cookie_visitor.cpp index eea845f35..1ac11add8 100644 --- a/src/client_handler/cookie_visitor.cpp +++ b/src/client_handler/cookie_visitor.cpp @@ -11,7 +11,7 @@ bool CookieVisitor::Visit( int total, bool& deleteCookie ) { - REQUIRE_IO_THREAD(); + REQUIRE_UI_THREAD(); return CookieVisitor_Visit(cookieVisitorId_, cookie, count, total, deleteCookie); } diff --git a/src/client_handler/cookie_visitor.h b/src/client_handler/cookie_visitor.h index 54c2b53fd..0b990baca 100644 --- a/src/client_handler/cookie_visitor.h +++ b/src/client_handler/cookie_visitor.h @@ -24,7 +24,7 @@ class CookieVisitor : public CefCookieVisitor int count, int total, bool& deleteCookie - ) OVERRIDE; + ) override; protected: IMPLEMENT_REFCOUNTING(CookieVisitor); diff --git a/src/client_handler/dialog_handler.cpp b/src/client_handler/dialog_handler.cpp index ab90de9b6..4f7117590 100644 --- a/src/client_handler/dialog_handler.cpp +++ b/src/client_handler/dialog_handler.cpp @@ -19,7 +19,8 @@ bool DialogHandler::OnFileDialog(CefRefPtr browser, const CefString& title, const CefString& default_file_path, const std::vector& accept_filters, - int selected_accept_filter, + const std::vector& accept_extensions, + const std::vector& accept_descriptions, CefRefPtr callback) { #if defined(OS_LINUX) @@ -28,7 +29,8 @@ bool DialogHandler::OnFileDialog(CefRefPtr browser, title, default_file_path, accept_filters, - selected_accept_filter, + accept_extensions, + accept_descriptions, callback); #else return false; diff --git a/src/client_handler/dialog_handler.h b/src/client_handler/dialog_handler.h index 21d79a60d..1698c47bf 100644 --- a/src/client_handler/dialog_handler.h +++ b/src/client_handler/dialog_handler.h @@ -23,7 +23,8 @@ class DialogHandler : public CefDialogHandler const CefString& title, const CefString& default_file_path, const std::vector& accept_filters, - int selected_accept_filter, + const std::vector& accept_extensions, + const std::vector& accept_descriptions, CefRefPtr callback) override; diff --git a/src/client_handler/dialog_handler_gtk.cpp b/src/client_handler/dialog_handler_gtk.cpp index ce5a4d4e0..9326940c4 100644 --- a/src/client_handler/dialog_handler_gtk.cpp +++ b/src/client_handler/dialog_handler_gtk.cpp @@ -30,64 +30,44 @@ std::string GetPromptText(GtkDialog* dialog) { 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); +// Split |str| on |delim| and return the parts. +std::vector SplitString(const std::string& str, char delim) { + std::vector result; + std::string token; + for (char c : str) { + if (c == delim) { + if (!token.empty()) + result.push_back(token); + token.clear(); + } else { + token += c; + } } - - return std::string(); + if (!token.empty()) + result.push_back(token); + return result; } void AddFilters(GtkFileChooser* chooser, const std::vector& accept_filters, + const std::vector& accept_extensions, + const std::vector& accept_descriptions, 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]; + for (size_t j = 0; j < accept_filters.size(); ++j) { + const std::string& filter = accept_filters[j]; 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); + // Use pre-parsed extensions when available. + std::vector extensions = + SplitString(accept_extensions[j], ';'); + std::string description = accept_descriptions[j]; + if (extensions.empty()) { + // Fallback: convert MIME type to extensions. std::vector ext; CefGetExtensionsForMimeType(filter, ext); for (size_t x = 0; x < ext.size(); ++x) @@ -101,7 +81,9 @@ void AddFilters(GtkFileChooser* chooser, std::string ext_str; for (size_t x = 0; x < extensions.size(); ++x) { - const std::string& pattern = "*" + extensions[x]; + std::string pattern = extensions[x]; + if (pattern[0] != '*') + pattern = "*" + pattern; if (x != 0) ext_str += ";"; ext_str += pattern; @@ -133,7 +115,7 @@ void AddFilters(GtkFileChooser* chooser, } // namespace -ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(NULL) {} +ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(nullptr) {} bool ClientDialogHandlerGtk::OnFileDialog( CefRefPtr browser, @@ -141,26 +123,23 @@ bool ClientDialogHandlerGtk::OnFileDialog( const CefString& title, const CefString& default_file_path, const std::vector& accept_filters, - int selected_accept_filter, + const std::vector& accept_extensions, + const std::vector& accept_descriptions, 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) { + if (mode == FILE_DIALOG_OPEN || mode == FILE_DIALOG_OPEN_MULTIPLE) { action = GTK_FILE_CHOOSER_ACTION_OPEN; - accept_button = GTK_STOCK_OPEN; - } else if (mode_type == FILE_DIALOG_OPEN_FOLDER) { + accept_button = "_Open"; + } else if (mode == FILE_DIALOG_OPEN_FOLDER) { action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - accept_button = GTK_STOCK_OPEN; - } else if (mode_type == FILE_DIALOG_SAVE) { + accept_button = "_Open"; + } else if (mode == FILE_DIALOG_SAVE) { action = GTK_FILE_CHOOSER_ACTION_SAVE; - accept_button = GTK_STOCK_SAVE; + accept_button = "_Save"; } else { NOTREACHED(); return false; @@ -170,7 +149,7 @@ bool ClientDialogHandlerGtk::OnFileDialog( if (!title.empty()) { title_str = title; } else { - switch (mode_type) { + switch (mode) { case FILE_DIALOG_OPEN: title_str = "Open File"; break; @@ -193,21 +172,13 @@ bool ClientDialogHandlerGtk::OnFileDialog( 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); + title_str.c_str(), GTK_WINDOW(window), action, "_Cancel", + GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, nullptr); - if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) + if (mode == 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) { + if (!default_file_path.empty() && mode == FILE_DIALOG_SAVE) { const std::string& file_path = default_file_path; bool exists = false; @@ -227,25 +198,22 @@ bool ClientDialogHandlerGtk::OnFileDialog( } 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]); - } + AddFilters(GTK_FILE_CHOOSER(dialog), accept_filters, accept_extensions, + accept_descriptions, true, &filters); 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) { + if (mode == FILE_DIALOG_OPEN || mode == FILE_DIALOG_OPEN_FOLDER || + mode == 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) { + } else if (mode == FILE_DIALOG_OPEN_MULTIPLE) { GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); if (filenames) { - for (GSList* iter = filenames; iter != NULL; + for (GSList* iter = filenames; iter != nullptr; iter = g_slist_next(iter)) { std::string path(static_cast(iter->data)); g_free(iter->data); @@ -257,24 +225,10 @@ bool ClientDialogHandlerGtk::OnFileDialog( } } - 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); + callback->Continue(files); else callback->Cancel(); @@ -316,11 +270,6 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, js_dialog_callback_ = callback; - if (!origin_url.empty()) { - // title += " - "; - // title += CefFormatUrlForSecurityDisplay(origin_url).ToString(); - } - GtkWindow* window = CefBrowser_GetGtkWindow(browser); if (!window) return false; @@ -329,12 +278,12 @@ bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, 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); + G_CALLBACK(gtk_widget_hide_on_delete), nullptr); 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); + "_OK", GTK_RESPONSE_OK); if (dialog_type != JSDIALOGTYPE_PROMPT) gtk_widget_grab_focus(ok_button); @@ -378,8 +327,8 @@ void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr browser) { if (!gtk_dialog_) return; gtk_widget_destroy(gtk_dialog_); - gtk_dialog_ = NULL; - js_dialog_callback_ = NULL; + gtk_dialog_ = nullptr; + js_dialog_callback_ = nullptr; } // static @@ -401,5 +350,5 @@ void ClientDialogHandlerGtk::OnDialogResponse(GtkDialog* dialog, NOTREACHED(); } - handler->OnResetDialogState(NULL); + handler->OnResetDialogState(nullptr); } diff --git a/src/client_handler/dialog_handler_gtk.h b/src/client_handler/dialog_handler_gtk.h index 59f65eda1..85c0fed42 100644 --- a/src/client_handler/dialog_handler_gtk.h +++ b/src/client_handler/dialog_handler_gtk.h @@ -25,8 +25,9 @@ class ClientDialogHandlerGtk : public CefDialogHandler, const CefString& title, const CefString& default_file_path, const std::vector& accept_filters, - int selected_accept_filter, - CefRefPtr callback) OVERRIDE; + const std::vector& accept_extensions, + const std::vector& accept_descriptions, + CefRefPtr callback) override; // CefJSDialogHandler methods. bool OnJSDialog(CefRefPtr browser, @@ -35,12 +36,12 @@ class ClientDialogHandlerGtk : public CefDialogHandler, const CefString& message_text, const CefString& default_prompt_text, CefRefPtr callback, - bool& suppress_message) OVERRIDE; + bool& suppress_message) override; bool OnBeforeUnloadDialog(CefRefPtr browser, const CefString& message_text, bool is_reload, - CefRefPtr callback) OVERRIDE; - void OnResetDialogState(CefRefPtr browser) OVERRIDE; + CefRefPtr callback) override; + void OnResetDialogState(CefRefPtr browser) override; private: static void OnDialogResponse(GtkDialog* dialog, diff --git a/src/client_handler/display_handler.cpp b/src/client_handler/display_handler.cpp index b00bf6a51..573549e26 100644 --- a/src/client_handler/display_handler.cpp +++ b/src/client_handler/display_handler.cpp @@ -59,4 +59,13 @@ void DisplayHandler::OnLoadingProgressChange(CefRefPtr browser, double progress) { REQUIRE_UI_THREAD(); return DisplayHandler_OnLoadingProgressChange(browser, progress); +} + +bool DisplayHandler::OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) +{ + REQUIRE_UI_THREAD(); + return DisplayHandler_OnCursorChange(browser, cursor); } \ No newline at end of file diff --git a/src/client_handler/display_handler.h b/src/client_handler/display_handler.h index a281713d9..382bceeaa 100644 --- a/src/client_handler/display_handler.h +++ b/src/client_handler/display_handler.h @@ -38,6 +38,12 @@ class DisplayHandler : public CefDisplayHandler void OnLoadingProgressChange(CefRefPtr browser, double progress) override; + + bool OnCursorChange(CefRefPtr browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info + ) override; private: IMPLEMENT_REFCOUNTING(DisplayHandler); diff --git a/src/client_handler/download_handler.cpp b/src/client_handler/download_handler.cpp index 9e887e467..ac63a4eee 100644 --- a/src/client_handler/download_handler.cpp +++ b/src/client_handler/download_handler.cpp @@ -6,7 +6,7 @@ #include "include/base/cef_logging.h" -void DownloadHandler::OnBeforeDownload( +bool DownloadHandler::OnBeforeDownload( CefRefPtr browser, CefRefPtr download_item, const CefString& suggested_name, @@ -23,6 +23,7 @@ void DownloadHandler::OnBeforeDownload( LOG(INFO) << "[Browser process] Tried to download file," " but downloads are disabled"; } + return false; } diff --git a/src/client_handler/download_handler.h b/src/client_handler/download_handler.h index e4fc5faf1..a64ba17fd 100644 --- a/src/client_handler/download_handler.h +++ b/src/client_handler/download_handler.h @@ -12,7 +12,7 @@ class DownloadHandler : public CefDownloadHandler DownloadHandler(){} virtual ~DownloadHandler(){} - void OnBeforeDownload(CefRefPtr browser, + bool OnBeforeDownload(CefRefPtr browser, CefRefPtr download_item, const CefString& suggested_name, CefRefPtr callback diff --git a/src/client_handler/dpi_aware.cpp b/src/client_handler/dpi_aware.cpp index 48e2a13f7..a7854079f 100644 --- a/src/client_handler/dpi_aware.cpp +++ b/src/client_handler/dpi_aware.cpp @@ -4,6 +4,8 @@ // Windows only +#ifdef _WIN32 + #pragma comment(lib, "Gdi32.lib") #include @@ -11,6 +13,7 @@ #include "include/wrapper/cef_closure_task.h" #include "include/base/cef_bind.h" #include "include/base/cef_logging.h" +#include "include/base/cef_callback.h" const int DEFAULT_DPIX = 96; @@ -219,10 +222,11 @@ void SetBrowserDpiSettings(CefRefPtr cefBrowser, CefPostDelayedTask( TID_UI, CefCreateClosureTask( - base::Bind(&SetBrowserDpiSettings, + base::BindOnce(&SetBrowserDpiSettings, cefBrowser, autoZooming) ), 50 ); } +#endif // _WIN32 diff --git a/src/client_handler/lifespan_handler.cpp b/src/client_handler/lifespan_handler.cpp index b4d7d52f6..1298a4112 100644 --- a/src/client_handler/lifespan_handler.cpp +++ b/src/client_handler/lifespan_handler.cpp @@ -11,6 +11,7 @@ bool LifespanHandler::OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, WindowOpenDisposition target_disposition, @@ -19,6 +20,7 @@ bool LifespanHandler::OnBeforePopup(CefRefPtr browser, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings, + CefRefPtr& extra_info, bool* no_javascript_access) { REQUIRE_UI_THREAD(); @@ -27,7 +29,7 @@ bool LifespanHandler::OnBeforePopup(CefRefPtr browser, return LifespanHandler_OnBeforePopup(browser, frame, target_url, target_frame_name, target_disposition, user_gesture, popupFeaturesNotImpl, windowInfo, client, settings, - no_javascript_access); + extra_info, no_javascript_access); } diff --git a/src/client_handler/lifespan_handler.h b/src/client_handler/lifespan_handler.h index 91244eff9..ddef2bc91 100644 --- a/src/client_handler/lifespan_handler.h +++ b/src/client_handler/lifespan_handler.h @@ -16,6 +16,7 @@ class LifespanHandler : public CefLifeSpanHandler bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, WindowOpenDisposition target_disposition, @@ -24,6 +25,7 @@ class LifespanHandler : public CefLifeSpanHandler CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings, + CefRefPtr& extra_info, bool* no_javascript_access) override; void OnAfterCreated(CefRefPtr browser) override; bool DoClose(CefRefPtr browser) override; diff --git a/src/client_handler/render_handler.cpp b/src/client_handler/render_handler.cpp index cf45d15ee..1935a3baf 100644 --- a/src/client_handler/render_handler.cpp +++ b/src/client_handler/render_handler.cpp @@ -13,11 +13,11 @@ bool RenderHandler::GetRootScreenRect(CefRefPtr browser, } -bool RenderHandler::GetViewRect(CefRefPtr browser, +void RenderHandler::GetViewRect(CefRefPtr browser, CefRect& rect) { REQUIRE_UI_THREAD(); - return RenderHandler_GetViewRect(browser, rect); + RenderHandler_GetViewRect(browser, rect); } @@ -69,16 +69,6 @@ void RenderHandler::OnPaint(CefRefPtr browser, } -void RenderHandler::OnCursorChange(CefRefPtr browser, - CefCursorHandle cursor, - CursorType type, - const CefCursorInfo& custom_cursor_info) -{ - REQUIRE_UI_THREAD(); - RenderHandler_OnCursorChange(browser, cursor); -} - - void RenderHandler::OnScrollOffsetChanged(CefRefPtr browser, double x, double y) diff --git a/src/client_handler/render_handler.h b/src/client_handler/render_handler.h index 75eee86c5..c87c52354 100644 --- a/src/client_handler/render_handler.h +++ b/src/client_handler/render_handler.h @@ -22,7 +22,7 @@ class RenderHandler : public CefRenderHandler, bool GetRootScreenRect(CefRefPtr browser, CefRect& rect) override; - bool GetViewRect(CefRefPtr browser, + void GetViewRect(CefRefPtr browser, CefRect& rect) override; bool GetScreenPoint(CefRefPtr browser, @@ -46,12 +46,6 @@ class RenderHandler : public CefRenderHandler, const void* buffer, int width, int height) override; - void OnCursorChange(CefRefPtr browser, - CefCursorHandle cursor, - CursorType type, - const CefCursorInfo& custom_cursor_info - ) override; - void OnScrollOffsetChanged(CefRefPtr browser, double x, double y) override; diff --git a/src/client_handler/request_context_handler.cpp b/src/client_handler/request_context_handler.cpp deleted file mode 100644 index bf816cf51..000000000 --- a/src/client_handler/request_context_handler.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2013 CEF Python, see the Authors file. -// All rights reserved. Licensed under BSD 3-clause license. -// Project website: https://github.com/cztomczak/cefpython - -#include "request_context_handler.h" -#include "common/cefpython_public_api.h" - -// -------------------------------------------------------------------------- -// CefRequestContextHandler -// -------------------------------------------------------------------------- - -CefRefPtr RequestContextHandler::GetCookieManager() { - REQUIRE_IO_THREAD(); - if (browser_.get()) { - return RequestHandler_GetCookieManager(browser_, - browser_->GetMainFrame()->GetURL()); - } else { - CefString mainUrl; - return RequestHandler_GetCookieManager(browser_, mainUrl); - } - // Default: return NULL. -} - -bool RequestContextHandler::OnBeforePluginLoad( - const CefString& mime_type, - const CefString& plugin_url, - bool is_main_frame, - const CefString& top_origin_url, - CefRefPtr plugin_info, - PluginPolicy* plugin_policy) { - // Called on multiple threads - return RequestHandler_OnBeforePluginLoad(browser_, - mime_type, - plugin_url, - is_main_frame, - top_origin_url, - plugin_info, - plugin_policy); -} diff --git a/src/client_handler/request_context_handler.h b/src/client_handler/request_context_handler.h index b8bf25f3a..6c4e3218d 100644 --- a/src/client_handler/request_context_handler.h +++ b/src/client_handler/request_context_handler.h @@ -10,12 +10,14 @@ #include "common/cefpython_public_api.h" +#include "include/cef_request_context_handler.h" +#include "include/base/cef_callback.h" + class RequestContextHandler : public CefRequestContextHandler { private: CefRefPtr browser_; - typedef cef_plugin_policy_t PluginPolicy; public: // Browser may be NULL when instantiated from cefpython.CreateBrowserSync. @@ -29,14 +31,6 @@ class RequestContextHandler : browser_ = browser; } - virtual CefRefPtr GetCookieManager() OVERRIDE; - virtual bool OnBeforePluginLoad(const CefString& mime_type, - const CefString& plugin_url, - bool is_main_frame, - const CefString& top_origin_url, - CefRefPtr plugin_info, - PluginPolicy* plugin_policy) OVERRIDE; - private: IMPLEMENT_REFCOUNTING(RequestContextHandler); }; diff --git a/src/client_handler/request_handler.cpp b/src/client_handler/request_handler.cpp index 5227b8921..964dacf19 100644 --- a/src/client_handler/request_handler.cpp +++ b/src/client_handler/request_handler.cpp @@ -4,6 +4,19 @@ #include "request_handler.h" #include "include/base/cef_logging.h" +#include "include/base/cef_callback.h" + + +CefRefPtr RequestHandler::GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) { + return new ResourceRequestHandler(); +} bool RequestHandler::OnBeforeBrowse(CefRefPtr browser, @@ -22,7 +35,7 @@ ReturnValue RequestHandler::OnBeforeResourceLoad( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, - CefRefPtr callback) + CefRefPtr callback) { REQUIRE_IO_THREAD(); bool retval = RequestHandler_OnBeforeResourceLoad(browser, frame, request); @@ -73,8 +86,8 @@ bool RequestHandler::GetAuthCredentials(CefRefPtr browser, bool RequestHandler::OnQuotaRequest(CefRefPtr browser, const CefString& origin_url, - int64 new_size, - CefRefPtr callback) { + int64_t new_size, + CefRefPtr callback) { REQUIRE_IO_THREAD(); return RequestHandler_OnQuotaRequest(browser, origin_url, new_size, callback); @@ -94,7 +107,7 @@ bool RequestHandler::OnCertificateError( cef_errorcode_t cert_error, const CefString& request_url, CefRefPtr ssl_info, // not used - CefRefPtr callback) + CefRefPtr callback) { REQUIRE_UI_THREAD(); return RequestHandler_OnCertificateError(cert_error, request_url, @@ -103,32 +116,11 @@ bool RequestHandler::OnCertificateError( void RequestHandler::OnRenderProcessTerminated(CefRefPtr browser, - cef_termination_status_t status) + cef_termination_status_t status, + int error_code, + const CefString& error_string) { REQUIRE_UI_THREAD(); LOG(ERROR) << "[Browser process] OnRenderProcessTerminated()"; RequestHandler_OnRendererProcessTerminated(browser, status); } - - -void RequestHandler::OnPluginCrashed(CefRefPtr browser, - const CefString& plugin_path) -{ - REQUIRE_UI_THREAD(); - RequestHandler_OnPluginCrashed(browser, plugin_path); -} - -bool RequestHandler::CanGetCookies(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr request) { - REQUIRE_IO_THREAD(); - return RequestHandler_CanGetCookies(browser, frame, request); -} - -bool RequestHandler::CanSetCookie(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr request, - const CefCookie& cookie) { - REQUIRE_IO_THREAD(); - return RequestHandler_CanSetCookie(browser, frame, request, cookie); -} diff --git a/src/client_handler/request_handler.h b/src/client_handler/request_handler.h index 7e5e0e6c3..b8c10d877 100644 --- a/src/client_handler/request_handler.h +++ b/src/client_handler/request_handler.h @@ -4,16 +4,29 @@ #include "common/cefpython_public_api.h" #include "include/cef_request_handler.h" +#include "include/base/cef_callback.h" +#include "cookie_access_filter.h" +#include "resource_request_handler.h" typedef cef_return_value_t ReturnValue; -class RequestHandler : public CefRequestHandler +class RequestHandler : public CefRequestHandler, + public CookieAccessFilter { public: RequestHandler(){} virtual ~RequestHandler(){} + CefRefPtr GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) override; + bool OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, @@ -23,19 +36,19 @@ class RequestHandler : public CefRequestHandler ReturnValue OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, - CefRefPtr callback - ) override; + CefRefPtr callback + ) ; CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, - CefRefPtr request) override; + CefRefPtr request) ; void OnResourceRedirect(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr response, - CefString& new_url) override; + CefString& new_url) ; bool GetAuthCredentials(CefRefPtr browser, CefRefPtr frame, @@ -44,37 +57,27 @@ class RequestHandler : public CefRequestHandler int port, const CefString& realm, const CefString& scheme, - CefRefPtr callback) override; + CefRefPtr callback) ; bool OnQuotaRequest(CefRefPtr browser, const CefString& origin_url, - int64 new_size, - CefRefPtr callback) override; + int64_t new_size, + CefRefPtr callback) ; void OnProtocolExecution(CefRefPtr browser, const CefString& url, - bool& allow_os_execution) override; + bool& allow_os_execution) ; bool OnCertificateError(CefRefPtr browser, cef_errorcode_t cert_error, const CefString& request_url, CefRefPtr ssl_info, - CefRefPtr callback) override; + CefRefPtr callback) override; void OnRenderProcessTerminated(CefRefPtr browser, - cef_termination_status_t status) override; - - void OnPluginCrashed(CefRefPtr browser, - const CefString& plugin_path) override; - - bool CanGetCookies(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr request) override; - - bool CanSetCookie(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr request, - const CefCookie& cookie) override; + cef_termination_status_t status, + int error_code, + const CefString& error_string) override; private: IMPLEMENT_REFCOUNTING(RequestHandler); diff --git a/src/client_handler/resource_handler.cpp b/src/client_handler/resource_handler.cpp index 963575928..08d2565a0 100644 --- a/src/client_handler/resource_handler.cpp +++ b/src/client_handler/resource_handler.cpp @@ -12,7 +12,7 @@ bool ResourceHandler::ProcessRequest(CefRefPtr request, } void ResourceHandler::GetResponseHeaders(CefRefPtr response, - int64& response_length, + int64_t& response_length, CefString& redirectUrl) { REQUIRE_IO_THREAD(); ResourceHandler_GetResponseHeaders(resourceHandlerId_, response, @@ -28,16 +28,6 @@ bool ResourceHandler::ReadResponse(void* data_out, bytes_to_read, bytes_read, callback); } -bool ResourceHandler::CanGetCookie(const CefCookie& cookie) { - REQUIRE_IO_THREAD(); - return ResourceHandler_CanGetCookie(resourceHandlerId_, cookie); -} - -bool ResourceHandler::CanSetCookie(const CefCookie& cookie) { - REQUIRE_IO_THREAD(); - return ResourceHandler_CanSetCookie(resourceHandlerId_, cookie); -} - void ResourceHandler::Cancel() { REQUIRE_IO_THREAD(); return ResourceHandler_Cancel(resourceHandlerId_); diff --git a/src/client_handler/resource_handler.h b/src/client_handler/resource_handler.h index 3bf5e41f6..fc81264ad 100644 --- a/src/client_handler/resource_handler.h +++ b/src/client_handler/resource_handler.h @@ -23,7 +23,7 @@ class ResourceHandler : public CefResourceHandler CefRefPtr callback) override; virtual void GetResponseHeaders(CefRefPtr response, - int64& response_length, + int64_t& response_length, CefString& redirectUrl) override; virtual bool ReadResponse(void* data_out, @@ -31,11 +31,7 @@ class ResourceHandler : public CefResourceHandler int& bytes_read, CefRefPtr callback) override; - virtual bool CanGetCookie(const CefCookie& cookie) override; - - virtual bool CanSetCookie(const CefCookie& cookie) override; - - virtual void Cancel() OVERRIDE; + virtual void Cancel() override; private: IMPLEMENT_REFCOUNTING(ResourceHandler); diff --git a/src/client_handler/resource_request_handler.h b/src/client_handler/resource_request_handler.h new file mode 100644 index 000000000..33eb44ca5 --- /dev/null +++ b/src/client_handler/resource_request_handler.h @@ -0,0 +1,26 @@ +// 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 "include/cef_resource_request_handler.h" +#include "cookie_access_filter.h" + +// Minimal CefResourceRequestHandler that returns a CookieAccessFilter. +// CEF 146 moved CanSendCookie/CanSaveCookie out of CefRequestHandler into +// CefCookieAccessFilter, reachable only via this intermediate interface. +class ResourceRequestHandler : public CefResourceRequestHandler { +public: + ResourceRequestHandler() {} + virtual ~ResourceRequestHandler() {} + + CefRefPtr GetCookieAccessFilter( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + return new CookieAccessFilter(); + } + +private: + IMPLEMENT_REFCOUNTING(ResourceRequestHandler); +}; diff --git a/src/client_handler/string_visitor.h b/src/client_handler/string_visitor.h index 3765442c5..c36cf40f6 100644 --- a/src/client_handler/string_visitor.h +++ b/src/client_handler/string_visitor.h @@ -21,7 +21,7 @@ class StringVisitor : public CefStringVisitor virtual void Visit( const CefString& string - ) OVERRIDE; + ) override; protected: IMPLEMENT_REFCOUNTING(StringVisitor); diff --git a/src/client_handler/task.cpp b/src/client_handler/task.cpp index ec032a2d6..62a9fcbff 100644 --- a/src/client_handler/task.cpp +++ b/src/client_handler/task.cpp @@ -5,21 +5,22 @@ #include "task.h" #include "include/wrapper/cef_closure_task.h" #include "include/base/cef_bind.h" +#include "include/base/cef_callback.h" void PostTaskWrapper(int threadId, int taskId) { CefPostTask( static_cast(threadId), - CefCreateClosureTask(base::Bind( + CefCreateClosureTask(base::BindOnce( &PyTaskRunnable, taskId )) ); } -void PostDelayedTaskWrapper(int threadId, int64 delay_ms, int taskId) { +void PostDelayedTaskWrapper(int threadId, int64_t delay_ms, int taskId) { CefPostDelayedTask( static_cast(threadId), - CefCreateClosureTask(base::Bind( + CefCreateClosureTask(base::BindOnce( &PyTaskRunnable, taskId )), @@ -33,7 +34,7 @@ CefRefPtr CreateTask_SetCookie( const CefCookie& cookie, CefRefPtr callback) { - return CefCreateClosureTask(base::Bind( + return CefCreateClosureTask(base::BindOnce( base::IgnoreResult(&CefCookieManager::SetCookie), obj, url, cookie, @@ -47,7 +48,7 @@ CefRefPtr CreateTask_DeleteCookies( const CefString& cookie_name, CefRefPtr callback) { - return CefCreateClosureTask(base::Bind( + return CefCreateClosureTask(base::BindOnce( base::IgnoreResult(&CefCookieManager::DeleteCookies), obj, url, cookie_name, diff --git a/src/client_handler/task.h b/src/client_handler/task.h index 1839d9e32..a7d8c5b38 100644 --- a/src/client_handler/task.h +++ b/src/client_handler/task.h @@ -9,7 +9,7 @@ #include "include/cef_task.h" void PostTaskWrapper(int threadId, int taskId); -void PostDelayedTaskWrapper(int threadId, int64 delay_ms, int taskId); +void PostDelayedTaskWrapper(int threadId, int64_t delay_ms, int taskId); CefRefPtr CreateTask_SetCookie( CefCookieManager* obj, diff --git a/src/client_handler/util_mac.mm b/src/client_handler/util_mac.mm index 4916b253a..7727105e0 100644 --- a/src/client_handler/util_mac.mm +++ b/src/client_handler/util_mac.mm @@ -77,6 +77,30 @@ void MacInitialize() { // OFF: it's causing a crash during shutdown release // g_autopool = [[NSAutoreleasePool alloc] init]; [NSApplication sharedApplication]; + + // CEF 130+ builds the MachPortRendezvousServer bootstrap service name as + // BaseBundleID() + ".MachPortRendezvousServer." + pid + // When the process has no app bundle (e.g. a bare Python script), + // BaseBundleID() returns "" and the name starts with ".", which + // bootstrap_register rejects as invalid. All renderer subprocesses then + // crash with "Unknown service name" (1102) on bootstrap_look_up. + // Inject a synthetic CFBundleIdentifier before CefInitialize() so the + // service name becomes a valid reverse-DNS label ("org.cefpython..."). + CFBundleRef mainBundle = CFBundleGetMainBundle(); + if (mainBundle) { + CFStringRef bundleID = CFBundleGetIdentifier(mainBundle); + if (!bundleID || CFStringGetLength(bundleID) == 0) { + // CFBundleGetInfoDictionary returns the bundle's internal mutable + // dictionary; casting to CFMutableDictionaryRef is safe here. + CFMutableDictionaryRef infoDict = + (CFMutableDictionaryRef)CFBundleGetInfoDictionary(mainBundle); + if (infoDict) { + CFDictionarySetValue(infoDict, + CFSTR("CFBundleIdentifier"), + CFSTR("org.cefpython")); + } + } + } } void MacShutdown() { @@ -85,7 +109,7 @@ void MacShutdown() { } void MacSetWindowTitle(CefRefPtr browser, char* title) { - NSView* view = browser->GetHost()->GetWindowHandle(); + NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(browser->GetHost()->GetWindowHandle()); NSString* nstitle = [NSString stringWithFormat:@"%s" , title]; view.window.title = nstitle; } diff --git a/src/client_handler/web_request_client.cpp b/src/client_handler/web_request_client.cpp index 965de919b..509a72eda 100644 --- a/src/client_handler/web_request_client.cpp +++ b/src/client_handler/web_request_client.cpp @@ -9,14 +9,14 @@ void WebRequestClient::OnRequestComplete(CefRefPtr request) { } void WebRequestClient::OnUploadProgress(CefRefPtr request, - int64 current, - int64 total) { + int64_t current, + int64_t total) { WebRequestClient_OnUploadProgress(webRequestId_, request, current, total); } void WebRequestClient::OnDownloadProgress(CefRefPtr request, - int64 current, - int64 total) { + int64_t current, + int64_t total) { WebRequestClient_OnDownloadProgress(webRequestId_, request, current, total); } diff --git a/src/client_handler/web_request_client.h b/src/client_handler/web_request_client.h index 8b815a968..8f5bce96a 100644 --- a/src/client_handler/web_request_client.h +++ b/src/client_handler/web_request_client.h @@ -20,26 +20,26 @@ class WebRequestClient : public CefURLRequestClient } virtual ~WebRequestClient(){} - virtual void OnRequestComplete(CefRefPtr request) OVERRIDE; + virtual void OnRequestComplete(CefRefPtr request) override; virtual void OnUploadProgress(CefRefPtr request, - int64 current, - int64 total) OVERRIDE; + int64_t current, + int64_t total) override; virtual void OnDownloadProgress(CefRefPtr request, - int64 current, - int64 total) OVERRIDE; + int64_t current, + int64_t total) override; virtual void OnDownloadData(CefRefPtr request, const void* data, - size_t data_length) OVERRIDE; + size_t data_length) override; virtual bool GetAuthCredentials(bool isProxy, const CefString& host, int port, const CefString& realm, const CefString& scheme, - CefRefPtr callback) OVERRIDE; + CefRefPtr callback) override; protected: IMPLEMENT_REFCOUNTING(WebRequestClient); diff --git a/src/client_handler/x11.cpp b/src/client_handler/x11.cpp index 2e97a6e91..4db68dd80 100644 --- a/src/client_handler/x11.cpp +++ b/src/client_handler/x11.cpp @@ -36,6 +36,7 @@ void SetX11WindowBounds(CefRefPtr browser, int x, int y, int width, int height) { ::Window xwindow = browser->GetHost()->GetWindowHandle(); ::Display* xdisplay = cef_get_xdisplay(); + if (!xdisplay || !xwindow) return; XWindowChanges changes = {0}; changes.x = x; changes.y = y; @@ -43,17 +44,27 @@ void SetX11WindowBounds(CefRefPtr browser, changes.height = static_cast(height); XConfigureWindow(xdisplay, xwindow, CWX | CWY | CWHeight | CWWidth, &changes); + XFlush(xdisplay); } void SetX11WindowTitle(CefRefPtr browser, char* title) { ::Window xwindow = browser->GetHost()->GetWindowHandle(); ::Display* xdisplay = cef_get_xdisplay(); + if (!xdisplay || !xwindow) return; XStoreName(xdisplay, xwindow, title); } GtkWindow* CefBrowser_GetGtkWindow(CefRefPtr browser) { // TODO: Should return NULL when using the Views framework // -- REWRITTEN FOR CEF PYTHON USE CASE -- + // + // WARNING (CEF 146 Ozone X11): gtk_plug_new_for_display() below sends an + // XEMBED_EMBEDDED_NOTIFY to the browser's X11 window, which causes GTK to + // call XReparentWindow and move the browser window into a new GtkSocket. + // This breaks embedded-window positioning. Only call this function when + // showing a transient dialog (file chooser, print dialog) where the browser + // window displacement is acceptable or the dialog is temporary. + // // X11 window handle ::Window xwindow = browser->GetHost()->GetWindowHandle(); // X11 display @@ -99,7 +110,7 @@ GtkWindow* CefBrowser_GetGtkWindow(CefRefPtr browser) { XImage* CefBrowser_GetImage(CefRefPtr browser) { ::Display* display = cef_get_xdisplay(); if (!display) { - LOG(ERROR) << "XOpenDisplay failed in CefBrowser_GetImage"; + LOG(ERROR) << "cef_get_xdisplay() returned NULL in CefBrowser_GetImage"; return NULL; } ::Window browser_window = browser->GetHost()->GetWindowHandle(); diff --git a/src/client_handler/x11.h b/src/client_handler/x11.h index d92ee41ed..9c922aaae 100644 --- a/src/client_handler/x11.h +++ b/src/client_handler/x11.h @@ -4,12 +4,17 @@ #pragma once +// CEF headers must come before X11/GTK headers: X11/Xlib.h defines 'Status' +// (typedef int Status) which conflicts with CefURLRequest::Status typedef. +// Pre-including cef_urlrequest.h ensures it is processed first. +#include "include/cef_urlrequest.h" +#include "include/cef_browser.h" + #include #include +#include #include -#include "include/cef_browser.h" - void InstallX11ErrorHandlers(); void SetX11WindowBounds(CefRefPtr browser, int x, int y, int width, int height); diff --git a/src/command_line.pyx b/src/command_line.pyx index dd26b61d3..1662fcc8d 100644 --- a/src/command_line.pyx +++ b/src/command_line.pyx @@ -12,10 +12,10 @@ cdef void AppendSwitchesToCommandLine( # 1. App_OnBeforeCommandLineProcessing_BrowserProcess() # 2. BrowserProcessHandler_OnRenderProcessThreadCreated() cdef PyCommandLine pyCommandLine = CreatePyCommandLine(cefCommandLine) - cdef py_string switch - cdef py_string value - for switch, value in switches.iteritems(): - if not isinstance(switch, basestring) or switch[0] == '-': + cdef object switch + cdef object value + for switch, value in switches.items(): + if not isinstance(switch, (str, bytes)) or switch[0] == '-': Debug("Invalid command line switch: %s" % switch) continue if value: @@ -39,27 +39,27 @@ cdef PyCommandLine CreatePyCommandLine( cdef class PyCommandLine: cdef CefRefPtr[CefCommandLine] cefCommandLine - cdef py_void AppendSwitch(self, py_string switch): + cdef py_void AppendSwitch(self, object switch): cdef CefString cefSwitch cefSwitch = PyToCefStringValue(switch) self.cefCommandLine.get().AppendSwitch(cefSwitch) - cdef py_void AppendSwitchWithValue(self, py_string switch, py_string value): + cdef py_void AppendSwitchWithValue(self, object switch, object value): cdef CefString cefSwitch cdef CefString cefValue cefSwitch = PyToCefStringValue(switch) cefValue = PyToCefStringValue(value) self.cefCommandLine.get().AppendSwitchWithValue(cefSwitch, cefValue) - cdef py_string GetCommandLineString(self): + cdef object GetCommandLineString(self): return CefToPyString(self.cefCommandLine.get().GetCommandLineString()) - cdef py_bool HasSwitch(self, py_string switch): + cdef py_bool HasSwitch(self, object switch): cdef CefString cefSwitch cefSwitch = PyToCefStringValue(switch) return self.cefCommandLine.get().HasSwitch(cefSwitch) - cdef py_string GetSwitchValue(self, py_string switch): + cdef object GetSwitchValue(self, object switch): cdef CefString cefValue cdef CefString cefSwitch cefSwitch = PyToCefStringValue(switch) diff --git a/src/common/cefpython_public_api.h b/src/common/cefpython_public_api.h index c796388e6..f46f340da 100644 --- a/src/common/cefpython_public_api.h +++ b/src/common/cefpython_public_api.h @@ -2,9 +2,8 @@ // All rights reserved. Licensed under BSD 3-clause license. // Project website: https://github.com/cztomczak/cefpython -/* This is a wrapper around including cefpython_fixed.h that is generated - by Cython. Functions marked with the 'public' keyword are exposed - to C through that header file. */ +/* Wrapper around the Cython-generated public API header. + Functions marked 'public' in cefpython.pyx are exposed to C++ through it. */ #ifndef CEFPYTHON_PUBLIC_API_H #define CEFPYTHON_PUBLIC_API_H @@ -13,19 +12,16 @@ #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 +// Python.h must be included first (avoids _POSIX_C_SOURCE redefinition on Linux) #include "Python.h" - -// Includes required by "cefpython_fixed.h". +// Includes required by the generated 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. +// cefpython_fixed.h uses DL_IMPORT/DL_EXPORT which were removed in Python 3. #ifndef DL_IMPORT #define DL_IMPORT(RTYPE) RTYPE #endif @@ -33,24 +29,9 @@ #define DL_EXPORT(RTYPE) RTYPE #endif -#if PY_MAJOR_VERSION == 2 -#if PY_MINOR_VERSION == 7 -#include "../../build/build_cefpython/cefpython_py27_fixed.h" -#endif // PY_MINOR_VERSION -#elif PY_MAJOR_VERSION == 3 -#if PY_MINOR_VERSION == 4 -#include "../../build/build_cefpython/cefpython_py34_fixed.h" -#elif PY_MINOR_VERSION == 5 -#include "../../build/build_cefpython/cefpython_py35_fixed.h" -#elif PY_MINOR_VERSION == 6 -#include "../../build/build_cefpython/cefpython_py36_fixed.h" -#elif PY_MINOR_VERSION == 7 -#include "../../build/build_cefpython/cefpython_py37_fixed.h" -#elif PY_MINOR_VERSION == 8 -#include "../../build/build_cefpython/cefpython_py38_fixed.h" -#elif PY_MINOR_VERSION == 9 -#include "../../build/build_cefpython/cefpython_py39_fixed.h" -#endif // PY_MINOR_VERSION -#endif // PY_MAJOR_VERSION +// CMake sets CEFPYTHON_API_H_FILE to "cefpython_api_fixed.h", a generated +// stable-name wrapper in the pyx_stage/ build directory resolved via the +// target's include path. +#include CEFPYTHON_API_H_FILE #endif // CEFPYTHON_PUBLIC_API_H diff --git a/src/compile_time_constants.pxi b/src/compile_time_constants.pxi index bf130d6ed..45e11d1f3 100644 --- a/src/compile_time_constants.pxi +++ b/src/compile_time_constants.pxi @@ -1,10 +1,3 @@ -# This file was generated by setup.py - -# Type this command to ignore changes to this file: -# git update-index --assume-unchanged src/compile_time_constants.pxi - -DEF UNAME_SYSNAME = "Windows" -DEF PY_MAJOR_VERSION = 3 -cdef extern from "limits.h": - cdef int INT_MIN - cdef int INT_MAX +# UNAME_SYSNAME and PY_MAJOR_VERSION are Cython built-in compile-time +# constants; no DEF needed. INT_MIN/INT_MAX come from libc.limits. +from libc.limits cimport INT_MIN, INT_MAX diff --git a/src/cookie.pyx b/src/cookie.pyx index 6d90144b0..ba14d5bf4 100644 --- a/src/cookie.pyx +++ b/src/cookie.pyx @@ -67,6 +67,10 @@ cdef class Cookie: self.SetHasExpires(cookie[key]) elif key == "expires": self.SetExpires(cookie[key]) + elif key == "sameSite": + self.SetSameSite(cookie[key]) + elif key == "priority": + self.SetPriority(cookie[key]) else: raise Exception("Invalid key: %s" % key) @@ -82,9 +86,11 @@ cdef class Cookie: "lastAccess": self.GetLastAccess(), "hasExpires": self.GetHasExpires(), "expires": self.GetExpires(), + "sameSite": self.GetSameSite(), + "priority": self.GetPriority(), } - cpdef py_void SetName(self, py_string name): + cpdef py_void SetName(self, object name): # This works: # | CefString(&self.cefCookie.name).FromString(name) # This does not work: @@ -108,7 +114,7 @@ cdef class Cookie: cefString.Attach(&self.cefCookie.name, False) return CefToPyString(cefString) - cpdef py_void SetValue(self, py_string value): + cpdef py_void SetValue(self, object value): cdef CefString cefString cefString.Attach(&self.cefCookie.value, False) PyToCefString(value, cefString) @@ -118,20 +124,7 @@ cdef class Cookie: cefString.Attach(&self.cefCookie.value, False) return CefToPyString(cefString) - cpdef py_void SetDomain(self, py_string domain): - pattern = re.compile(r"^(?:[a-z0-9](?:[a-z0-9-_]{0,61}[a-z0-9])?\.)" - r"+[a-z0-9][a-z0-9-_]{0,61}[a-z]$") - if PY_MAJOR_VERSION == 2: - assert isinstance(domain, bytes), "domain type is not bytes" - domain = domain.decode(g_applicationSettings["string_encoding"], - errors=BYTES_DECODE_ERRORS) - try: - if not pattern.match(domain.encode("idna").decode("ascii")): - raise Exception("Cookie.SetDomain() failed, invalid domain: {0}" - .format(domain)) - except UnicodeError: - raise Exception("Cookie.SetDomain() failed, invalid domain: {0}" - .format(domain)) + cpdef py_void SetDomain(self, object domain): cdef CefString cefString cefString.Attach(&self.cefCookie.domain, False) PyToCefString(domain, cefString) @@ -141,7 +134,7 @@ cdef class Cookie: cefString.Attach(&self.cefCookie.domain, False) return CefToPyString(cefString) - cpdef py_void SetPath(self, py_string path): + cpdef py_void SetPath(self, object path): cdef CefString cefString cefString.Attach(&self.cefCookie.path, False) PyToCefString(path, cefString) @@ -152,43 +145,52 @@ cdef class Cookie: return CefToPyString(cefString) cpdef py_void SetSecure(self, py_bool secure): - # Need to wrap it with bool() to get rid of the C++ compiler - # warnings: "cefpython.cpp(24740) : warning C4800: 'int' : - # forcing value to bool 'true' or 'false' (performance warning)". - self.cefCookie.secure = bool(secure) + self.cefCookie.secure = int(bool(secure)) cpdef py_bool GetSecure(self): - return self.cefCookie.secure + return bool(self.cefCookie.secure) cpdef py_void SetHttpOnly(self, py_bool httpOnly): - self.cefCookie.httponly = bool(httpOnly) + self.cefCookie.httponly = int(bool(httpOnly)) cpdef py_bool GetHttpOnly(self): - return self.cefCookie.httponly + return bool(self.cefCookie.httponly) cpdef py_void SetCreation(self, object creation): - DatetimeToCefTimeT(creation, self.cefCookie.creation) + DatetimeToCefBasetimeT(creation, self.cefCookie.creation) cpdef object GetCreation(self): - return CefTimeTToDatetime(self.cefCookie.creation) + return CefBasetimeTToDatetime(self.cefCookie.creation) cpdef py_void SetLastAccess(self, object lastAccess): - DatetimeToCefTimeT(lastAccess, self.cefCookie.last_access) + DatetimeToCefBasetimeT(lastAccess, self.cefCookie.last_access) cpdef object GetLastAccess(self): - return CefTimeTToDatetime(self.cefCookie.last_access) + return CefBasetimeTToDatetime(self.cefCookie.last_access) cpdef py_void SetHasExpires(self, py_bool hasExpires): - self.cefCookie.has_expires = bool(hasExpires) + self.cefCookie.has_expires = int(bool(hasExpires)) cpdef py_bool GetHasExpires(self): - return self.cefCookie.has_expires + return bool(self.cefCookie.has_expires) cpdef py_void SetExpires(self, object expires): - DatetimeToCefTimeT(expires, self.cefCookie.expires) + DatetimeToCefBasetimeT(expires, self.cefCookie.expires) cpdef object GetExpires(self): - return CefTimeTToDatetime(self.cefCookie.expires) + return CefBasetimeTToDatetime(self.cefCookie.expires) + + cpdef int GetSameSite(self) except *: + return self.cefCookie.same_site + + cpdef py_void SetSameSite(self, int sameSite): + self.cefCookie.same_site = sameSite + + cpdef int GetPriority(self) except *: + return self.cefCookie.priority + + cpdef py_void SetPriority(self, int priority): + self.cefCookie.priority = priority # ------------------------------------------------------------------------------ # CookieManager @@ -204,37 +206,10 @@ class CookieManager(object): cdef CefRefPtr[CefCookieManager] cefCookieManager if not g_globalCookieManager: cefCookieManager = CefCookieManager_GetGlobalManager( - NULL) + nullptr) g_globalCookieManager = CreatePyCookieManager(cefCookieManager) return g_globalCookieManager - @classmethod - def GetBlockingManager(cls): - return CreatePyCookieManager(CefCookieManager_GetBlockingManager()) - - @classmethod - def CreateManager(cls, py_string path, - py_bool persist_session_cookies=False): - """ - Create a new cookie manager. - :param path: - :type path: str - :param persist_session_cookies: - :type path: bool - :return: CookieManager object - :rtype: CookieManager - """ - # When PyCharm generates a stub for the cefpython module - # it doesn't use the above docstring for code inspections. - # No idea why. - cdef CefRefPtr[CefCookieManager] cefCookieManager - cefCookieManager = CefCookieManager_CreateManager( - PyToCefStringValue(path), bool(persist_session_cookies), - NULL) - if cefCookieManager != NULL and cefCookieManager.get(): - return CreatePyCookieManager(cefCookieManager) - return None - # ------------------------------------------------------------------------------ # PyCookieManager # ------------------------------------------------------------------------------ @@ -248,13 +223,6 @@ cdef PyCookieManager CreatePyCookieManager( cdef class PyCookieManager: cdef CefRefPtr[CefCookieManager] cefCookieManager - cpdef py_void SetSupportedSchemes(self, list schemes): - cdef cpp_vector[CefString] schemesVector - for scheme in schemes: - schemesVector.push_back(PyToCefStringValue(scheme)) - self.cefCookieManager.get().SetSupportedSchemes(schemesVector, - NULL) - cdef py_void ValidateUserCookieVisitor(self, object userCookieVisitor): if userCookieVisitor and hasattr(userCookieVisitor, "Visit") and ( callable(getattr(userCookieVisitor, "Visit"))): @@ -271,7 +239,7 @@ cdef class PyCookieManager: return self.cefCookieManager.get().VisitAllCookies( cefCookieVisitor) - cpdef py_bool VisitUrlCookies(self, py_string url, + cpdef py_bool VisitUrlCookies(self, object url, py_bool includeHttpOnly, object userCookieVisitor): self.ValidateUserCookieVisitor(userCookieVisitor) cdef int cookieVisitorId = StoreUserCookieVisitor(userCookieVisitor) @@ -282,28 +250,22 @@ cdef class PyCookieManager: PyToCefStringValue(url), bool(includeHttpOnly), cefCookieVisitor) - cpdef py_void SetCookie(self, py_string url, PyCookie cookie): + cpdef py_void SetCookie(self, object url, PyCookie cookie): assert isinstance(cookie, Cookie), "cookie object is invalid" CefPostTask(TID_IO, CreateTask_SetCookie( self.cefCookieManager.get(), PyToCefStringValue(url), cookie.cefCookie, - NULL)) + nullptr)) - cpdef py_void DeleteCookies(self, py_string url, py_string cookie_name): + cpdef py_void DeleteCookies(self, object url, object cookie_name): CefPostTask(TID_IO, CreateTask_DeleteCookies( self.cefCookieManager.get(), PyToCefStringValue(url), PyToCefStringValue(cookie_name), - NULL)) - - cpdef py_bool SetStoragePath(self, py_string path, - py_bool persistSessionCookies=False): - return self.cefCookieManager.get().SetStoragePath( - PyToCefStringValue(path), bool(persistSessionCookies), - NULL) + nullptr)) cpdef py_bool FlushStore(self, callback=None): return self.cefCookieManager.get().FlushStore( - NULL) + nullptr) # ------------------------------------------------------------------------------ @@ -355,7 +317,7 @@ cdef public cpp_bool CookieVisitor_Visit( cdef PyCookie pyCookie cdef list pyDeleteCookie = [False] try: - assert IsThread(TID_IO), "Must be called on the IO thread" + assert IsThread(TID_UI), "Must be called on the UI thread" pyCookieVisitor = GetPyCookieVisitor(cookieVisitorId) pyCookie = CreatePyCookie(cookie) if pyCookieVisitor: diff --git a/src/cpp_utils/CMakeLists.txt b/src/cpp_utils/CMakeLists.txt new file mode 100644 index 000000000..a72e2519e --- /dev/null +++ b/src/cpp_utils/CMakeLists.txt @@ -0,0 +1,30 @@ +file(GLOB _sources "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") + +add_library(cpp_utils STATIC ${_sources}) +add_dependencies(cpp_utils cefpython_headers) + +target_include_directories(cpp_utils PRIVATE + "${CEFPYTHON_SRC_DIR}" + "${CEFPYTHON_SRC_DIR}/common" + "${CEFPYTHON_PYX_STAGE_DIR}" + ${Python_INCLUDE_DIRS} +) + +if(WIN32) + target_compile_options(cpp_utils PRIVATE /EHsc /wd4190) + target_compile_definitions(cpp_utils PRIVATE + WIN32 _WIN32 _WINDOWS + NTDDI_VERSION=0x06010000 WINVER=0x0601 _WIN32_WINNT=0x0601 + NDEBUG _NDEBUG _CRT_SECURE_NO_WARNINGS NOMINMAX + BROWSER_PROCESS + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h" + ) +elseif(APPLE) + target_compile_options(cpp_utils PRIVATE -DNDEBUG -O3) + target_compile_definitions(cpp_utils PRIVATE + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h") +else() + target_compile_options(cpp_utils PRIVATE -DNDEBUG -O3 -Wno-stringop-overflow) + target_compile_definitions(cpp_utils PRIVATE + CEFPYTHON_API_H_FILE="cefpython_api_fixed.h") +endif() diff --git a/src/dpi_aware_win.pyx b/src/dpi_aware_win.pyx index a7fc345fa..5a458ff6e 100644 --- a/src/dpi_aware_win.pyx +++ b/src/dpi_aware_win.pyx @@ -41,7 +41,7 @@ class DpiAware: # modern displays have equal horizontal and vertical resolution. default_dpix = 96 scale = MulDiv(dpix, 100, default_dpix) - if isinstance(arg, (int, long)): + if isinstance(arg, int): v = arg new_value = MulDiv(v, scale, 100) return new_value @@ -63,8 +63,3 @@ class DpiAware: """Deprecated.""" DpiAware.EnableHighDpiSupport() - @classmethod - def EnableHighDpiSupport(cls): - # This CEF function sets process to be DPI aware. This - # CEF func is also called in subprocesses. - CefEnableHighDPISupport() diff --git a/src/drag_data.pyx b/src/drag_data.pyx index 5da99a6f2..628272fc2 100644 --- a/src/drag_data.pyx +++ b/src/drag_data.pyx @@ -24,16 +24,16 @@ cdef class DragData: cpdef py_bool IsFragment(self): return self.cef_drag_data.get().IsFragment() - cpdef py_string GetLinkUrl(self): + cpdef object GetLinkUrl(self): return CefToPyString(self.cef_drag_data.get().GetLinkURL()) - cpdef py_string GetLinkTitle(self): + cpdef object GetLinkTitle(self): return CefToPyString(self.cef_drag_data.get().GetLinkTitle()) - cpdef py_string GetFragmentText(self): + cpdef object GetFragmentText(self): return CefToPyString(self.cef_drag_data.get().GetFragmentText()) - cpdef py_string GetFragmentHtml(self): + cpdef object GetFragmentHtml(self): return CefToPyString(self.cef_drag_data.get().GetFragmentHtml()) cpdef PyImage GetImage(self): diff --git a/src/extern/cef/cef_app.pxd b/src/extern/cef/cef_app.pxd index ef270f1fc..f824d4ca3 100644 --- a/src/extern/cef/cef_app.pxd +++ b/src/extern/cef/cef_app.pxd @@ -5,26 +5,17 @@ # Circular imports are allowed in form "cimport ...", # but won't work if you do "from ... cimport *". -include "compile_time_constants.pxi" +include "platform_cimports.pxi" from cef_types cimport CefSettings from cef_ptr cimport CefRefPtr from libcpp cimport bool as cpp_bool -IF UNAME_SYSNAME == "Windows": - from cef_win cimport CefMainArgs -ELIF UNAME_SYSNAME == "Linux": - from cef_linux cimport CefMainArgs -ELIF UNAME_SYSNAME == "Darwin": - from cef_mac cimport CefMainArgs - cdef extern from "include/cef_app.h": cdef cppclass CefApp: pass - cdef void CefEnableHighDPISupport() nogil - cdef int CefExecuteProcess(CefMainArgs& args, CefRefPtr[CefApp] application, void* windows_sandbox_info @@ -39,4 +30,7 @@ cdef extern from "include/cef_app.h": cdef void CefDoMessageLoopWork() nogil cdef void CefQuitMessageLoop() nogil cdef void CefShutdown() nogil - cdef void CefSetOSModalLoop(cpp_bool osModalLoop) nogil + +IF UNAME_SYSNAME == "Windows": + cdef extern from "include/internal/cef_win.h": + cdef void CefSetOSModalLoop(cpp_bool osModalLoop) nogil diff --git a/src/extern/cef/cef_browser.pxd b/src/extern/cef/cef_browser.pxd index da209d5c3..80ad8c533 100644 --- a/src/extern/cef/cef_browser.pxd +++ b/src/extern/cef/cef_browser.pxd @@ -2,16 +2,17 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -include "compile_time_constants.pxi" +include "platform_cimports.pxi" from cef_ptr cimport CefRefPtr from cef_string cimport CefString from cef_client cimport CefClient from libcpp cimport bool as cpp_bool from libcpp.vector cimport vector as cpp_vector +from libc.stdint cimport int64_t from cef_frame cimport CefFrame cimport cef_types -from cef_types cimport int64, cef_state_t, CefSize +from cef_types cimport cef_state_t, CefSize from cef_types cimport CefBrowserSettings, CefPoint from cef_drag_data cimport CefDragData from cef_types cimport CefMouseEvent @@ -19,13 +20,6 @@ from cef_request_context cimport CefRequestContext from cef_process_message cimport CefProcessMessage, CefProcessId -IF UNAME_SYSNAME == "Windows": - from cef_win cimport CefWindowHandle, CefWindowInfo -ELIF UNAME_SYSNAME == "Linux": - from cef_linux cimport CefWindowHandle, CefWindowInfo -ELIF UNAME_SYSNAME == "Darwin": - from cef_mac cimport CefWindowHandle, CefWindowInfo - cdef extern from "include/cef_browser.h": cdef cppclass CefBrowserHost: @@ -38,8 +32,6 @@ cdef extern from "include/cef_browser.h": double GetZoomLevel() void SetZoomLevel(double zoomLevel) void StartDownload(const CefString& url) - void SetMouseCursorChangeDisabled(cpp_bool disabled) - cpp_bool IsMouseCursorChangeDisabled() cpp_bool IsWindowRenderingDisabled() void WasResized() void WasHidden(cpp_bool hidden) @@ -54,7 +46,6 @@ cdef extern from "include/cef_browser.h": cpp_bool mouseLeave) void SendMouseWheelEvent(cef_types.CefMouseEvent, int deltaX, int deltaY) - void SendFocusEvent(cpp_bool setFocus) void SendCaptureLostEvent() void ShowDevTools(const CefWindowInfo& windowInfo, @@ -66,7 +57,7 @@ cdef extern from "include/cef_browser.h": CefRefPtr[CefRequestContext] GetRequestContext() - void Find(int identifier, const CefString& searchText, cpp_bool forward, + void Find(const CefString& searchText, cpp_bool forward, cpp_bool matchCase, cpp_bool findNext) void StopFinding(cpp_bool clearSelection) void Print() @@ -100,8 +91,8 @@ cdef extern from "include/cef_browser.h": cpp_bool CanGoBack() cpp_bool CanGoForward() CefRefPtr[CefFrame] GetFocusedFrame() - CefRefPtr[CefFrame] GetFrame(CefString& name) - CefRefPtr[CefFrame] GetFrame(int64 identifier) + CefRefPtr[CefFrame] GetFrameByName(CefString& name) + CefRefPtr[CefFrame] GetFrameByIdentifier(CefString& identifier) void GetFrameNames(cpp_vector[CefString]& names) CefRefPtr[CefFrame] GetMainFrame() void GoBack() @@ -113,5 +104,3 @@ cdef extern from "include/cef_browser.h": void StopLoad() cpp_bool IsLoading() int GetIdentifier() - cpp_bool SendProcessMessage(CefProcessId target_process, - CefRefPtr[CefProcessMessage] message) diff --git a/src/extern/cef/cef_browser_static.pxd b/src/extern/cef/cef_browser_static.pxd index 17b1a283d..111fcb6c0 100644 --- a/src/extern/cef/cef_browser_static.pxd +++ b/src/extern/cef/cef_browser_static.pxd @@ -2,18 +2,14 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -include "compile_time_constants.pxi" +include "platform_cimports.pxi" +from libcpp cimport bool as cpp_bool from cef_ptr cimport CefRefPtr -IF UNAME_SYSNAME == "Windows": - from cef_win cimport CefWindowInfo -ELIF UNAME_SYSNAME == "Linux": - from cef_linux cimport CefWindowInfo -ELIF UNAME_SYSNAME == "Darwin": - from cef_mac cimport CefWindowInfo # noinspection PyUnresolvedReferences from cef_client cimport CefClient from cef_types cimport CefBrowserSettings +from cef_values cimport CefDictionaryValue # noinspection PyUnresolvedReferences from cef_request_context cimport CefRequestContext # noinspection PyUnresolvedReferences @@ -23,9 +19,18 @@ from cef_string cimport CefString # Specifying namespace allows to import a static method. cdef extern from "include/cef_browser.h" namespace "CefBrowserHost": + cdef cpp_bool CreateBrowser( + CefWindowInfo&, + CefRefPtr[CefClient], + CefString&, + CefBrowserSettings&, + CefRefPtr[CefDictionaryValue], + CefRefPtr[CefRequestContext]) nogil + cdef CefRefPtr[CefBrowser] CreateBrowserSync( CefWindowInfo&, CefRefPtr[CefClient], CefString&, CefBrowserSettings&, + CefRefPtr[CefDictionaryValue], CefRefPtr[CefRequestContext]) nogil diff --git a/src/extern/cef/cef_command_line.pxd b/src/extern/cef/cef_command_line.pxd index 7fd9b3c23..7bf1648c4 100644 --- a/src/extern/cef/cef_command_line.pxd +++ b/src/extern/cef/cef_command_line.pxd @@ -9,6 +9,8 @@ include "compile_time_constants.pxi" from cef_string cimport CefString from libcpp cimport bool as cpp_bool +from libcpp.vector cimport vector as cpp_vector +from libcpp.map cimport map as cpp_map cdef extern from "include/cef_command_line.h": cdef cppclass CefCommandLine: @@ -17,3 +19,9 @@ cdef extern from "include/cef_command_line.h": CefString GetCommandLineString() cpp_bool HasSwitch(const CefString& name) CefString GetSwitchValue(const CefString& name) + void Reset() + CefString GetProgram() + void SetProgram(const CefString& program) + void GetSwitches(cpp_map[CefString, CefString]& switches) + void GetArguments(cpp_vector[CefString]& arguments) + void AppendArgument(const CefString& argument) diff --git a/src/extern/cef/cef_cookie.pxd b/src/extern/cef/cef_cookie.pxd index c113aea9a..cc5200670 100644 --- a/src/extern/cef/cef_cookie.pxd +++ b/src/extern/cef/cef_cookie.pxd @@ -2,6 +2,7 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython +from libc.stddef cimport size_t from cef_string cimport cef_string_t from libcpp cimport bool as cpp_bool from cef_time cimport cef_time_t @@ -10,37 +11,43 @@ from cef_string cimport CefString from cef_ptr cimport CefRefPtr # noinspection PyUnresolvedReferences from cef_callback cimport CefCompletionCallback +from cef_time cimport cef_basetime_t + +cdef extern from "include/internal/cef_types.h": + ctypedef enum cef_cookie_priority_t: + CEF_COOKIE_PRIORITY_LOW = -1 + CEF_COOKIE_PRIORITY_MEDIUM = 0 + CEF_COOKIE_PRIORITY_HIGH = 1 + + ctypedef enum cef_cookie_same_site_t: + CEF_COOKIE_SAME_SITE_UNSPECIFIED + CEF_COOKIE_SAME_SITE_NO_RESTRICTION + CEF_COOKIE_SAME_SITE_LAX_MODE + CEF_COOKIE_SAME_SITE_STRICT_MODE + CEF_COOKIE_SAME_SITE_NUM_VALUES -cdef extern from "include/cef_cookie.h": ctypedef struct CefCookie: + size_t size cef_string_t name cef_string_t value cef_string_t domain cef_string_t path - cpp_bool secure - cpp_bool httponly - cef_time_t creation - cef_time_t last_access - cpp_bool has_expires - cef_time_t expires + int secure + int httponly + cef_basetime_t creation + cef_basetime_t last_access + int has_expires + cef_basetime_t expires + cef_cookie_same_site_t same_site + cef_cookie_priority_t priority + +cdef extern from "include/cef_cookie.h": cdef CefRefPtr[CefCookieManager] CefCookieManager_GetGlobalManager \ "CefCookieManager::GetGlobalManager"( CefRefPtr[CefCompletionCallback] callback) - cdef CefRefPtr[CefCookieManager] CefCookieManager_GetBlockingManager \ - "CefCookieManager::GetBlockingManager"() - - - cdef CefRefPtr[CefCookieManager] CefCookieManager_CreateManager \ - "CefCookieManager::CreateManager"( - const CefString& path, - cpp_bool persist_session_cookies, - CefRefPtr[CefCompletionCallback] callback) - cdef cppclass CefCookieManager: - void SetSupportedSchemes(const cpp_vector[CefString]& schemes, - CefRefPtr[CefCompletionCallback] callback) cpp_bool VisitAllCookies(CefRefPtr[CefCookieVisitor] visitor) cpp_bool VisitUrlCookies(const CefString& url, cpp_bool includeHttpOnly, @@ -50,9 +57,6 @@ cdef extern from "include/cef_cookie.h": cpp_bool DeleteCookies(const CefString& url, const CefString& cookie_name, CefRefPtr[CefDeleteCookiesCallback] callback) - cpp_bool SetStoragePath(const CefString& path, - cpp_bool persist_session_cookies, - CefRefPtr[CefCompletionCallback] callback) cpp_bool FlushStore(CefRefPtr[CefCompletionCallback] callback) cdef cppclass CefCookieVisitor: diff --git a/src/extern/cef/cef_frame.pxd b/src/extern/cef/cef_frame.pxd index 28cdceee2..54efdd373 100644 --- a/src/extern/cef/cef_frame.pxd +++ b/src/extern/cef/cef_frame.pxd @@ -4,12 +4,12 @@ include "compile_time_constants.pxi" -from cef_types cimport int64 from cef_string cimport CefString from libcpp cimport bool as cpp_bool from cef_ptr cimport CefRefPtr from cef_browser cimport CefBrowser from cef_string_visitor cimport CefStringVisitor +from cef_process_message cimport CefProcessMessage, CefProcessId cdef extern from "include/cef_frame.h": @@ -17,7 +17,7 @@ cdef extern from "include/cef_frame.h": cpp_bool IsValid() void ExecuteJavaScript(CefString& jsCode, CefString& scriptUrl, int startLine) CefString GetURL() - int64 GetIdentifier() + CefString GetIdentifier() cpp_bool IsMain() void LoadURL(CefString& url) void Undo() @@ -35,3 +35,5 @@ cdef extern from "include/cef_frame.h": CefString GetName() CefRefPtr[CefFrame] GetParent() CefRefPtr[CefBrowser] GetBrowser() + cpp_bool SendProcessMessage(CefProcessId target_process, + CefRefPtr[CefProcessMessage] message) diff --git a/src/extern/cef/cef_linux.pxd b/src/extern/cef/cef_linux.pxd index 4ae54bf94..4455c5f2d 100644 --- a/src/extern/cef/cef_linux.pxd +++ b/src/extern/cef/cef_linux.pxd @@ -12,7 +12,13 @@ cdef extern from "include/internal/cef_linux.h": ctypedef unsigned long CefWindowHandle ctypedef unsigned long CefCursorHandle + ctypedef enum cef_runtime_style_t: + CEF_RUNTIME_STYLE_DEFAULT + CEF_RUNTIME_STYLE_CHROME + CEF_RUNTIME_STYLE_ALLOY + cdef cppclass CefWindowInfo: + cef_runtime_style_t runtime_style void SetAsChild(CefWindowHandle parent, const CefRect& windowRect) void SetAsWindowless(CefWindowHandle parent) diff --git a/src/extern/cef/cef_mac.pxd b/src/extern/cef/cef_mac.pxd index 5bc92f49f..945846318 100644 --- a/src/extern/cef/cef_mac.pxd +++ b/src/extern/cef/cef_mac.pxd @@ -5,15 +5,22 @@ include "compile_time_constants.pxi" from libcpp cimport bool as cpp_bool +from cef_types cimport CefRect cdef extern from "include/internal/cef_mac.h": ctypedef void* CefWindowHandle ctypedef void* CefCursorHandle + ctypedef enum cef_runtime_style_t: + CEF_RUNTIME_STYLE_DEFAULT + CEF_RUNTIME_STYLE_CHROME + CEF_RUNTIME_STYLE_ALLOY + cdef cppclass CefWindowInfo: + cef_runtime_style_t runtime_style void SetAsChild(CefWindowHandle parent, - int x, int y, int width, int height) + const CefRect& windowRect) void SetAsWindowless(CefWindowHandle parent) cdef cppclass CefMainArgs: diff --git a/src/extern/cef/cef_path_util.pxd b/src/extern/cef/cef_path_util.pxd index d90c08496..04c34aaa2 100644 --- a/src/extern/cef/cef_path_util.pxd +++ b/src/extern/cef/cef_path_util.pxd @@ -8,4 +8,3 @@ from libcpp cimport bool as cpp_bool cdef extern from "include/cef_path_util.h" nogil: cpp_bool CefGetPath(PathKey key, CefString& path) - cpp_bool CefOverridePath(PathKey key, const CefString& path) diff --git a/src/extern/cef/cef_platform.pxd b/src/extern/cef/cef_platform.pxd index bf428e5f5..ca085aa0e 100644 --- a/src/extern/cef/cef_platform.pxd +++ b/src/extern/cef/cef_platform.pxd @@ -2,12 +2,4 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -include "compile_time_constants.pxi" - -IF UNAME_SYSNAME == "Windows": - # noinspection PyUnresolvedReferences - from cef_win cimport * -ELIF UNAME_SYSNAME == "Darwin": - from cef_mac cimport * -ELIF UNAME_SYSNAME == "Linux": - from cef_linux cimport * +include "platform_cimports.pxi" diff --git a/src/extern/cef/cef_ptr.pxd b/src/extern/cef/cef_ptr.pxd index c5d9877cf..31bf22e44 100644 --- a/src/extern/cef/cef_ptr.pxd +++ b/src/extern/cef/cef_ptr.pxd @@ -1,6 +1,7 @@ # 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 +from libcpp cimport nullptr_t, bool cdef extern from "include/internal/cef_ptr.h": cdef cppclass CefRefPtr[T]: @@ -14,4 +15,7 @@ cdef extern from "include/internal/cef_ptr.h": # noinspection PyUnresolvedReferences void swap(CefRefPtr[T]& r) # noinspection PyUnresolvedReferences + CefRefPtr[T]& Assign "operator="(nullptr_t) CefRefPtr[T]& Assign "operator="(T* p) + bool operator bool() + bool operator!() diff --git a/src/extern/cef/cef_request.pxd b/src/extern/cef/cef_request.pxd index e7bd336d0..0ec425ae3 100644 --- a/src/extern/cef/cef_request.pxd +++ b/src/extern/cef/cef_request.pxd @@ -53,6 +53,7 @@ cdef extern from "include/cef_request.h": # noinspection PyUnresolvedReferences ctypedef cef_postdataelement_type_t ElementType + @staticmethod cdef CefRefPtr[CefPostDataElement] CefPostDataElement_Create \ "CefPostDataElement::Create"() cdef cppclass CefPostDataElement: diff --git a/src/extern/cef/cef_request_context.pxd b/src/extern/cef/cef_request_context.pxd index 116741749..1107cb6fc 100644 --- a/src/extern/cef/cef_request_context.pxd +++ b/src/extern/cef/cef_request_context.pxd @@ -16,5 +16,5 @@ cdef extern from "include/cef_request_context.h": CefRefPtr[CefRequestContext] CreateContext( CefRefPtr[CefRequestContext] other, CefRefPtr[CefRequestContextHandler] handler) - CefRefPtr[CefCookieManager] GetDefaultCookieManager( + CefRefPtr[CefCookieManager] GetCookieManager( CefRefPtr[CefCompletionCallback] callback) diff --git a/src/extern/cef/cef_request_handler.pxd b/src/extern/cef/cef_request_handler.pxd index 96a7d88c6..a6e320488 100644 --- a/src/extern/cef/cef_request_handler.pxd +++ b/src/extern/cef/cef_request_handler.pxd @@ -11,7 +11,3 @@ cdef extern from "include/cef_auth_callback.h": const CefString& password) void Cancel() -cdef extern from "include/cef_request_handler.h": - cdef cppclass CefRequestCallback: - void Continue(cpp_bool allow) - void Cancel() diff --git a/src/extern/cef/cef_resource_request_handler.pxd b/src/extern/cef/cef_resource_request_handler.pxd new file mode 100644 index 000000000..1087d55bf --- /dev/null +++ b/src/extern/cef/cef_resource_request_handler.pxd @@ -0,0 +1,9 @@ +# Copyright (c) 2013 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 "include/cef_resource_request_handler.h": + cdef cppclass CefResourceRequestHandler: + pass + cdef cppclass CefCookieAccessFilter: + pass \ No newline at end of file diff --git a/src/extern/cef/cef_response.pxd b/src/extern/cef/cef_response.pxd index ef6729f12..9c08a0ec1 100644 --- a/src/extern/cef/cef_response.pxd +++ b/src/extern/cef/cef_response.pxd @@ -21,6 +21,6 @@ cdef extern from "include/cef_response.h": void SetStatusText(CefString& statusText) CefString GetMimeType() void SetMimeType(CefString& mimeType) - CefString GetHeader(CefString& name) + CefString GetHeaderByName(CefString& name) void GetHeaderMap(CefResponseHeaderMap& headerMap) void SetHeaderMap(CefResponseHeaderMap& headerMap) diff --git a/src/extern/cef/cef_scoped_ptr.pxd b/src/extern/cef/cef_scoped_ptr.pxd deleted file mode 100644 index 2e770a5b9..000000000 --- a/src/extern/cef/cef_scoped_ptr.pxd +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2016 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 "include/base/cef_scoped_ptr.h": - cdef cppclass scoped_ptr[T]: - scoped_ptr() - # noinspection PyUnresolvedReferences - scoped_ptr(T* p) - # noinspection PyUnresolvedReferences - void reset() - # noinspection PyUnresolvedReferences - void reset(T* p) - # noinspection PyUnresolvedReferences - T* get() - # noinspection PyUnresolvedReferences - scoped_ptr[T]& Assign "operator="(scoped_ptr[T] p) diff --git a/src/extern/cef/cef_task.pxd b/src/extern/cef/cef_task.pxd index fbc0181e0..c7dfcdf02 100644 --- a/src/extern/cef/cef_task.pxd +++ b/src/extern/cef/cef_task.pxd @@ -3,9 +3,9 @@ # Project website: https://github.com/cztomczak/cefpython from libcpp cimport bool as cpp_bool +from libc.stdint cimport int64_t # noinspection PyUnresolvedReferences cimport cef_types -from cef_types cimport int64 from cef_ptr cimport CefRefPtr cdef extern from "include/cef_task.h": @@ -16,7 +16,7 @@ cdef extern from "include/cef_task.h": CefRefPtr[CefTask] task) cdef cpp_bool CefPostDelayedTask(CefThreadId threadId, CefRefPtr[CefTask] task, - int64 delay_ms) + int64_t delay_ms) cdef cppclass CefTask: pass diff --git a/src/extern/cef/cef_time.pxd b/src/extern/cef/cef_time.pxd index cdbdc639f..55efa42da 100644 --- a/src/extern/cef/cef_time.pxd +++ b/src/extern/cef/cef_time.pxd @@ -3,6 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython from ctime cimport time_t +from libc.stdint cimport int64_t cdef extern from "include/internal/cef_time.h": ctypedef struct cef_time_t: @@ -15,6 +16,9 @@ cdef extern from "include/internal/cef_time.h": int second int millisecond + ctypedef struct cef_basetime_t: + int64_t val + cdef extern from "include/internal/cef_types.h": cdef cppclass CefTime: CefTime() diff --git a/src/extern/cef/cef_types.pxd b/src/extern/cef/cef_types.pxd index 6bd21ba46..2e7767a46 100644 --- a/src/extern/cef/cef_types.pxd +++ b/src/extern/cef/cef_types.pxd @@ -2,8 +2,6 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -include "compile_time_constants.pxi" - from libcpp cimport bool as cpp_bool # noinspection PyUnresolvedReferences from libc.stddef cimport wchar_t @@ -13,28 +11,23 @@ from cef_string cimport cef_string_t # noinspection PyUnresolvedReferences 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 +# char16_t is wchar_t on Windows and unsigned short elsewhere in CEF. +# Using a C macro avoids the deprecated Cython IF statement. +cdef extern from *: + """ + #ifdef _WIN32 + typedef wchar_t cef_cython_char16_t; + #else + typedef unsigned short cef_cython_char16_t; + #endif + """ + ctypedef unsigned short char16_t "cef_cython_char16_t" - IF UNAME_SYSNAME == "Windows": - # noinspection PyUnresolvedReferences - ctypedef wchar_t char16 - ELSE: - ctypedef unsigned short char16 +cdef extern from "include/internal/cef_types.h": - ctypedef uint32 cef_color_t + ctypedef uint32_t cef_color_t ctypedef struct CefSettings: - cef_string_t accept_language_list - int single_process cef_string_t browser_subprocess_path int command_line_args_disabled cef_string_t cache_path @@ -56,14 +49,12 @@ cdef extern from "include/internal/cef_types.h": int ignore_certificate_errors cef_color_t background_color int persist_user_preferences - cef_string_t user_data_path int windowless_rendering_enabled int no_sandbox int external_message_pump cef_string_t framework_dir_path ctypedef struct CefBrowserSettings: - cef_string_t accept_language_list cef_color_t background_color cef_string_t standard_font_family cef_string_t fixed_font_family @@ -81,17 +72,12 @@ cdef extern from "include/internal/cef_types.h": cef_state_t javascript_close_windows cef_state_t javascript_access_clipboard cef_state_t javascript_dom_paste - cef_state_t plugins - cef_state_t universal_access_from_file_urls - cef_state_t file_access_from_file_urls - cef_state_t web_security cef_state_t image_loading cef_state_t image_shrink_standalone_to_fit cef_state_t text_area_resize cef_state_t tab_to_links cef_state_t local_storage - cef_state_t databases - cef_state_t application_cache + cef_state_t databases_deprecated cef_state_t webgl int windowless_frame_rate @@ -191,12 +177,12 @@ cdef extern from "include/internal/cef_types.h": KEYEVENT_CHAR ctypedef struct _cef_key_event_t: cef_key_event_type_t type - uint32 modifiers + uint32_t modifiers int windows_key_code int native_key_code int is_system_key - char16 character - char16 unmodified_character + char16_t character + char16_t unmodified_character cpp_bool focus_on_editable_field ctypedef _cef_key_event_t CefKeyEvent ctypedef enum cef_event_flags_t: @@ -245,7 +231,6 @@ cdef extern from "include/internal/cef_types.h": ERR_ADDRESS_UNREACHABLE = -109, ERR_SSL_CLIENT_AUTH_CERT_NEEDED = -110, ERR_TUNNEL_CONNECTION_FAILED = -111, - ERR_NO_SSL_VERSIONS_ENABLED = -112, ERR_SSL_VERSION_OR_CIPHER_MISMATCH = -113, ERR_SSL_RENEGOTIATION_REQUESTED = -114, ERR_CERT_COMMON_NAME_INVALID = -200, @@ -287,7 +272,7 @@ cdef extern from "include/internal/cef_types.h": ctypedef struct cef_mouse_event_t: int x int y - uint32 modifiers + uint32_t modifiers ctypedef cef_mouse_event_t CefMouseEvent # RenderHandler > GetScreenInfo(): @@ -323,16 +308,18 @@ cdef extern from "include/internal/cef_types.h": # LifespanHandler and RequestHandler ctypedef enum cef_window_open_disposition_t: - WOD_UNKNOWN, - WOD_CURRENT_TAB, - WOD_SINGLETON_TAB, - WOD_NEW_FOREGROUND_TAB, - WOD_NEW_BACKGROUND_TAB, - WOD_NEW_POPUP, - WOD_NEW_WINDOW, - WOD_SAVE_TO_DISK, - WOD_OFF_THE_RECORD, - WOD_IGNORE_ACTION + CEF_WOD_UNKNOWN, + CEF_WOD_CURRENT_TAB, + CEF_WOD_SINGLETON_TAB, + CEF_WOD_NEW_FOREGROUND_TAB, + CEF_WOD_NEW_BACKGROUND_TAB, + CEF_WOD_NEW_POPUP, + CEF_WOD_NEW_WINDOW, + CEF_WOD_SAVE_TO_DISK, + CEF_WOD_OFF_THE_RECORD, + CEF_WOD_IGNORE_ACTION, + CEF_WOD_SWITCH_TO_TAB, + CEF_WOD_NEW_PICTURE_IN_PICTURE ctypedef cef_window_open_disposition_t WindowOpenDisposition ctypedef enum cef_path_key_t: @@ -347,12 +334,6 @@ cdef extern from "include/internal/cef_types.h": PK_DIR_RESOURCES, ctypedef cef_path_key_t PathKey - ctypedef enum cef_plugin_policy_t: - PLUGIN_POLICY_ALLOW, - PLUGIN_POLICY_DETECT_IMPORTANT, - PLUGIN_POLICY_BLOCK, - PLUGIN_POLICY_DISABLE, - # Drag & drop ctypedef enum cef_drag_operations_mask_t: diff --git a/src/extern/cef/cef_values.pxd b/src/extern/cef/cef_values.pxd index 3210432b4..249c2ae53 100644 --- a/src/extern/cef/cef_values.pxd +++ b/src/extern/cef/cef_values.pxd @@ -9,6 +9,7 @@ from libcpp.vector cimport vector from cef_types cimport cef_value_type_t cdef extern from "include/cef_values.h": + @staticmethod cdef CefRefPtr[CefBinaryValue] CefBinaryValue_Create \ "CefBinaryValue::Create"(const void* data, size_t data_size) @@ -31,7 +32,8 @@ cdef extern from "include/cef_values.h": CefRefPtr[CefBinaryValue] Copy() size_t GetSize() size_t GetData(void* buffer_, size_t buffer_size, size_t data_offset) - + + @staticmethod cdef CefRefPtr[CefDictionaryValue] CefDictionaryValue_Create \ "CefDictionaryValue::Create"() diff --git a/src/extern/cef/cef_web_plugin.pxd b/src/extern/cef/cef_web_plugin.pxd deleted file mode 100644 index 25d33fa9d..000000000 --- a/src/extern/cef/cef_web_plugin.pxd +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2013 CEF Python, see the Authors file. -# All rights reserved. Licensed under BSD 3-clause license. -# Project website: https://github.com/cztomczak/cefpython - -from cef_string cimport CefString - -# CEF 3 only. - -cdef extern from "include/cef_web_plugin.h": - cdef cppclass CefWebPluginInfo: - CefString GetName() - CefString GetPath() - CefString GetVersion() - CefString GetDescription() diff --git a/src/extern/cef/cef_win.pxd b/src/extern/cef/cef_win.pxd index 5e341b75b..3b1dd1a83 100644 --- a/src/extern/cef/cef_win.pxd +++ b/src/extern/cef/cef_win.pxd @@ -5,9 +5,10 @@ include "compile_time_constants.pxi" # noinspection PyUnresolvedReferences -from windows cimport HWND, RECT, HINSTANCE, HCURSOR +from windows cimport HWND, HINSTANCE, HCURSOR from cef_string cimport CefString from libcpp cimport bool as cpp_bool +from cef_types cimport CefRect cdef extern from "include/internal/cef_win.h": @@ -16,9 +17,15 @@ cdef extern from "include/internal/cef_win.h": # noinspection PyUnresolvedReferences ctypedef HCURSOR CefCursorHandle + ctypedef enum cef_runtime_style_t: + CEF_RUNTIME_STYLE_DEFAULT + CEF_RUNTIME_STYLE_CHROME + CEF_RUNTIME_STYLE_ALLOY + cdef cppclass CefWindowInfo: + cef_runtime_style_t runtime_style void SetAsChild(CefWindowHandle parent, - RECT windowRect) + const CefRect windowRect) void SetAsPopup(CefWindowHandle parent, const CefString& windowName) void SetAsWindowless(CefWindowHandle parent) diff --git a/src/extern/cef/platform_cimports.pxi b/src/extern/cef/platform_cimports.pxi new file mode 100644 index 000000000..3e1f18f60 --- /dev/null +++ b/src/extern/cef/platform_cimports.pxi @@ -0,0 +1,6 @@ +IF UNAME_SYSNAME == "Windows": + from cef_win cimport * +ELIF UNAME_SYSNAME == "Darwin": + from cef_mac cimport * +ELSE: + from cef_linux cimport * diff --git a/src/extern/linux.pxd b/src/extern/linux.pxd index b2da7068d..c1e62ec4d 100644 --- a/src/extern/linux.pxd +++ b/src/extern/linux.pxd @@ -3,10 +3,11 @@ # Project website: https://github.com/cztomczak/cefpython cdef extern from "gtk/gtk.h" nogil: - ctypedef void* GdkNativeWindow ctypedef void* GtkWidget - cdef GtkWidget* gtk_plug_new(GdkNativeWindow socket_id) + cdef GtkWidget* gtk_plug_new(unsigned long socket_id) cdef void gtk_widget_show(GtkWidget* widget) + ctypedef void* GMainContext + int g_main_context_iteration(GMainContext* context, int may_block) ctypedef char* XPointer ctypedef struct XImage: diff --git a/src/extern/main_message_loop.pxd b/src/extern/main_message_loop.pxd index 486f309af..af05e1100 100644 --- a/src/extern/main_message_loop.pxd +++ b/src/extern/main_message_loop.pxd @@ -2,11 +2,11 @@ # All rights reserved. Licensed under BSD 3-clause license. # Project website: https://github.com/cztomczak/cefpython -from cef_scoped_ptr cimport scoped_ptr +from libcpp.memory cimport unique_ptr cdef extern from \ "subprocess/main_message_loop/main_message_loop_external_pump.h": cdef cppclass MainMessageLoopExternalPump: @staticmethod - scoped_ptr[MainMessageLoopExternalPump] Create() + unique_ptr[MainMessageLoopExternalPump] Create() diff --git a/src/extern/multimap.pxd b/src/extern/multimap.pxd index a059744a5..19cc95d7d 100644 --- a/src/extern/multimap.pxd +++ b/src/extern/multimap.pxd @@ -15,7 +15,7 @@ cdef extern from "" namespace "std": iterator operator--() nogil bint operator==(iterator) nogil bint operator!=(iterator) nogil - multimap() nogil except + + multimap() except + nogil # noinspection PyUnresolvedReferences U& operator[](T&) nogil # noinspection PyUnresolvedReferences diff --git a/src/extern/task.pxd b/src/extern/task.pxd index 14f2777b4..3c883ad95 100644 --- a/src/extern/task.pxd +++ b/src/extern/task.pxd @@ -11,13 +11,13 @@ from cef_cookie cimport CefCookie, CefCookieManager from cef_cookie cimport CefSetCookieCallback, CefDeleteCookiesCallback # noinspection PyUnresolvedReferences from libcpp cimport bool as cpp_bool -from cef_types cimport int64 +from libc.stdint cimport int64_t cdef extern from "client_handler/task.h": void PostTaskWrapper(int threadId, int taskId) nogil - void PostDelayedTaskWrapper(int threadId, int64 delay_ms, int taskId) nogil + void PostDelayedTaskWrapper(int threadId, int64_t delay_ms, int taskId) nogil cdef CefRefPtr[CefTask] CreateTask_SetCookie( CefCookieManager* obj, diff --git a/src/extern/wstring.pxd b/src/extern/wstring.pxd index fdfbe9b9e..df685dfb4 100644 --- a/src/extern/wstring.pxd +++ b/src/extern/wstring.pxd @@ -9,15 +9,15 @@ cdef extern from *: cdef extern from "" namespace "std": - size_t npos = -1 + const size_t npos cdef cppclass wstring: - wstring() nogil except + - wstring(wchar_t *) nogil except + - wstring(wchar_t *, size_t) nogil except + - wstring(string&) nogil except + + wstring() except + nogil + wstring(wchar_t *) except + nogil + wstring(wchar_t *, size_t) except + nogil + wstring(string&) except + nogil # as a string formed by a repetition of character c, n times. - wstring(size_t, char) nogil except + + wstring(size_t, char) except + nogil const_wchar_t* c_str() nogil const_wchar_t* data() nogil diff --git a/src/frame.pyx b/src/frame.pyx index 812e4b3c9..f1c5276d9 100644 --- a/src/frame.pyx +++ b/src/frame.pyx @@ -11,11 +11,11 @@ cdef dict g_pyFrames = {} # it shouldn't be kept global anymore. cdef list g_unreferenced_frames = [] # [str unique identifier, ..] -cdef object GetUniqueFrameId(int browserId, object frameId): - return str(browserId) +"#"+ str(frameId) +cdef str GetUniqueFrameId(int browserId, object frameId): + return str(browserId) +"#"+ frameId cdef PyFrame GetPyFrameById(int browserId, object frameId): - cdef object uniqueFrameId = GetUniqueFrameId(browserId, frameId) + cdef str uniqueFrameId = GetUniqueFrameId(browserId, frameId) if uniqueFrameId in g_pyFrames: return g_pyFrames[uniqueFrameId] return None @@ -23,23 +23,36 @@ cdef PyFrame GetPyFrameById(int browserId, object frameId): cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): global g_pyFrames - if cefFrame == NULL or not cefFrame.get(): + if not cefFrame or not cefFrame.get(): 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 frameId < 0: + cdef CefString frameId = cefFrame.get().GetIdentifier() + cdef CefRefPtr[CefBrowser] cefBrowser = cefFrame.get().GetBrowser() + if not cefBrowser.get(): + # GetBrowser() can return NULL when a frame is being created or + # destroyed during rapid navigation (e.g. iframes on heavy pages). + # Raising here prevents a SIGSEGV from the naked .get().GetIdentifier() + # chain that was here before this check. + raise Exception("GetPyFrame(): CefBrowser is NULL" + " (frame lifecycle transition during navigation?)") + cdef int browserId = cefBrowser.get().GetIdentifier() + if not browserId: + raise Exception("GetPyFrame(): browserId is 0 (browser not yet initialised)") + # frameId may be empty for internal frames that CEF creates before the + # underlying renderer frame is ready (e.g. the PDF-viewer internal frame + # on the first OnLoadStart). The code below already creates an incomplete + # PyFrame for this case, so do not assert here. + cdef str uniqueFrameId = GetUniqueFrameId(browserId, CefToPyString(frameId)) + + if frameId.empty(): # Underlying frame does not yet exist. In such case PyFrame # is not stored in g_pyFrames since frameId is invalid. # However even though frame is not supposed to exist, you # can still call CefFrame.ExecuteFunction and it works fine # in tutorial.py example. Debug("GetPyFrame(): underlying frame does not yet exist:" - " browserId = {0}, frameId = {1}".format(browserId, frameId)) + " browserId = {0}, frameId = {1}".format(browserId, CefToPyString(frameId))) else: if uniqueFrameId in g_pyFrames: return g_pyFrames[uniqueFrameId] @@ -56,11 +69,11 @@ cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): del g_pyFrames[uFid] # ---- - pyFrame = PyFrame(browserId, frameId) + pyFrame = PyFrame(browserId, CefToPyString(frameId)) pyFrame.cefFrame = cefFrame if uniqueFrameId in g_unreferenced_frames \ - or frameId < 0 \ + or frameId.empty() \ or browserId in g_unreferenced_browsers \ or browserId in g_closed_browsers: # Browser was already globally unreferenced in OnBeforeClose, @@ -77,19 +90,19 @@ cdef PyFrame GetPyFrame(CefRefPtr[CefFrame] cefFrame): # 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) + Debug("GetPyFrame(): create new PyFrame, frameId=%s" % CefToPyString(frameId)) g_pyFrames[uniqueFrameId] = pyFrame return pyFrame -cdef void RemovePyFrame(int browserId, object frameId) except *: +cdef void RemovePyFrame(int browserId, str frameId) except *: # Called from V8ContextHandler_OnContextReleased(). global g_pyFrames cdef PyFrame pyFrame - cdef object uniqueFrameId = GetUniqueFrameId(browserId, frameId) + cdef str uniqueFrameId = GetUniqueFrameId(browserId, frameId) if uniqueFrameId in g_pyFrames: Debug("del g_pyFrames[%s]" % uniqueFrameId) pyFrame = g_pyFrames[uniqueFrameId] - pyFrame.cefFrame.Assign(NULL) + pyFrame.cefFrame.Assign(nullptr) del pyFrame del g_pyFrames[uniqueFrameId] g_unreferenced_frames.append(uniqueFrameId) @@ -103,13 +116,13 @@ cdef void RemovePyFramesForBrowser(int browserId) except *: cdef object uniqueFrameId cdef PyFrame pyFrame global g_pyFrames - for uniqueFrameId, pyFrame in g_pyFrames.iteritems(): + for uniqueFrameId, pyFrame in g_pyFrames.items(): if pyFrame.GetBrowserIdentifier() == browserId: toRemove.append(uniqueFrameId) for uniqueFrameId in toRemove: Debug("del g_pyFrames[%s]" % uniqueFrameId) pyFrame = g_pyFrames[uniqueFrameId] - pyFrame.cefFrame.Assign(NULL) + pyFrame.cefFrame.Assign(nullptr) del pyFrame del g_pyFrames[uniqueFrameId] g_unreferenced_frames.append(uniqueFrameId) @@ -119,22 +132,22 @@ cdef void RemovePyFramesForBrowser(int browserId) except *: cdef class PyFrame: cdef CefRefPtr[CefFrame] cefFrame cdef int browserId - cdef object frameId + cdef str frameId cdef CefRefPtr[CefFrame] GetCefFrame(self) except *: # Do not call IsValid() here, if the frame does not exist # then no big deal, no reason to crash the application. # The CEF calls will fail, but they also won't cause crash. - if self.cefFrame != NULL and self.cefFrame.get(): + if self.cefFrame and self.cefFrame.get(): return self.cefFrame raise Exception("PyFrame.GetCefFrame() failed: CefFrame was destroyed") - def __init__(self, int browserId, int frameId): + def __init__(self, int browserId, object frameId): self.browserId = browserId self.frameId = frameId cpdef py_bool IsValid(self): - if self.cefFrame != NULL and self.cefFrame.get() \ + if self.cefFrame and self.cefFrame.get() \ and self.cefFrame.get().IsValid(): return True return False @@ -163,12 +176,14 @@ cdef class PyFrame: code += ")" self.ExecuteJavascript(code) - cpdef py_void ExecuteJavascript(self, py_string jsCode, - py_string scriptUrl="", int startLine=1): + cpdef py_void ExecuteJavascript(self, object jsCode, + object scriptUrl=None, int startLine=1): + if scriptUrl is None: + scriptUrl = u"" self.GetCefFrame().get().ExecuteJavaScript(PyToCefStringValue(jsCode), PyToCefStringValue(scriptUrl), startLine) - cpdef object GetIdentifier(self): + cpdef str GetIdentifier(self): # It is better to save browser and frame identifiers during # browser instantiation. When freeing PyBrowser and PyFrame # we need these identifiers. CefFrame and CefBrowser may already @@ -203,14 +218,7 @@ cdef class PyFrame: cpdef py_bool IsMain(self): return self.GetCefFrame().get().IsMain() - cpdef py_void LoadString(self, py_string value, py_string url): - cdef CefString cefValue - cdef CefString cefUrl - PyToCefString(value, cefValue) - PyToCefString(url, cefUrl) - self.GetCefFrame().get().LoadString(cefValue, cefUrl) - - cpdef py_void LoadUrl(self, py_string url): + cpdef py_void LoadUrl(self, object url): cdef CefString cefUrl PyToCefString(url, cefUrl) self.GetCefFrame().get().LoadURL(cefUrl) @@ -229,3 +237,25 @@ cdef class PyFrame: cpdef py_void ViewSource(self): self.GetCefFrame().get().ViewSource() + + cpdef py_void SendProcessMessage(self, cef_process_id_t targetProcess, + object frameId, object messageName, list pyArguments + ) : + cdef CefRefPtr[CefProcessMessage] message = \ + CefProcessMessage_Create(PyToCefStringValue(messageName)) + # This does not work, no idea why, the CEF implementation + # seems not to allow it, both Assign() and swap() do not work: + # | message.get().GetArgumentList().Assign(arguments.get()) + # | message.get().GetArgumentList().swap(arguments) + cdef CefRefPtr[CefListValue] messageArguments = \ + message.get().GetArgumentList() + PyListToExistingCefListValue(self.GetBrowserIdentifier(), + frameId, + pyArguments, messageArguments) + Debug("SendProcessMessage(): message=%s, arguments size=%d" % ( + messageName, + message.get().GetArgumentList().get().GetSize())) + + self.GetCefFrame().get().SendProcessMessage(targetProcess, message) + + diff --git a/src/handlers/accessibility_handler.pyx b/src/handlers/accessibility_handler.pyx index 055fb61cf..b1c39cc50 100644 --- a/src/handlers/accessibility_handler.pyx +++ b/src/handlers/accessibility_handler.pyx @@ -9,7 +9,7 @@ include "../process_message_utils.pyx" cdef public void AccessibilityHandler_OnAccessibilityTreeChange( CefRefPtr[CefValue] cefValue - ) except * with gil: + ) noexcept with gil: cdef object value = CefValueToPyValue(cefValue) cdef object callback try: @@ -22,7 +22,7 @@ cdef public void AccessibilityHandler_OnAccessibilityTreeChange( cdef public void AccessibilityHandler_OnAccessibilityLocationChange( CefRefPtr[CefValue] cefValue - ) except * with gil: + ) noexcept with gil: cdef object value = CefValueToPyValue(cefValue) cdef object callback try: diff --git a/src/handlers/browser_process_handler.pyx b/src/handlers/browser_process_handler.pyx index 09463b0ef..0ff261d7f 100644 --- a/src/handlers/browser_process_handler.pyx +++ b/src/handlers/browser_process_handler.pyx @@ -4,9 +4,164 @@ include "../cefpython.pyx" +cdef public void BrowserProcessHandler_OnContextInitialized() noexcept with gil: + try: + global g_context_initialized + Debug("BrowserProcessHandler_OnContextInitialized()") + g_context_initialized = True + # Browser creation is handled by BrowserProcessHandler_CreatePendingBrowsers, + # posted as a separate task in C++ so it runs at the outer message-loop level. + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) + +cdef public void BrowserProcessHandler_CreatePendingBrowsers() noexcept with gil: + try: + Debug("BrowserProcessHandler_CreatePendingBrowsers()") + if g_pending_browsers: + pending = list(g_pending_browsers) + del g_pending_browsers[:] + for params in pending: + browser = CreateBrowserSync(**params) + # _linux_schedule_xembed is called inside CreateBrowserSync. + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) + + +IF UNAME_SYSNAME == "Linux": + def _linux_schedule_xembed(browser, embed_info): + """Schedule deferred XUnmap+XReparentWindow for Xwayland embedding. + + WindowInfo.SetAsChild() substituted root as CEF's parent to avoid the + Xwayland cross-client MatchError. This function performs the reparent: + 1. Poll (10 ms) until browser.GetWindowHandle() returns non-zero. + 2. Poll (10 ms) until Chrome's window becomes IsViewable. + 3. XUnmapWindow — Mutter withdraws its decoration frame. + 4. 100 ms GLib timer — Mutter processes UnmapNotify (race-free). + 5. XReparentWindow(chrome -> real_parent, 0, 0) + 6. XMoveResizeWindow(0, 0, width, height) + 7. XMapRaised + """ + import ctypes as _ct + + real_parent_xid = embed_info['real_parent'] + width = embed_info['width'] + height = embed_info['height'] + if not real_parent_xid: + return + + _x11 = _ct.CDLL("libX11.so.6") + _gdk = _ct.CDLL("libgdk-3.so.0") + _glib = _ct.CDLL("libglib-2.0.so.0") + + _gdk.gdk_display_get_default.restype = _ct.c_void_p + _gdk.gdk_x11_display_get_xdisplay.restype = _ct.c_void_p + _xdisp = _ct.c_void_p(_gdk.gdk_x11_display_get_xdisplay( + _ct.c_void_p(_gdk.gdk_display_get_default()))) + + _x11.XGetWindowAttributes.restype = _ct.c_int + + # Full XWindowAttributes struct (LP64 layout, 136 bytes). + # All fields must be declared; omitting trailing fields truncates the + # buffer to 96 bytes and XGetWindowAttributes writes all_event_masks + # (offset 96) past the end, corrupting adjacent heap memory. + class _XWA(_ct.Structure): + _fields_ = [ + ("x", _ct.c_int), ("y", _ct.c_int), + ("width", _ct.c_int), ("height", _ct.c_int), + ("border_width", _ct.c_int), ("depth", _ct.c_int), + ("visual", _ct.c_void_p), ("root", _ct.c_ulong), + ("c_class", _ct.c_int), ("bit_gravity", _ct.c_int), + ("win_gravity", _ct.c_int), ("backing_store", _ct.c_int), + ("backing_planes", _ct.c_ulong), ("backing_pixel", _ct.c_ulong), + ("save_under", _ct.c_int), ("colormap", _ct.c_ulong), + ("map_installed", _ct.c_int), ("map_state", _ct.c_int), + ("all_event_masks", _ct.c_long), + ("your_event_mask", _ct.c_long), + ("do_not_propagate_mask", _ct.c_long), + ("override_redirect", _ct.c_int), + ("screen", _ct.c_void_p), + ] + + _browser_ref = [browser] + # Discovered once GetWindowHandle() returns non-zero; shared into + # the reparent closure via a one-element list so it can be set + # inside _wait_for_map and read inside _do_reparent. + _chrome_xid = [0] + _poll_count = [0] + + # Steps 5-7: reparent callback — fired 100 ms after the unmap. + _ReparentCb = _ct.CFUNCTYPE(_ct.c_bool, _ct.c_void_p) + def _do_reparent(_ud, _pxid=real_parent_xid, _w=width, _h=height, + _xd=_xdisp): + _cxid = _chrome_xid[0] + try: + # width/height were captured at schedule time and may be 0 + # if the parent window had not yet been laid out (e.g. the + # Qt container hadn't been sized by the layout manager yet). + # Query the parent's current size so the browser is resized + # to whatever the container actually is now. + _wa_p = _XWA() + if _x11.XGetWindowAttributes(_xd, _ct.c_ulong(_pxid), + _ct.byref(_wa_p)): + if _wa_p.width > 0 and _wa_p.height > 0: + _w, _h = _wa_p.width, _wa_p.height + _x11.XReparentWindow(_xd, _ct.c_ulong(_cxid), + _ct.c_ulong(_pxid), + _ct.c_int(0), _ct.c_int(0)) + _x11.XMoveResizeWindow(_xd, _ct.c_ulong(_cxid), + _ct.c_int(0), _ct.c_int(0), + _ct.c_uint(_w), _ct.c_uint(_h)) + _x11.XMapRaised(_xd, _ct.c_ulong(_cxid)) + _x11.XSync(_xd, _ct.c_int(0)) + _b = _browser_ref[0] + if _b: + _b.SetBounds(0, 0, _w, _h) + _b.NotifyMoveOrResizeStarted() + _b.SetFocus(True) + except Exception as _e: + Debug("_linux_schedule_xembed reparent error: " + str(_e)) + return False # one-shot timer + _rep_cb = _ReparentCb(_do_reparent) + g_linux_reparent_callbacks.append(_rep_cb) + + # Steps 1-4: poll until handle is available and window is IsViewable, + # then unmap to drop Mutter's frame and schedule the reparent. + _PollCb = _ct.CFUNCTYPE(_ct.c_bool, _ct.c_void_p) + def _wait_for_map(_ud, _xd=_xdisp): + _poll_count[0] += 1 + # Step 1: wait for a valid native window handle. + if not _chrome_xid[0]: + _b = _browser_ref[0] + if _b: + _chrome_xid[0] = _b.GetWindowHandle() + if not _chrome_xid[0]: + if _poll_count[0] > 300: # 3 s timeout + Debug("_linux_schedule_xembed: timeout waiting for handle") + return False + return True # keep polling + # Step 2: wait until Chrome's window is mapped and visible. + _xwa = _XWA() + _ok = _x11.XGetWindowAttributes(_xd, _ct.c_ulong(_chrome_xid[0]), + _ct.byref(_xwa)) + _ms = _xwa.map_state if _ok else -1 + if _ok and _ms == 2: # IsViewable + _x11.XUnmapWindow(_xd, _ct.c_ulong(_chrome_xid[0])) + _x11.XSync(_xd, _ct.c_int(0)) + _glib.g_timeout_add(100, _rep_cb, None) + return False # stop polling + if _poll_count[0] > 300: # 3 s timeout + Debug("_linux_schedule_xembed: timeout waiting for IsViewable") + return False + return True # keep polling + _poll_cb = _PollCb(_wait_for_map) + g_linux_reparent_callbacks.append(_poll_cb) + _glib.g_timeout_add(10, _poll_cb, None) + cdef public void BrowserProcessHandler_OnRenderProcessThreadCreated( CefRefPtr[CefListValue] extra_info - ) except * with gil: + ) noexcept with gil: try: pass except: @@ -15,9 +170,79 @@ cdef public void BrowserProcessHandler_OnRenderProcessThreadCreated( cdef public void BrowserProcessHandler_OnBeforeChildProcessLaunch( CefRefPtr[CefCommandLine] cefCommandLine - ) except * with gil: + ) noexcept with gil: try: AppendSwitchesToCommandLine(cefCommandLine, g_commandLineSwitches) + IF UNAME_SYSNAME == "Linux": + _StripPseudonymizationSaltHandle(cefCommandLine) # strips crash-inducing switches except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) + + +IF UNAME_SYSNAME == "Linux": + # Switches that must be stripped from every child-process command line on + # Linux to prevent crashes in CEF 146 subprocesses. + # + # --pseudonymization-salt-handle (Chrome 130+): + # Passed to directly-launched (non-zygote) utility subprocesses, + # expecting GlobalDescriptors[key=7] to already be populated. + # CEF 146 does not perform this GlobalDescriptors initialization for + # the non-zygote path, so the subprocess crashes: + # "Failed global descriptor lookup: 7" + # Removing the switch causes Chrome to use a per-process random salt. + # + # --change-stack-guard-on-fork (Chrome / Linux zygote): + # Tells a Zygote-forked child to randomize its stack canary after fork. + # ChangeStackGuard() is called from inside the Zygote event-loop stack + # frames that the fork() inherited. Those frames have the pre-fork + # canary saved; when they return the canary check fails: + # "*** stack smashing detected ***: terminated" + # The subprocess exits before Chrome's initialization completes, so + # OnContextInitialized never fires. Removing the switch prevents + # ChangeStackGuard() from being called at all; the child keeps the + # Zygote's canary (which is still randomized at Zygote startup). + _STRIP_CHILD_SWITCHES = frozenset([ + "pseudonymization-salt-handle", + "change-stack-guard-on-fork", + ]) + + cdef void _StripPseudonymizationSaltHandle( + CefRefPtr[CefCommandLine] cefCommandLine) except * with gil: + cdef CefString cefSwitchName + cdef bint needs_strip = False + for sw in _STRIP_CHILD_SWITCHES: + cefSwitchName = PyToCefStringValue(sw) + if cefCommandLine.get().HasSwitch(cefSwitchName): + needs_strip = True + break + if not needs_strip: + return + + # Capture current state before resetting. + cdef CefString program + program = cefCommandLine.get().GetProgram() + + cdef cpp_map[CefString, CefString] switches + cefCommandLine.get().GetSwitches(switches) + + cdef cpp_vector[CefString] arguments + cefCommandLine.get().GetArguments(arguments) + + # Rebuild command line omitting the problem switches. + cefCommandLine.get().Reset() + cefCommandLine.get().SetProgram(program) + + cdef cpp_map[CefString, CefString].iterator it = switches.begin() + while it != switches.end(): + if CefToPyString(deref(it).first) not in _STRIP_CHILD_SWITCHES: + if deref(it).second.empty(): + cefCommandLine.get().AppendSwitch(deref(it).first) + else: + cefCommandLine.get().AppendSwitchWithValue( + deref(it).first, deref(it).second) + preinc(it) + + cdef size_t i + for i in range(arguments.size()): + cefCommandLine.get().AppendArgument(arguments[i]) diff --git a/src/handlers/context_menu_handler.pyx b/src/handlers/context_menu_handler.pyx new file mode 100644 index 000000000..1fdd8a69c --- /dev/null +++ b/src/handlers/context_menu_handler.pyx @@ -0,0 +1,141 @@ +# Copyright (c) 2016 CEF Python, see the Authors file. +# All rights reserved. Licensed under BSD 3-clause license. +# Project website: https://github.com/cztomczak/cefpython + +include "../cefpython.pyx" +include "../browser.pyx" + +cimport cef_types +from cef_types cimport TID_UI, cef_event_flags_t + +# --------------------------------------------------------------------------- +# Minimal CefMenuModel and CefRunContextMenuCallback declarations. +# cef_context_menu_handler.h includes cef_menu_model.h so one block suffices. +# --------------------------------------------------------------------------- + +cdef extern from "include/cef_context_menu_handler.h": + # CefMenuModel methods (subset used by RunContextMenu) + cdef cppclass CefMenuModel: + size_t GetCount() noexcept + int GetCommandIdAt(size_t index) noexcept + CefString GetLabelAt(size_t index) noexcept + int GetTypeAt(size_t index) noexcept # returns cef_menu_item_type_t as int + cpp_bool IsEnabledAt(size_t index) noexcept + + cdef cppclass CefRunContextMenuCallback: + void Continue(int command_id, cef_event_flags_t event_flags) noexcept + void Cancel() noexcept + +# --------------------------------------------------------------------------- +# Python wrapper: RunContextMenuCallback +# --------------------------------------------------------------------------- + +cdef class RunContextMenuCallback: + cdef CefRefPtr[CefRunContextMenuCallback] _cb + cdef bint _done + + def __init__(self): + self._done = False + + def Continue(self, int command_id, int event_flags=0): + if not self._done: + self._done = True + self._cb.get().Continue(command_id, + event_flags) + + def Cancel(self): + if not self._done: + self._done = True + self._cb.get().Cancel() + +cdef RunContextMenuCallback _MakeRunContextMenuCallback( + CefRefPtr[CefRunContextMenuCallback] cb): + cdef RunContextMenuCallback obj = RunContextMenuCallback.__new__( + RunContextMenuCallback) + obj._cb = cb + obj._done = False + return obj + +# --------------------------------------------------------------------------- +# Python wrapper: MenuModel (read-only snapshot for RunContextMenu). +# --------------------------------------------------------------------------- + +cdef class MenuModel: + # Snapshot taken at callback time so Python code can iterate freely + # without worrying about CEF model lifetime. + cdef list _items # list of dicts + + def __init__(self): + self._items = [] + + def GetCount(self): + return len(self._items) + + def GetTypeAt(self, int index): + return self._items[index]['type'] + + def GetLabelAt(self, int index): + return self._items[index]['label'] + + def GetCommandIdAt(self, int index): + return self._items[index]['command_id'] + + def IsEnabledAt(self, int index): + return self._items[index]['enabled'] + +cdef MenuModel _SnapshotMenuModel(CefRefPtr[CefMenuModel] cef_model): + cdef MenuModel obj = MenuModel.__new__(MenuModel) + cdef size_t count = cef_model.get().GetCount() + cdef size_t i + obj._items = [] + for i in range(count): + item_type = int(cef_model.get().GetTypeAt(i)) + label = CefToPyString(cef_model.get().GetLabelAt(i)) + command_id = cef_model.get().GetCommandIdAt(i) + enabled = bool(cef_model.get().IsEnabledAt(i)) + obj._items.append({ + 'type': item_type, + 'label': label, + 'command_id': command_id, + 'enabled': enabled, + }) + return obj + +# --------------------------------------------------------------------------- +# CEF menu item type constants exposed to Python (from cef_types.h). +# --------------------------------------------------------------------------- + +MENUITEMTYPE_NONE = 0 +MENUITEMTYPE_COMMAND = 1 +MENUITEMTYPE_CHECK = 2 +MENUITEMTYPE_RADIO = 3 +MENUITEMTYPE_SEPARATOR = 4 +MENUITEMTYPE_SUBMENU = 5 + +# --------------------------------------------------------------------------- +# C-level dispatch called from context_menu_handler.cpp +# --------------------------------------------------------------------------- + +cdef public int ContextMenuHandler_RunContextMenu( + CefRefPtr[CefBrowser] cef_browser, + CefRefPtr[CefMenuModel] cef_model, + CefRefPtr[CefRunContextMenuCallback] cef_callback + ) noexcept with gil: + cdef PyBrowser browser + cdef py_bool ret + try: + assert IsThread(TID_UI), "Must be called on the UI thread" + browser = GetPyBrowser(cef_browser, "RunContextMenu") + callback_py = browser.GetClientCallback("RunContextMenu") + if not callback_py: + return 0 # no Python handler — fall back to CEF default + model_py = _SnapshotMenuModel(cef_model) + cb_py = _MakeRunContextMenuCallback(cef_callback) + ret = callback_py(browser=browser, + model=model_py, + callback=cb_py) + return 1 if ret else 0 + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) + return 0 diff --git a/src/handlers/cookie_access_filter.pyx b/src/handlers/cookie_access_filter.pyx new file mode 100644 index 000000000..439dffc9c --- /dev/null +++ b/src/handlers/cookie_access_filter.pyx @@ -0,0 +1,91 @@ +# Copyright (c) 2018 CEF Python, see the Authors file. +# All rights reserved. Licensed under BSD 3-clause license. +# Project website: https://github.com/cztomczak/cefpython + +include "../cefpython.pyx" +include "../browser.pyx" +include "../frame.pyx" +include "../process_message_utils.pyx" + +cdef public cpp_bool CookieAccessFilter_CanSendCookie( + CefRefPtr[CefBrowser] cef_browser, + CefRefPtr[CefFrame] cef_frame, + CefRefPtr[CefRequest] cef_request, + const CefCookie& cef_cookie + ) except * with gil: + cdef PyBrowser browser + cdef PyFrame frame + cdef PyRequest request + cdef PyCookie cookie + cdef object callback + cdef py_bool retval + try: + Debug("CookieAccessFilter_CanSendCookie") + # Issue #455: CefRequestHandler callbacks still executed after + # browser was closed. + if IsBrowserClosed(cef_browser): + return False + if not cef_frame.get().GetBrowser().get(): + return True # default: allow cookie + + browser = GetPyBrowser(cef_browser, "CanSendCookie") + frame = GetPyFrame(cef_frame) + request = CreatePyRequest(cef_request) + cookie = CreatePyCookie(cef_cookie) + callback = browser.GetClientCallback("CanSendCookie") + if callback: + retval = callback( + browser=browser, + frame=frame, + request=request, + cookie=cookie) + return bool(retval) + else: + # Return True by default + return True + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) + +cdef public cpp_bool CookieAccessFilter_CanSaveCookie( + CefRefPtr[CefBrowser] cef_browser, + CefRefPtr[CefFrame] cef_frame, + CefRefPtr[CefRequest] cef_request, + CefRefPtr[CefResponse] cef_response, + const CefCookie& cef_cookie + ) except * with gil: + cdef PyBrowser browser + cdef PyFrame frame + cdef PyRequest request + cdef PyResponse response + cdef PyCookie cookie + cdef object callback + cdef py_bool retval + try: + # Issue #455: CefRequestHandler callbacks still executed after + # browser was closed. + if IsBrowserClosed(cef_browser): + return False + if not cef_frame.get().GetBrowser().get(): + return True # default: allow cookie + + browser = GetPyBrowser(cef_browser, "CanSaveCookie") + frame = GetPyFrame(cef_frame) + request = CreatePyRequest(cef_request) + response = CreatePyResponse(cef_response) + cookie = CreatePyCookie(cef_cookie) + callback = browser.GetClientCallback("CanSaveCookie") + if callback: + retval = callback( + browser=browser, + frame=frame, + request=request, + response=response, + cookie=cookie) + return bool(retval) + else: + # Return True by default + return True + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) diff --git a/src/handlers/display_handler.pyx b/src/handlers/display_handler.pyx index 7d66d00f7..961f14399 100644 --- a/src/handlers/display_handler.pyx +++ b/src/handlers/display_handler.pyx @@ -9,12 +9,14 @@ cdef public void DisplayHandler_OnAddressChange( CefRefPtr[CefBrowser] cefBrowser, CefRefPtr[CefFrame] cefFrame, const CefString& cefUrl - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame - cdef py_string pyUrl + cdef object pyUrl cdef object callback try: + if not cefFrame.get().GetBrowser().get(): + return pyBrowser = GetPyBrowser(cefBrowser, "OnAddressChange") pyFrame = GetPyFrame(cefFrame) pyUrl = CefToPyString(cefUrl) @@ -47,9 +49,9 @@ cdef public cpp_bool DisplayHandler_OnAutoResize( cdef public void DisplayHandler_OnTitleChange( CefRefPtr[CefBrowser] cefBrowser, const CefString& cefTitle - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser - cdef py_string pyTitle + cdef object pyTitle cdef object callback try: pyBrowser = GetPyBrowser(cefBrowser, "OnTitleChange") @@ -66,7 +68,7 @@ cdef public cpp_bool DisplayHandler_OnTooltip( CefString& cefText ) except * with gil: cdef PyBrowser pyBrowser - cdef py_string pyText + cdef object pyText cdef list pyTextOut cdef object callback cdef py_bool returnValue @@ -88,9 +90,9 @@ cdef public cpp_bool DisplayHandler_OnTooltip( cdef public void DisplayHandler_OnStatusMessage( CefRefPtr[CefBrowser] cefBrowser, const CefString& cefValue - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser - cdef py_string pyValue + cdef object pyValue cdef object callback try: pyBrowser = GetPyBrowser(cefBrowser, "OnStatusMessage") @@ -110,8 +112,8 @@ cdef public cpp_bool DisplayHandler_OnConsoleMessage( int line ) except * with gil: cdef PyBrowser pyBrowser - cdef py_string pyMessage - cdef py_string pySource + cdef object pyMessage + cdef object pySource cdef py_bool returnValue cdef object callback try: @@ -132,7 +134,7 @@ cdef public cpp_bool DisplayHandler_OnConsoleMessage( cdef public void DisplayHandler_OnLoadingProgressChange( CefRefPtr[CefBrowser] cefBrowser, double progress - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef object callback try: @@ -143,3 +145,23 @@ cdef public void DisplayHandler_OnLoadingProgressChange( except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) + +cdef public cpp_bool DisplayHandler_OnCursorChange( + CefRefPtr[CefBrowser] cefBrowser, + CefCursorHandle cursor + ) except * with gil: + cdef PyBrowser pyBrowser + try: + pyBrowser = GetPyBrowser(cefBrowser, "OnCursorChange") + callback = pyBrowser.GetClientCallback("OnCursorChange") + if callback: + ret = callback(browser=pyBrowser, cursor=cursor) + if ret: + return True + else: + return False + else: + return False + except: + (exc_type, exc_value, exc_trace) = sys.exc_info() + sys.excepthook(exc_type, exc_value, exc_trace) diff --git a/src/handlers/focus_handler.pyx b/src/handlers/focus_handler.pyx index 71c04523a..a3cb1f8f0 100644 --- a/src/handlers/focus_handler.pyx +++ b/src/handlers/focus_handler.pyx @@ -15,7 +15,7 @@ FOCUS_SOURCE_SYSTEM = cef_types.FOCUS_SOURCE_SYSTEM cdef public void FocusHandler_OnTakeFocus( CefRefPtr[CefBrowser] cef_browser, cpp_bool next_ - ) except * with gil: + ) noexcept with gil: cdef PyBrowser browser try: assert IsThread(TID_UI), "Must be called on the UI thread" @@ -50,7 +50,7 @@ cdef public cpp_bool FocusHandler_OnSetFocus( cdef public void FocusHandler_OnGotFocus( CefRefPtr[CefBrowser] cef_browser - ) except * with gil: + ) noexcept with gil: cdef PyBrowser browser try: assert IsThread(TID_UI), "Must be called on the UI thread" diff --git a/src/handlers/javascript_dialog_handler.pyx b/src/handlers/javascript_dialog_handler.pyx index 283aad54c..a86a04d7f 100644 --- a/src/handlers/javascript_dialog_handler.pyx +++ b/src/handlers/javascript_dialog_handler.pyx @@ -23,7 +23,7 @@ cdef PyJavascriptDialogCallback CreatePyJavascriptDialogCallback( cdef class PyJavascriptDialogCallback: cdef CefRefPtr[CefJSDialogCallback] cefCallback - cpdef py_void Continue(self, py_bool allow, py_string user_input): + cpdef py_void Continue(self, py_bool allow, object user_input): self.cefCallback.get().Continue(bool(allow), PyToCefStringValue(user_input)) # ----------------------------------------------------------------------------- @@ -40,9 +40,9 @@ cdef public cpp_bool JavascriptDialogHandler_OnJavascriptDialog( cpp_bool& suppress_message ) except * with gil: cdef PyBrowser pyBrowser - cdef py_string pyOriginUrl - cdef py_string pyMessageText - cdef py_string pyDefaultPromptText + cdef object pyOriginUrl + cdef object pyMessageText + cdef object pyDefaultPromptText cdef PyJavascriptDialogCallback pyCallback cdef list pySuppressMessage = [] @@ -80,7 +80,7 @@ cdef public cpp_bool JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( CefRefPtr[CefJSDialogCallback] callback ) except * with gil: cdef PyBrowser pyBrowser - cdef py_string pyMessageText + cdef object pyMessageText cdef py_bool pyIsReload cdef PyJavascriptDialogCallback pyCallback @@ -109,7 +109,7 @@ cdef public cpp_bool JavascriptDialogHandler_OnBeforeUnloadJavascriptDialog( cdef public void JavascriptDialogHandler_OnResetJavascriptDialogState( CefRefPtr[CefBrowser] cefBrowser - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser try: pyBrowser = GetPyBrowser(cefBrowser, @@ -124,7 +124,7 @@ cdef public void JavascriptDialogHandler_OnResetJavascriptDialogState( cdef public void JavascriptDialogHandler_OnJavascriptDialogClosed( CefRefPtr[CefBrowser] cefBrowser, - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser try: pyBrowser = GetPyBrowser(cefBrowser, diff --git a/src/handlers/lifespan_handler.pyx b/src/handlers/lifespan_handler.pyx index bfe22bd11..3fb372e3f 100644 --- a/src/handlers/lifespan_handler.pyx +++ b/src/handlers/lifespan_handler.pyx @@ -11,16 +11,16 @@ from cef_types cimport WindowOpenDisposition cimport cef_types # WindowOpenDisposition -WOD_UNKNOWN = cef_types.WOD_UNKNOWN -WOD_CURRENT_TAB = cef_types.WOD_CURRENT_TAB -WOD_SINGLETON_TAB = cef_types.WOD_SINGLETON_TAB -WOD_NEW_FOREGROUND_TAB = cef_types.WOD_NEW_FOREGROUND_TAB -WOD_NEW_BACKGROUND_TAB = cef_types.WOD_NEW_BACKGROUND_TAB -WOD_NEW_POPUP = cef_types.WOD_NEW_POPUP -WOD_NEW_WINDOW = cef_types.WOD_NEW_WINDOW -WOD_SAVE_TO_DISK = cef_types.WOD_SAVE_TO_DISK -WOD_OFF_THE_RECORD = cef_types.WOD_OFF_THE_RECORD -WOD_IGNORE_ACTION = cef_types.WOD_IGNORE_ACTION +CEF_WOD_UNKNOWN = cef_types.CEF_WOD_UNKNOWN +CEF_WOD_CURRENT_TAB = cef_types.CEF_WOD_CURRENT_TAB +CEF_WOD_SINGLETON_TAB = cef_types.CEF_WOD_SINGLETON_TAB +CEF_WOD_NEW_FOREGROUND_TAB = cef_types.CEF_WOD_NEW_FOREGROUND_TAB +CEF_WOD_NEW_BACKGROUND_TAB = cef_types.CEF_WOD_NEW_BACKGROUND_TAB +CEF_WOD_NEW_POPUP = cef_types.CEF_WOD_NEW_POPUP +CEF_WOD_NEW_WINDOW = cef_types.CEF_WOD_NEW_WINDOW +CEF_WOD_SAVE_TO_DISK = cef_types.CEF_WOD_SAVE_TO_DISK +CEF_WOD_OFF_THE_RECORD = cef_types.CEF_WOD_OFF_THE_RECORD +CEF_WOD_IGNORE_ACTION = cef_types.CEF_WOD_IGNORE_ACTION cdef public cpp_bool LifespanHandler_OnBeforePopup( @@ -34,19 +34,22 @@ cdef public cpp_bool LifespanHandler_OnBeforePopup( CefWindowInfo& windowInfo, CefRefPtr[CefClient]& client, CefBrowserSettings& settings, + CefRefPtr[CefDictionaryValue]& extra_info, cpp_bool* noJavascriptAccess ) except * with gil: # Empty place-holders: popupFeatures, client. cdef PyBrowser pyBrowser cdef PyFrame pyFrame, - cdef py_string pyTargetUrl - cdef py_string pyTargetFrameName + cdef object pyTargetUrl + cdef object pyTargetFrameName cdef list pyNoJavascriptAccess # out bool pyNoJavascriptAccess[0] cdef list pyWindowInfo cdef list pyBrowserSettings cdef object callback cdef py_bool returnValue try: + if not cefFrame.get().GetBrowser().get(): + return False # frame is being destroyed; cancel popup pyBrowser = GetPyBrowser(cefBrowser, "OnBeforePopup") pyFrame = GetPyFrame(cefFrame) pyTargetUrl = CefToPyString(targetUrl) @@ -81,7 +84,7 @@ cdef public cpp_bool LifespanHandler_OnBeforePopup( cdef public void LifespanHandler_OnAfterCreated( CefRefPtr[CefBrowser] cefBrowser - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser try: pyBrowser = GetPyBrowser(cefBrowser, "OnAfterCreated") @@ -108,7 +111,7 @@ cdef public cpp_bool LifespanHandler_DoClose( cdef public void LifespanHandler_OnBeforeClose( CefRefPtr[CefBrowser] cefBrowser - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef int browserId cdef object callback @@ -133,13 +136,13 @@ cdef public void LifespanHandler_OnBeforeClose( # GetCookieManager to implement custom cookie managers then # flushing of cookies would need to be handled manually. cefBrowser.get().GetHost().get().GetRequestContext().get() \ - .GetDefaultCookieManager( - NULL) \ - .get().FlushStore(NULL) + .GetCookieManager( + nullptr) \ + .get().FlushStore(nullptr) browserId = pyBrowser.GetIdentifier() - pyBrowser.cefBrowser.Assign(NULL) - cefBrowser.Assign(NULL) + pyBrowser.cefBrowser.Assign(nullptr) + cefBrowser.Assign(nullptr) del pyBrowser RemovePythonCallbacksForBrowser(browserId) diff --git a/src/handlers/load_handler.pyx b/src/handlers/load_handler.pyx index c7627f5cf..17363e79a 100644 --- a/src/handlers/load_handler.pyx +++ b/src/handlers/load_handler.pyx @@ -10,7 +10,7 @@ cdef public void LoadHandler_OnLoadingStateChange( cpp_bool isLoading, cpp_bool canGoBack, cpp_bool canGoForward - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef object callback try: @@ -28,11 +28,13 @@ cdef public void LoadHandler_OnLoadingStateChange( cdef public void LoadHandler_OnLoadStart( CefRefPtr[CefBrowser] cefBrowser, CefRefPtr[CefFrame] cefFrame - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef object clientCallback try: + if not cefFrame.get().GetBrowser().get(): + return pyBrowser = GetPyBrowser(cefBrowser, "OnLoadStart") pyFrame = GetPyFrame(cefFrame) clientCallback = pyBrowser.GetClientCallback("OnLoadStart") @@ -46,11 +48,13 @@ cdef public void LoadHandler_OnLoadEnd( CefRefPtr[CefBrowser] cefBrowser, CefRefPtr[CefFrame] cefFrame, int httpStatusCode - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef object clientCallback try: + if not cefFrame.get().GetBrowser().get(): + return pyBrowser = GetPyBrowser(cefBrowser, "OnLoadEnd") pyFrame = GetPyFrame(cefFrame) clientCallback = pyBrowser.GetClientCallback("OnLoadEnd") @@ -68,7 +72,7 @@ cdef public void LoadHandler_OnLoadError( cef_types.cef_errorcode_t cefErrorCode, const CefString& cefErrorText, const CefString& cefFailedUrl - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef list errorTextOut @@ -78,6 +82,8 @@ 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. + if not cefFrame.get().GetBrowser().get(): + return pyBrowser = GetPyBrowser(cefBrowser, "OnLoadError") pyFrame = GetPyFrame(cefFrame) errorTextOut = [CefToPyString(cefErrorText)] diff --git a/src/handlers/render_handler.pyx b/src/handlers/render_handler.pyx index a8ad49124..a7221e751 100644 --- a/src/handlers/render_handler.pyx +++ b/src/handlers/render_handler.pyx @@ -7,6 +7,7 @@ include "../browser.pyx" include "../string_utils.pyx" cimport cef_types +from libc.stdint cimport uint32_t from cef_types cimport CefRange # cef_paint_element_type_t, PaintElementType @@ -73,7 +74,13 @@ cdef public cpp_bool RenderHandler_GetViewRect( else: return False else: - return False + # without a default cefRect, pysdl2 example will fail + # the value is inspired by https://github.com/obsproject/obs-browser/blob/master/browser-client.cpp#L280 + cefRect.x = 0 + cefRect.y = 0 + cefRect.width = 16 + cefRect.height = 16 + return True except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) @@ -146,7 +153,7 @@ cdef public cpp_bool RenderHandler_GetScreenInfo( cdef public void RenderHandler_OnPopupShow( CefRefPtr[CefBrowser] cefBrowser, cpp_bool show - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser try: pyBrowser = GetPyBrowser(cefBrowser, "OnPopupShow") @@ -160,7 +167,7 @@ cdef public void RenderHandler_OnPopupShow( cdef public void RenderHandler_OnPopupSize( CefRefPtr[CefBrowser] cefBrowser, const CefRect& cefRect - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef list pyRect try: @@ -180,7 +187,7 @@ cdef public void RenderHandler_OnPaint( const void* cefBuffer, int width, int height - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef list pyDirtyRects = [] cdef list pyRect @@ -219,23 +226,9 @@ cdef public void RenderHandler_OnPaint( (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) -cdef public void RenderHandler_OnCursorChange( - CefRefPtr[CefBrowser] cefBrowser, - CefCursorHandle cursor - ) except * with gil: - cdef PyBrowser pyBrowser - try: - pyBrowser = GetPyBrowser(cefBrowser, "OnCursorChange") - callback = pyBrowser.GetClientCallback("OnCursorChange") - if callback: - callback(browser=pyBrowser, cursor=cursor) - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - cdef public void RenderHandler_OnScrollOffsetChanged( CefRefPtr[CefBrowser] cefBrowser - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser try: pyBrowser = GetPyBrowser(cefBrowser, "OnScrollOffsetChanged") @@ -249,7 +242,7 @@ cdef public void RenderHandler_OnScrollOffsetChanged( cdef public cpp_bool RenderHandler_StartDragging( CefRefPtr[CefBrowser] cef_browser, CefRefPtr[CefDragData] cef_drag_data, - uint32 allowed_ops, + uint32_t allowed_ops, int x, int y ) except * with gil: cdef PyBrowser browser @@ -278,8 +271,8 @@ cdef public cpp_bool RenderHandler_StartDragging( cdef public void RenderHandler_UpdateDragCursor( CefRefPtr[CefBrowser] cef_browser, - uint32 operation, - ) except * with gil: + uint32_t operation, + ) noexcept with gil: cdef PyBrowser browser try: browser = GetPyBrowser(cef_browser, "UpdateDragCursor") @@ -294,7 +287,7 @@ cdef public void RenderHandler_OnTextSelectionChanged( CefRefPtr[CefBrowser] cef_browser, const CefString& selected_text, const CefRange& selected_range - ) except * with gil: + ) noexcept with gil: cdef PyBrowser browser try: browser = GetPyBrowser(cef_browser, "OnTextSelectionChanged") diff --git a/src/handlers/request_handler.pyx b/src/handlers/request_handler.pyx index 866942bfd..eb26bcf8d 100644 --- a/src/handlers/request_handler.pyx +++ b/src/handlers/request_handler.pyx @@ -8,6 +8,7 @@ include "../cookie.pyx" # cef_termination_status_t cimport cef_types +from libc.stdint cimport int64_t TS_ABNORMAL_TERMINATION = cef_types.TS_ABNORMAL_TERMINATION TS_PROCESS_WAS_KILLED = cef_types.TS_PROCESS_WAS_KILLED TS_PROCESS_CRASHED = cef_types.TS_PROCESS_CRASHED @@ -24,7 +25,7 @@ cdef PyAuthCallback CreatePyAuthCallback( cdef class PyAuthCallback: cdef CefRefPtr[CefAuthCallback] cefCallback - cpdef py_void Continue(self, py_string username, py_string password): + cpdef py_void Continue(self, object username, object password): self.cefCallback.get().Continue( PyToCefStringValue(username), PyToCefStringValue(password)) @@ -37,16 +38,16 @@ cdef class PyAuthCallback: # ----------------------------------------------------------------------------- cdef PyRequestCallback CreatePyRequestCallback( - CefRefPtr[CefRequestCallback] cefCallback): + CefRefPtr[CefCallback] cefCallback): cdef PyRequestCallback pyCallback = PyRequestCallback() pyCallback.cefCallback = cefCallback return pyCallback cdef class PyRequestCallback: - cdef CefRefPtr[CefRequestCallback] cefCallback + cdef CefRefPtr[CefCallback] cefCallback - cpdef py_void Continue(self, py_bool allow): - self.cefCallback.get().Continue(bool(allow)) + cpdef py_void Continue(self): + self.cefCallback.get().Continue() cpdef py_void Cancel(self): self.cefCallback.get().Cancel() @@ -73,6 +74,8 @@ cdef public cpp_bool RequestHandler_OnBeforeBrowse( # browser was closed. if IsBrowserClosed(cefBrowser): return False + if not cefFrame.get().GetBrowser().get(): + return False pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeBrowse") pyFrame = GetPyFrame(cefFrame) @@ -108,6 +111,8 @@ cdef public cpp_bool RequestHandler_OnBeforeResourceLoad( # browser was closed. if IsBrowserClosed(cefBrowser): return False + if not cefFrame.get().GetBrowser().get(): + return False pyBrowser = GetPyBrowser(cefBrowser, "OnBeforeResourceLoad") pyFrame = GetPyFrame(cefFrame) @@ -130,7 +135,7 @@ cdef public CefRefPtr[CefResourceHandler] RequestHandler_GetResourceHandler( CefRefPtr[CefBrowser] cefBrowser, CefRefPtr[CefFrame] cefFrame, CefRefPtr[CefRequest] cefRequest - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef PyRequest pyRequest @@ -140,7 +145,9 @@ cdef public CefRefPtr[CefResourceHandler] RequestHandler_GetResourceHandler( # Issue #455: CefRequestHandler callbacks still executed after # browser was closed. if IsBrowserClosed(cefBrowser): - return NULL + return nullptr + if not cefFrame.get().GetBrowser().get(): + return nullptr pyBrowser = GetPyBrowser(cefBrowser, "GetResourceHandler") pyFrame = GetPyFrame(cefFrame) @@ -154,9 +161,9 @@ cdef public CefRefPtr[CefResourceHandler] RequestHandler_GetResourceHandler( if returnValue: return CreateResourceHandler(returnValue) else: - return NULL + return nullptr else: - return NULL + return nullptr except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) @@ -169,7 +176,7 @@ cdef public void RequestHandler_OnResourceRedirect( CefString& cefNewUrl, CefRefPtr[CefRequest] cefRequest, CefRefPtr[CefResponse] cefResponse - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef str pyOldUrl @@ -182,6 +189,8 @@ cdef public void RequestHandler_OnResourceRedirect( # browser was closed. if IsBrowserClosed(cefBrowser): return + if not cefFrame.get().GetBrowser().get(): + return pyBrowser = GetPyBrowser(cefBrowser, "OnResourceRedirect") pyFrame = GetPyFrame(cefFrame) @@ -232,6 +241,8 @@ cdef public cpp_bool RequestHandler_GetAuthCredentials( # browser was closed. if IsBrowserClosed(cefBrowser): return False + if not cefFrame.get().GetBrowser().get(): + return False pyBrowser = GetPyBrowser(cefBrowser, "GetAuthCredentials") pyFrame = GetPyFrame(cefFrame) @@ -279,11 +290,11 @@ cdef public cpp_bool RequestHandler_GetAuthCredentials( cdef public cpp_bool RequestHandler_OnQuotaRequest( CefRefPtr[CefBrowser] cefBrowser, const CefString& cefOriginUrl, - int64 newSize, - CefRefPtr[CefRequestCallback] cefRequestCallback + int64_t newSize, + CefRefPtr[CefCallback] cefCallback ) except * with gil: cdef PyBrowser pyBrowser - cdef py_string pyOriginUrl + cdef object pyOriginUrl cdef py_bool returnValue cdef object clientCallback try: @@ -300,7 +311,7 @@ cdef public cpp_bool RequestHandler_OnQuotaRequest( browser=pyBrowser, origin_url=pyOriginUrl, new_size=newSize, - callback=CreatePyRequestCallback(cefRequestCallback)) + callback=CreatePyRequestCallback(cefCallback)) return bool(returnValue) else: return False @@ -309,54 +320,11 @@ cdef public cpp_bool RequestHandler_OnQuotaRequest( sys.excepthook(exc_type, exc_value, exc_trace) -cdef public CefRefPtr[CefCookieManager] RequestHandler_GetCookieManager( - CefRefPtr[CefBrowser] cefBrowser, - const CefString& cefMainUrl - ) except * with gil: - # In CEF the GetCookieManager callback belongs to - # CefRequestContextHandler. - # In an exceptional case the browser parameter may be None - # due to limitation in CEF API. No workaround as of now. - cdef PyBrowser pyBrowser - cdef str pyMainUrl - cdef object clientCallback - cdef PyCookieManager returnValue - try: - # Issue #429: in some cases due to a race condition the browser - # may be NULL. - if not cefBrowser.get(): - return NULL - - # Issue #455: CefRequestHandler callbacks still executed after - # browser was closed. - if IsBrowserClosed(cefBrowser): - return NULL - - pyBrowser = GetPyBrowser(cefBrowser, "GetCookieManager") - pyMainUrl = CefToPyString(cefMainUrl) - clientCallback = pyBrowser.GetClientCallback("GetCookieManager") - if clientCallback: - returnValue = clientCallback( - browser=pyBrowser, - main_url=pyMainUrl) - if returnValue: - if isinstance(returnValue, PyCookieManager): - return returnValue.cefCookieManager - else: - raise Exception("Expected a CookieManager object") - return NULL - else: - return NULL - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - - cdef public void RequestHandler_OnProtocolExecution( CefRefPtr[CefBrowser] cefBrowser, const CefString& cefUrl, cpp_bool& cefAllowOSExecution - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef str pyUrl cdef list pyAllowOSExecutionOut @@ -388,60 +356,10 @@ cdef public void RequestHandler_OnProtocolExecution( sys.excepthook(exc_type, exc_value, exc_trace) -cdef public cpp_bool RequestHandler_OnBeforePluginLoad( - CefRefPtr[CefBrowser] browser, - const CefString& mime_type, - const CefString& plugin_url, - cpp_bool is_main_frame, - const CefString& top_origin_url, - CefRefPtr[CefWebPluginInfo] plugin_info, - cef_types.cef_plugin_policy_t* plugin_policy - ) except * with gil: - cdef PyBrowser pyBrowser - cdef PyWebPluginInfo pyInfo - 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 - - # Issue #455: CefRequestHandler callbacks still executed after - # browser was closed. - if IsBrowserClosed(browser): - return False - - py_browser = GetPyBrowser(browser, "OnBeforePluginLoad") - py_plugin_info = CreatePyWebPluginInfo(plugin_info) - clientCallback = GetGlobalClientCallback("OnBeforePluginLoad") - if clientCallback: - returnValue = clientCallback( - browser=py_browser, - mime_type=CefToPyString(mime_type), - plugin_url=CefToPyString(plugin_url), - is_main_frame=bool(is_main_frame), - top_origin_url=CefToPyString(top_origin_url), - plugin_info=py_plugin_info) - if returnValue: - plugin_policy[0] = cef_types.PLUGIN_POLICY_DISABLE - return bool(returnValue) - else: - return False - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - - cdef public cpp_bool RequestHandler_OnCertificateError( int certError, const CefString& cefRequestUrl, - CefRefPtr[CefRequestCallback] cefCertCallback + CefRefPtr[CefCallback] cefCertCallback ) except * with gil: cdef py_bool returnValue cdef object clientCallback @@ -463,7 +381,7 @@ cdef public cpp_bool RequestHandler_OnCertificateError( cdef public void RequestHandler_OnRendererProcessTerminated( CefRefPtr[CefBrowser] cefBrowser, cef_types.cef_termination_status_t cefStatus - ) except * with gil: + ) noexcept with gil: # TODO: proccess may crash during browser creation. Let this callback # to be set either through cefpython.SetGlobalClientCallback() # or PyBrowser.SetClientCallback(). Modify the @@ -485,103 +403,3 @@ cdef public void RequestHandler_OnRendererProcessTerminated( except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) - - -cdef public void RequestHandler_OnPluginCrashed( - CefRefPtr[CefBrowser] cefBrowser, - const CefString& cefPluginPath - ) except * with gil: - # TODO: plugin may crash during browser creation. Let this callback - # to be set either through cefpython.SetGlobalClientCallback() - # or PyBrowser.SetClientCallback(). Modify the - # PyBrowser.GetClientCallback() implementation to return a global - # callback first if set. - cdef PyBrowser pyBrowser - cdef object clientCallback - try: - # Issue #455: CefRequestHandler callbacks still executed after - # browser was closed. - if IsBrowserClosed(cefBrowser): - return - - pyBrowser = GetPyBrowser(cefBrowser, "OnPluginCrashed") - clientCallback = pyBrowser.GetClientCallback("OnPluginCrashed") - if clientCallback: - clientCallback( - browser=pyBrowser, - plugin_path=CefToPyString(cefPluginPath)) - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - - -cdef public cpp_bool RequestHandler_CanGetCookies( - CefRefPtr[CefBrowser] cef_browser, - CefRefPtr[CefFrame] cef_frame, - CefRefPtr[CefRequest] cef_request - ) except * with gil: - cdef PyBrowser browser - cdef PyFrame frame - cdef PyRequest request - cdef object callback - cdef py_bool retval - try: - # Issue #455: CefRequestHandler callbacks still executed after - # browser was closed. - if IsBrowserClosed(cef_browser): - return False - - browser = GetPyBrowser(cef_browser, "CanGetCookies") - frame = GetPyFrame(cef_frame) - request = CreatePyRequest(cef_request) - callback = browser.GetClientCallback("CanGetCookies") - if callback: - retval = callback( - browser=browser, - frame=frame, - request=request) - return bool(retval) - else: - # Return True by default - return True - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - - -cdef public cpp_bool RequestHandler_CanSetCookie( - CefRefPtr[CefBrowser] cef_browser, - CefRefPtr[CefFrame] cef_frame, - CefRefPtr[CefRequest] cef_request, - const CefCookie& cef_cookie - ) except * with gil: - cdef PyBrowser browser - cdef PyFrame frame - cdef PyRequest request - cdef PyCookie cookie - cdef object callback - cdef py_bool retval - try: - # Issue #455: CefRequestHandler callbacks still executed after - # browser was closed. - if IsBrowserClosed(cef_browser): - return False - - browser = GetPyBrowser(cef_browser, "CanSetCookie") - frame = GetPyFrame(cef_frame) - request = CreatePyRequest(cef_request) - cookie = CreatePyCookie(cef_cookie) - callback = browser.GetClientCallback("CanSetCookie") - if callback: - retval = callback( - browser=browser, - frame=frame, - request=request, - cookie=cookie) - return bool(retval) - else: - # Return True by default - return True - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) diff --git a/src/handlers/resource_handler.pyx b/src/handlers/resource_handler.pyx index 1c379f192..1fc2c9df7 100644 --- a/src/handlers/resource_handler.pyx +++ b/src/handlers/resource_handler.pyx @@ -3,7 +3,7 @@ # Project website: https://github.com/cztomczak/cefpython include "../cefpython.pyx" - +from libc.stdint cimport int64_t import weakref # ----------------------------------------------------------------------------- @@ -21,7 +21,7 @@ cdef int g_userResourceHandlerMaxId = 0 cdef py_void ValidateUserResourceHandler(object userResourceHandler): cdef list methods = ["ProcessRequest", "GetResponseHeaders", - "ReadResponse", "CanGetCookie", "CanSetCookie", "Cancel"] + "ReadResponse", "Cancel"] for method in methods: if userResourceHandler and hasattr(userResourceHandler, method)\ and callable(getattr(userResourceHandler, method)): @@ -102,9 +102,9 @@ cdef public cpp_bool ResourceHandler_ProcessRequest( cdef public void ResourceHandler_GetResponseHeaders( int resourceHandlerId, CefRefPtr[CefResponse] cefResponse, - int64& cefResponseLength, + int64_t& cefResponseLength, CefString& cefRedirectUrl - ) except * with gil: + ) noexcept with gil: cdef PyResourceHandler pyResourceHandler cdef object userCallback cdef py_bool returnValue @@ -122,7 +122,7 @@ cdef public void ResourceHandler_GetResponseHeaders( if userCallback: returnValue = userCallback(pyResponse, responseLengthOut, redirectUrlOut) - (&cefResponseLength)[0] = responseLengthOut[0] + (&cefResponseLength)[0] = responseLengthOut[0] if redirectUrlOut[0]: PyToCefString(redirectUrlOut[0], cefRedirectUrl) return @@ -182,53 +182,9 @@ cdef public cpp_bool ResourceHandler_ReadResponse( (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) -cdef public cpp_bool ResourceHandler_CanGetCookie( - int resourceHandlerId, - const CefCookie& cefCookie - ) except * with gil: - cdef PyResourceHandler pyResourceHandler - cdef object userCallback - cdef py_bool returnValue - cdef PyCookie pyCookie - try: - assert IsThread(TID_IO), "Must be called on the IO thread" - pyResourceHandler = GetPyResourceHandler(resourceHandlerId) - pyCookie = CreatePyCookie(cefCookie) - if pyResourceHandler: - userCallback = pyResourceHandler.GetCallback("CanGetCookie") - if userCallback: - returnValue = userCallback(cookie=pyCookie) - return bool(returnValue) - return False - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - -cdef public cpp_bool ResourceHandler_CanSetCookie( - int resourceHandlerId, - const CefCookie& cefCookie - ) except * with gil: - cdef PyResourceHandler pyResourceHandler - cdef object userCallback - cdef py_bool returnValue - cdef PyCookie pyCookie - try: - assert IsThread(TID_IO), "Must be called on the IO thread" - pyResourceHandler = GetPyResourceHandler(resourceHandlerId) - pyCookie = CreatePyCookie(cefCookie) - if pyResourceHandler: - userCallback = pyResourceHandler.GetCallback("CanSetCookie") - if userCallback: - returnValue = userCallback(cookie=pyCookie) - return bool(returnValue) - return False - except: - (exc_type, exc_value, exc_trace) = sys.exc_info() - sys.excepthook(exc_type, exc_value, exc_trace) - cdef public void ResourceHandler_Cancel( int resourceHandlerId - ) except * with gil: + ) noexcept with gil: cdef PyResourceHandler pyResourceHandler cdef object userCallback try: diff --git a/src/handlers/v8context_handler.pyx b/src/handlers/v8context_handler.pyx index fccdad562..0de8954fb 100644 --- a/src/handlers/v8context_handler.pyx +++ b/src/handlers/v8context_handler.pyx @@ -11,19 +11,29 @@ include "../cefpython.pyx" include "../browser.pyx" include "../frame.pyx" +from libc.stdint cimport int64_t cdef public void V8ContextHandler_OnContextCreated( CefRefPtr[CefBrowser] cefBrowser, CefRefPtr[CefFrame] cefFrame - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef object clientCallback + cdef JavascriptBindings jsBindings try: - Debug("V8ContextHandler_OnContextCreated()") + if not cefFrame.get().GetBrowser().get(): + return pyBrowser = GetPyBrowser(cefBrowser, "OnContextCreated") pyBrowser.SetUserData("__v8ContextCreated", True) pyFrame = GetPyFrame(cefFrame) + # Re-send bindings before user callback so that any ExecuteJavascript + # the user sends from OnContextCreated has globals already registered + # when it arrives at the renderer (both travel the same IPC channel). + if pyFrame.IsMain(): + jsBindings = pyBrowser.GetJavascriptBindings() + if jsBindings: + jsBindings.Rebind() # User defined callback clientCallback = pyBrowser.GetClientCallback("OnContextCreated") if clientCallback: @@ -34,8 +44,8 @@ cdef public void V8ContextHandler_OnContextCreated( cdef public void V8ContextHandler_OnContextReleased( int browserId, - int64 frameId - ) except * with gil: + CefString frameId + ) noexcept with gil: cdef PyBrowser pyBrowser cdef PyFrame pyFrame cdef object clientCallback @@ -53,16 +63,16 @@ cdef public void V8ContextHandler_OnContextReleased( if not pyBrowser: Debug("OnContextReleased: Browser doesn't exist anymore, id={id}" .format(id=str(browserId))) - RemovePyFrame(browserId, frameId) + RemovePyFrame(browserId, CefToPyString(frameId)) return - pyFrame = GetPyFrameById(browserId, frameId) + pyFrame = GetPyFrameById(browserId, CefToPyString(frameId)) # Frame may already be destroyed while IPC messaging was executing # (Issue #431). if pyFrame: clientCallback = pyBrowser.GetClientCallback("OnContextReleased") if clientCallback: clientCallback(browser=pyBrowser, frame=pyFrame) - RemovePyFrame(browserId, frameId) + RemovePyFrame(browserId, CefToPyString(frameId)) except: (exc_type, exc_value, exc_trace) = sys.exc_info() sys.excepthook(exc_type, exc_value, exc_trace) diff --git a/src/handlers/v8function_handler.pyx b/src/handlers/v8function_handler.pyx index ee73619f8..0bcdffd2a 100644 --- a/src/handlers/v8function_handler.pyx +++ b/src/handlers/v8function_handler.pyx @@ -8,21 +8,21 @@ include "../frame.pyx" cdef public void V8FunctionHandler_Execute( CefRefPtr[CefBrowser] cefBrowser, - int64 frameId, + CefString& frameId, CefString& cefFuncName, CefRefPtr[CefListValue] cefFuncArgs - ) except * with gil: + ) noexcept with gil: cdef PyBrowser pyBrowser cdef CefRefPtr[CefFrame] cefFrame cdef PyFrame pyFrame # may be None - cdef py_string funcName + cdef object funcName cdef object func cdef list funcArgs cdef object returnValue - cdef py_string errorMessage + cdef object errorMessage try: pyBrowser = GetPyBrowser(cefBrowser, "V8FunctionHandler_Execute") - cefFrame = cefBrowser.get().GetFrame(frameId) + cefFrame = cefBrowser.get().GetFrameByName(frameId) if cefFrame.get(): pyFrame = GetPyFrame(cefFrame) else: diff --git a/src/include/base/cef_atomic_flag.h b/src/include/base/cef_atomic_flag.h new file mode 100644 index 000000000..3a2fdbc1d --- /dev/null +++ b/src/include/base/cef_atomic_flag.h @@ -0,0 +1,97 @@ +// Copyright (c) 2014 Marshall A. Greenblatt. Portions copyright (c) 2011 +// Google Inc. All rights reserved. +// +// 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 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. + +#ifndef CEF_INCLUDE_BASE_CEF_ATOMIC_FLAG_H_ +#define CEF_INCLUDE_BASE_CEF_ATOMIC_FLAG_H_ +#pragma once + +#if defined(USING_CHROMIUM_INCLUDES) +// When building CEF include the Chromium header directly. +#include "base/synchronization/atomic_flag.h" + +#else // !USING_CHROMIUM_INCLUDES +// The following is substantially similar to the Chromium implementation. +// If the Chromium implementation diverges the below implementation should be +// updated to match. + +#include + +#include + +#include "include/base/cef_thread_checker.h" + +namespace base { + +/// +/// A flag that can safely be set from one thread and read from other threads. +/// +/// This class IS NOT intended for synchronization between threads. +/// +class AtomicFlag { + public: + AtomicFlag(); + + AtomicFlag(const AtomicFlag&) = delete; + AtomicFlag& operator=(const AtomicFlag&) = delete; + + ~AtomicFlag(); + + /// + /// Set the flag. Must always be called from the same thread. + /// + void Set(); + + /// + /// Returns true iff the flag was set. If this returns true, the current + /// thread is guaranteed to be synchronized with all memory operations on the + /// thread which invoked Set() up until at least the first call to Set() on + /// it. + /// + bool IsSet() const { + // Inline here: this has a measurable performance impact on base::WeakPtr. + return flag_.load(std::memory_order_acquire) != 0; + } + + /// + /// Resets the flag. Be careful when using this: callers might not expect + /// IsSet() to return false after returning true once. + /// + void UnsafeResetForTesting(); + + private: + std::atomic flag_{0}; + base::ThreadChecker set_thread_checker_; +}; + +} // namespace base + +#endif // !USING_CHROMIUM_INCLUDES + +#endif // CEF_INCLUDE_BASE_CEF_ATOMIC_FLAG_H_ diff --git a/src/include/base/cef_atomic_ref_count.h b/src/include/base/cef_atomic_ref_count.h index 4d6777970..38e8f93b7 100644 --- a/src/include/base/cef_atomic_ref_count.h +++ b/src/include/base/cef_atomic_ref_count.h @@ -43,120 +43,78 @@ // When building CEF include the Chromium header directly. #include "base/atomic_ref_count.h" -// Used when declaring a base::AtomicRefCount value. This is an object type with -// Chromium headers. -#define ATOMIC_DECLARATION (0) - -// Maintaining compatibility with AtompicRefCount* functions that were removed -// from Chromium in http://crrev.com/ee96d561. -namespace base { - -// Increment a reference count by 1. -inline void AtomicRefCountInc(volatile AtomicRefCount* ptr) { - const_cast(ptr)->Increment(); -} - -// Decrement a reference count by 1 and return whether the result is non-zero. -// Insert barriers to ensure that state written before the reference count -// became zero will be visible to a thread that has just made the count zero. -inline bool AtomicRefCountDec(volatile AtomicRefCount* ptr) { - return const_cast(ptr)->Decrement(); -} - -// Return whether the reference count is one. If the reference count is used -// in the conventional way, a refrerence count of 1 implies that the current -// thread owns the reference and no other thread shares it. This call performs -// the test for a reference count of one, and performs the memory barrier -// needed for the owning thread to act on the object, knowing that it has -// exclusive access to the object. -inline bool AtomicRefCountIsOne(volatile AtomicRefCount* ptr) { - return const_cast(ptr)->IsOne(); -} - -// Return whether the reference count is zero. With conventional object -// referencing counting, the object will be destroyed, so the reference count -// should never be zero. Hence this is generally used for a debug check. -inline bool AtomicRefCountIsZero(volatile AtomicRefCount* ptr) { - return const_cast(ptr)->IsZero(); -} - -} // namespace base - #else // !USING_CHROMIUM_INCLUDES // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be // updated to match. -#include "include/base/cef_atomicops.h" - -// Annotations are not currently supported. -#define ANNOTATE_HAPPENS_BEFORE(obj) /* empty */ -#define ANNOTATE_HAPPENS_AFTER(obj) /* empty */ - -// Used when declaring a base::AtomicRefCount value. This is an integer/ptr type -// with CEF headers. -#define ATOMIC_DECLARATION = 0 +#include namespace base { -typedef subtle::Atomic32 AtomicRefCount; - -// Increment a reference count by "increment", which must exceed 0. -inline void AtomicRefCountIncN(volatile AtomicRefCount* ptr, - AtomicRefCount increment) { - subtle::NoBarrier_AtomicIncrement(ptr, increment); -} - -// Decrement a reference count by "decrement", which must exceed 0, -// and return whether the result is non-zero. -// Insert barriers to ensure that state written before the reference count -// became zero will be visible to a thread that has just made the count zero. -inline bool AtomicRefCountDecN(volatile AtomicRefCount* ptr, - AtomicRefCount decrement) { - ANNOTATE_HAPPENS_BEFORE(ptr); - bool res = (subtle::Barrier_AtomicIncrement(ptr, -decrement) != 0); - if (!res) { - ANNOTATE_HAPPENS_AFTER(ptr); +class AtomicRefCount { + public: + constexpr AtomicRefCount() : ref_count_(0) {} + explicit constexpr AtomicRefCount(int initial_value) + : ref_count_(initial_value) {} + + /// + /// Increment a reference count. + /// Returns the previous value of the count. + /// + int Increment() { return Increment(1); } + + /// + /// Increment a reference count by "increment", which must exceed 0. + /// Returns the previous value of the count. + /// + int Increment(int increment) { + return ref_count_.fetch_add(increment, std::memory_order_relaxed); + } + + /// + /// Decrement a reference count, and return whether the result is non-zero. + /// Insert barriers to ensure that state written before the reference count + /// became zero will be visible to a thread that has just made the count zero. + /// + bool Decrement() { + // TODO(jbroman): Technically this doesn't need to be an acquire operation + // unless the result is 1 (i.e., the ref count did indeed reach zero). + // However, there are toolchain issues that make that not work as well at + // present (notably TSAN doesn't like it). + return ref_count_.fetch_sub(1, std::memory_order_acq_rel) != 1; } - return res; -} - -// Increment a reference count by 1. -inline void AtomicRefCountInc(volatile AtomicRefCount* ptr) { - base::AtomicRefCountIncN(ptr, 1); -} - -// Decrement a reference count by 1 and return whether the result is non-zero. -// Insert barriers to ensure that state written before the reference count -// became zero will be visible to a thread that has just made the count zero. -inline bool AtomicRefCountDec(volatile AtomicRefCount* ptr) { - return base::AtomicRefCountDecN(ptr, 1); -} - -// Return whether the reference count is one. If the reference count is used -// in the conventional way, a refrerence count of 1 implies that the current -// thread owns the reference and no other thread shares it. This call performs -// the test for a reference count of one, and performs the memory barrier -// needed for the owning thread to act on the object, knowing that it has -// exclusive access to the object. -inline bool AtomicRefCountIsOne(volatile AtomicRefCount* ptr) { - bool res = (subtle::Acquire_Load(ptr) == 1); - if (res) { - ANNOTATE_HAPPENS_AFTER(ptr); + + /// + /// Return whether the reference count is one. If the reference count is used + /// in the conventional way, a refrerence count of 1 implies that the current + /// thread owns the reference and no other thread shares it. This call + /// performs the test for a reference count of one, and performs the memory + /// barrier needed for the owning thread to act on the object, knowing that it + /// has exclusive access to the object. + /// + bool IsOne() const { return ref_count_.load(std::memory_order_acquire) == 1; } + + /// + /// Return whether the reference count is zero. With conventional object + /// referencing counting, the object will be destroyed, so the reference count + /// should never be zero. Hence this is generally used for a debug check. + /// + bool IsZero() const { + return ref_count_.load(std::memory_order_acquire) == 0; } - return res; -} - -// Return whether the reference count is zero. With conventional object -// referencing counting, the object will be destroyed, so the reference count -// should never be zero. Hence this is generally used for a debug check. -inline bool AtomicRefCountIsZero(volatile AtomicRefCount* ptr) { - bool res = (subtle::Acquire_Load(ptr) == 0); - if (res) { - ANNOTATE_HAPPENS_AFTER(ptr); + + /// + /// Returns the current reference count (with no barriers). This is subtle, + /// and should be used only for debugging. + /// + int SubtleRefCountForDebug() const { + return ref_count_.load(std::memory_order_relaxed); } - return res; -} + + private: + std::atomic_int ref_count_; +}; } // namespace base diff --git a/src/include/base/cef_atomicops.h b/src/include/base/cef_atomicops.h deleted file mode 100644 index 96aebabf8..000000000 --- a/src/include/base/cef_atomicops.h +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2014 Marshall A. Greenblatt. Portions copyright (c) 2012 -// Google Inc. All rights reserved. -// -// 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 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. - -// For atomic operations on reference counts, see cef_atomic_ref_count.h. - -// The routines exported by this module are subtle. If you use them, even if -// you get the code right, it will depend on careful reasoning about atomicity -// and memory ordering; it will be less readable, and harder to maintain. If -// you plan to use these routines, you should have a good reason, such as solid -// evidence that performance would otherwise suffer, or there being no -// alternative. You should assume only properties explicitly guaranteed by the -// specifications in this file. You are almost certainly _not_ writing code -// just for the x86; if you assume x86 semantics, x86 hardware bugs and -// implementations on other archtectures will cause your code to break. If you -// do not know what you are doing, avoid these routines, and use a Mutex. -// -// It is incorrect to make direct assignments to/from an atomic variable. -// You should use one of the Load or Store routines. The NoBarrier -// versions are provided when no barriers are needed: -// NoBarrier_Store() -// NoBarrier_Load() -// Although there are currently no compiler enforcement, you are encouraged -// to use these. -// - -#ifndef CEF_INCLUDE_BASE_CEF_ATOMICOPS_H_ -#define CEF_INCLUDE_BASE_CEF_ATOMICOPS_H_ -#pragma once - -#if defined(BASE_ATOMICOPS_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) -// When building CEF include the Chromium header directly. -#include "base/atomicops.h" -#else // !USING_CHROMIUM_INCLUDES -// The following is substantially similar to the Chromium implementation. -// If the Chromium implementation diverges the below implementation should be -// updated to match. - -#include - -#include "include/base/cef_build.h" - -#if defined(OS_WIN) && defined(ARCH_CPU_64_BITS) -// windows.h #defines this (only on x64). This causes problems because the -// public API also uses MemoryBarrier at the public name for this fence. So, on -// X64, undef it, and call its documented -// (http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208.aspx) -// implementation directly. -#undef MemoryBarrier -#endif - -namespace base { -namespace subtle { - -typedef int32_t Atomic32; -#ifdef ARCH_CPU_64_BITS -// We need to be able to go between Atomic64 and AtomicWord implicitly. This -// means Atomic64 and AtomicWord should be the same type on 64-bit. -#if defined(__ILP32__) || defined(OS_NACL) -// NaCl's intptr_t is not actually 64-bits on 64-bit! -// http://code.google.com/p/nativeclient/issues/detail?id=1162 -typedef int64_t Atomic64; -#else -typedef intptr_t Atomic64; -#endif -#endif - -// Use AtomicWord for a machine-sized pointer. It will use the Atomic32 or -// Atomic64 routines below, depending on your architecture. -typedef intptr_t AtomicWord; - -// Atomically execute: -// result = *ptr; -// if (*ptr == old_value) -// *ptr = new_value; -// return result; -// -// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". -// Always return the old value of "*ptr" -// -// This routine implies no memory barriers. -Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value); - -// Atomically store new_value into *ptr, returning the previous value held in -// *ptr. This routine implies no memory barriers. -Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, Atomic32 new_value); - -// Atomically increment *ptr by "increment". Returns the new value of -// *ptr with the increment applied. This routine implies no memory barriers. -Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment); - -Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment); - -// These following lower-level operations are typically useful only to people -// implementing higher-level synchronization operations like spinlocks, -// mutexes, and condition-variables. They combine CompareAndSwap(), a load, or -// a store with appropriate memory-ordering instructions. "Acquire" operations -// ensure that no later memory access can be reordered ahead of the operation. -// "Release" operations ensure that no previous memory access can be reordered -// after the operation. "Barrier" operations have both "Acquire" and "Release" -// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory -// access. -Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value); -Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, - Atomic32 old_value, - Atomic32 new_value); - -void MemoryBarrier(); -void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value); -void Acquire_Store(volatile Atomic32* ptr, Atomic32 value); -void Release_Store(volatile Atomic32* ptr, Atomic32 value); - -Atomic32 NoBarrier_Load(volatile const Atomic32* ptr); -Atomic32 Acquire_Load(volatile const Atomic32* ptr); -Atomic32 Release_Load(volatile const Atomic32* ptr); - -// 64-bit atomic operations (only available on 64-bit processors). -#ifdef ARCH_CPU_64_BITS -Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value); -Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, Atomic64 new_value); -Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); -Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); - -Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value); -Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, - Atomic64 old_value, - Atomic64 new_value); -void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value); -void Acquire_Store(volatile Atomic64* ptr, Atomic64 value); -void Release_Store(volatile Atomic64* ptr, Atomic64 value); -Atomic64 NoBarrier_Load(volatile const Atomic64* ptr); -Atomic64 Acquire_Load(volatile const Atomic64* ptr); -Atomic64 Release_Load(volatile const Atomic64* ptr); -#endif // ARCH_CPU_64_BITS - -} // namespace subtle -} // namespace base - -// Include our platform specific implementation. -#if defined(OS_WIN) && defined(COMPILER_MSVC) && defined(ARCH_CPU_X86_FAMILY) -#include "include/base/internal/cef_atomicops_x86_msvc.h" -#elif defined(OS_MACOSX) -#include "include/base/internal/cef_atomicops_mac.h" -#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY) -#include "include/base/internal/cef_atomicops_x86_gcc.h" -#elif defined(COMPILER_GCC) && defined(__ARM_ARCH) -#include "include/base/internal/cef_atomicops_arm_gcc.h" -#else -#error "Atomic operations are not supported on your platform" -#endif - -// On some platforms we need additional declarations to make -// AtomicWord compatible with our other Atomic* types. -#if defined(OS_MACOSX) || defined(OS_OPENBSD) -#include "include/base/internal/cef_atomicops_atomicword_compat.h" -#endif - -#endif // !USING_CHROMIUM_INCLUDES - -#endif // CEF_INCLUDE_BASE_CEF_ATOMICOPS_H_ diff --git a/src/include/base/cef_auto_reset.h b/src/include/base/cef_auto_reset.h new file mode 100644 index 000000000..be3a05da5 --- /dev/null +++ b/src/include/base/cef_auto_reset.h @@ -0,0 +1,90 @@ +// Copyright (c) 2014 Marshall A. Greenblatt. Portions copyright (c) 2011 +// Google Inc. All rights reserved. +// +// 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 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. + +// base::AutoReset<> is useful for setting a variable to a new value only within +// a particular scope. An base::AutoReset<> object resets a variable to its +// original value upon destruction, making it an alternative to writing +// "var = false;" or "var = old_val;" at all of a block's exit points. +// +// This should be obvious, but note that an base::AutoReset<> instance should +// have a shorter lifetime than its scoped_variable, to prevent invalid memory +// writes when the base::AutoReset<> object is destroyed. + +#ifndef CEF_INCLUDE_BASE_CEF_AUTO_RESET_H_ +#define CEF_INCLUDE_BASE_CEF_AUTO_RESET_H_ +#pragma once + +#if defined(USING_CHROMIUM_INCLUDES) +// When building CEF include the Chromium header directly. +#include "base/auto_reset.h" +#else // !USING_CHROMIUM_INCLUDES +// The following is substantially similar to the Chromium implementation. +// If the Chromium implementation diverges the below implementation should be +// updated to match. + +#include + +namespace base { + +template +class AutoReset { + public: + template + AutoReset(T* scoped_variable, U&& new_value) + : scoped_variable_(scoped_variable), + original_value_( + std::exchange(*scoped_variable_, std::forward(new_value))) {} + + AutoReset(AutoReset&& other) + : scoped_variable_(std::exchange(other.scoped_variable_, nullptr)), + original_value_(std::move(other.original_value_)) {} + + AutoReset& operator=(AutoReset&& rhs) { + scoped_variable_ = std::exchange(rhs.scoped_variable_, nullptr); + original_value_ = std::move(rhs.original_value_); + return *this; + } + + ~AutoReset() { + if (scoped_variable_) { + *scoped_variable_ = std::move(original_value_); + } + } + + private: + T* scoped_variable_; + T original_value_; +}; + +} // namespace base + +#endif // !USING_CHROMIUM_INCLUDES + +#endif // CEF_INCLUDE_BASE_CEF_AUTO_RESET_H_ diff --git a/src/include/base/cef_bind.h b/src/include/base/cef_bind.h index 77c9c5573..0d92904de 100644 --- a/src/include/base/cef_bind.h +++ b/src/include/base/cef_bind.h @@ -28,544 +28,317 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// +/// \file +/// base::BindOnce() and base::BindRepeating() are helpers for creating +/// base::OnceCallback and base::RepeatingCallback objects respectively. +/// +/// For a runnable object of n-arity, the base::Bind*() family allows partial +/// application of the first m arguments. The remaining n - m arguments must be +/// passed when invoking the callback with Run(). +/// +///
+///   // The first argument is bound at callback creation; the remaining
+///   // two must be passed when calling Run() on the callback object.
+///   base::OnceCallback cb = base::BindOnce(
+///       [](short x, int y, long z) { return x * y * z; }, 42);
+/// 
+/// +/// When binding to a method, the receiver object must also be specified at +/// callback creation time. When Run() is invoked, the method will be invoked on +/// the specified receiver object. +/// +///
+///   class C : public base::RefCounted { void F(); };
+///   auto instance = base::MakeRefCounted();
+///   auto cb = base::BindOnce(&C::F, instance);
+///   std::move(cb).Run();  // Identical to instance->F()
+/// 
+/// +/// See https://chromium.googlesource.com/chromium/src/+/lkgr/docs/callback.md +/// for the full documentation. +/// + #ifndef CEF_INCLUDE_BASE_CEF_BIND_H_ #define CEF_INCLUDE_BASE_CEF_BIND_H_ #pragma once -#if defined(BASE_BIND_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) +#if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. -#include "base/bind.h" +#include "base/functional/bind.h" #else // !USING_CHROMIUM_INCLUDES // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be // updated to match. +#include +#include +#include +#include + +#include "include/base/cef_build.h" +#include "include/base/cef_compiler_specific.h" #include "include/base/internal/cef_bind_internal.h" -#include "include/base/internal/cef_callback_internal.h" -// ----------------------------------------------------------------------------- -// Usage documentation -// ----------------------------------------------------------------------------- -// -// See base/cef_callback.h for documentation. -// -// -// ----------------------------------------------------------------------------- -// Implementation notes -// ----------------------------------------------------------------------------- -// +// Implementation notes: // If you're reading the implementation, before proceeding further, you should -// read the top comment of base/bind_internal.h for a definition of common -// terms and concepts. -// -// RETURN TYPES -// -// Though Bind()'s result is meant to be stored in a Callback<> type, it -// cannot actually return the exact type without requiring a large amount -// of extra template specializations. The problem is that in order to -// discern the correct specialization of Callback<>, Bind would need to -// unwrap the function signature to determine the signature's arity, and -// whether or not it is a method. -// -// Each unique combination of (arity, function_type, num_prebound) where -// function_type is one of {function, method, const_method} would require -// one specialization. We eventually have to do a similar number of -// specializations anyways in the implementation (see the Invoker<>, -// classes). However, it is avoidable in Bind if we return the result -// via an indirection like we do below. -// -// TODO(ajwong): We might be able to avoid this now, but need to test. -// -// It is possible to move most of the COMPILE_ASSERT asserts into BindState<>, -// but it feels a little nicer to have the asserts here so people do not -// need to crack open bind_internal.h. On the other hand, it makes Bind() -// harder to read. +// read the top comment of include/base/internal/cef_bind_internal.h for a +// definition of common terms and concepts. namespace base { -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void()>::UnboundRunType> -Bind(Functor functor) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - typedef cef_internal::BindState BindState; - - return Callback( - new BindState(cef_internal::MakeRunnable(functor))); +/// +/// Bind as OnceCallback. +/// +template +inline auto BindOnce(Functor&& functor, Args&&... args) { + return cef_internal::BindHelper::Bind( + std::forward(functor), std::forward(args)...); } -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, const P1& p1) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); - - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType)> - BindState; - - return Callback( - new BindState(cef_internal::MakeRunnable(functor), p1)); +/// +/// Bind as RepeatingCallback. +/// +template +inline auto BindRepeating(Functor&& functor, Args&&... args) { + return cef_internal::BindHelper::Bind( + std::forward(functor), std::forward(args)...); } -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, const P1& p1, const P2& p2) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value || - is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); - - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p2_is_refcounted_type_and_needs_scoped_refptr); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)> - BindState; - - return Callback( - new BindState(cef_internal::MakeRunnable(functor), p1, p2)); +// Overloads to allow nicer compile errors when attempting to pass the address +// an overloaded function to `BindOnce()` or `BindRepeating()`. Otherwise, clang +// provides only the error message "no matching function [...] candidate +// template ignored: couldn't infer template argument 'Functor'", with no +// reference to the fact that `&` is being used on an overloaded function. +// +// These overloads to provide better error messages will never be selected +// unless template type deduction fails because of how overload resolution +// works; per [over.ics.rank/2.2]: +// +// When comparing the basic forms of implicit conversion sequences (as defined +// in [over.best.ics]) +// - a standard conversion sequence is a better conversion sequence than a +// user-defined conversion sequence or an ellipsis conversion sequence, and +// - a user-defined conversion sequence is a better conversion sequence than +// an ellipsis conversion sequence. +// +// So these overloads will only be selected as a last resort iff template type +// deduction fails. +BindFailedCheckPreviousErrors BindOnce(...); +BindFailedCheckPreviousErrors BindRepeating(...); + +/// +/// Unretained() allows binding a non-refcounted class, and to disable +/// refcounting on arguments that are refcounted. +/// +/// CEF simplified: no dangling pointer detection (UnsafeDangling, +/// UnsafeDanglingUntriaged removed). +/// +/// Example of Unretained() usage: +/// +///
+///   class Foo {
+///    public:
+///     void func() { cout << "Foo:f" << endl; }
+///   };
+///
+///   // In some function somewhere.
+///   Foo foo;
+///   OnceClosure foo_callback =
+///       BindOnce(&Foo::func, Unretained(&foo));
+///   std::move(foo_callback).Run();  // Prints "Foo:f".
+/// 
+/// +/// Without the Unretained() wrapper on |&foo|, the above call would fail +/// to compile because Foo does not support the AddRef() and Release() methods. +/// +template +inline cef_internal::UnretainedWrapper Unretained(T* o) { + return cef_internal::UnretainedWrapper(o); } -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); - - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p2_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p3_is_refcounted_type_and_needs_scoped_refptr); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)> - BindState; - - return Callback( - new BindState(cef_internal::MakeRunnable(functor), p1, p2, p3)); +/// +/// RetainedRef() accepts a ref counted object and retains a reference to it. +/// When the callback is called, the object is passed as a raw pointer. +/// +/// EXAMPLE OF RetainedRef(): +/// +///
+///    void foo(RefCountedBytes* bytes) {}
+///
+///    scoped_refptr bytes = ...;
+///    OnceClosure callback = BindOnce(&foo, base::RetainedRef(bytes));
+///    std::move(callback).Run();
+/// 
+/// +/// Without RetainedRef, the scoped_refptr would try to implicitly convert to +/// a raw pointer and fail compilation: +/// +///
+///    OnceClosure callback = BindOnce(&foo, bytes); // ERROR!
+/// 
+/// +template +inline cef_internal::RetainedRefWrapper RetainedRef(T* o) { + return cef_internal::RetainedRefWrapper(o); } - -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, const P1& p1, const P2& p2, const P3& p3, const P4& p4) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); - - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p2_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p3_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p4_is_refcounted_type_and_needs_scoped_refptr); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)> - BindState; - - return Callback( - new BindState(cef_internal::MakeRunnable(functor), p1, p2, p3, p4)); +template +inline cef_internal::RetainedRefWrapper RetainedRef(scoped_refptr o) { + return cef_internal::RetainedRefWrapper(std::move(o)); } -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, - const P1& p1, - const P2& p2, - const P3& p3, - const P4& p4, - const P5& p5) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); - - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p2_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p3_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p4_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p5_is_refcounted_type_and_needs_scoped_refptr); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)> - BindState; - - return Callback( - new BindState(cef_internal::MakeRunnable(functor), p1, p2, p3, p4, p5)); +/// +/// Owned() transfers ownership of an object to the callback resulting from +/// bind; the object will be deleted when the callback is deleted. +/// +/// EXAMPLE OF Owned(): +/// +///
+///   void foo(int* arg) { cout << *arg << endl }
+///
+///   int* pn = new int(1);
+///   RepeatingClosure foo_callback = BindRepeating(&foo, Owned(pn));
+///
+///   foo_callback.Run();  // Prints "1"
+///   foo_callback.Run();  // Prints "1"
+///   *pn = 2;
+///   foo_callback.Run();  // Prints "2"
+///
+///   foo_callback.Reset();  // |pn| is deleted.  Also will happen when
+///                          // |foo_callback| goes out of scope.
+/// 
+/// +/// Without Owned(), someone would have to know to delete |pn| when the last +/// reference to the callback is deleted. +/// +template +inline cef_internal::OwnedWrapper Owned(T* o) { + return cef_internal::OwnedWrapper(o); } -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, - const P1& p1, - const P2& p2, - const P3& p3, - const P4& p4, - const P5& p5, - const P6& p6) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); - - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p2_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p3_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p4_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p5_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p6_is_refcounted_type_and_needs_scoped_refptr); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)> - BindState; - - return Callback(new BindState( - cef_internal::MakeRunnable(functor), p1, p2, p3, p4, p5, p6)); +template +inline cef_internal::OwnedWrapper Owned( + std::unique_ptr&& ptr) { + return cef_internal::OwnedWrapper(std::move(ptr)); } -template -base::Callback::RunnableType, - typename cef_internal::FunctorTraits::RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)>:: - UnboundRunType> -Bind(Functor functor, - const P1& p1, - const P2& p2, - const P3& p3, - const P4& p4, - const P5& p5, - const P6& p6, - const P7& p7) { - // Typedefs for how to store and run the functor. - typedef - typename cef_internal::FunctorTraits::RunnableType RunnableType; - typedef typename cef_internal::FunctorTraits::RunType RunType; - - // Use RunnableType::RunType instead of RunType above because our - // checks should below for bound references need to know what the actual - // functor is going to interpret the argument as. - typedef cef_internal::FunctionTraits - BoundFunctorTraits; - - // Do not allow binding a non-const reference parameter. Non-const reference - // parameters are disallowed by the Google style guide. Also, binding a - // non-const reference parameter can make for subtle bugs because the - // invoked function will receive a reference to the stored copy of the - // argument and not the original. - COMPILE_ASSERT( - !(is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value || - is_non_const_reference::value), - do_not_bind_functions_with_nonconst_ref); +/// +/// OwnedRef() stores an object in the callback resulting from +/// bind and passes a reference to the object to the bound function. +/// +/// EXAMPLE OF OwnedRef(): +/// +///
+///   void foo(int& arg) { cout << ++arg << endl }
+///
+///   int counter = 0;
+///   RepeatingClosure foo_callback = BindRepeating(&foo, OwnedRef(counter));
+///
+///   foo_callback.Run();  // Prints "1"
+///   foo_callback.Run();  // Prints "2"
+///   foo_callback.Run();  // Prints "3"
+///
+///   cout << counter;     // Prints "0", OwnedRef creates a copy of counter.
+/// 
+/// +/// Supports OnceCallbacks as well, useful to pass placeholder arguments: +/// +///
+///   void bar(int& ignore, const std::string& s) { cout << s << endl }
+///
+///   OnceClosure bar_callback = BindOnce(&bar, OwnedRef(0), "Hello");
+///
+///   std::move(bar_callback).Run(); // Prints "Hello"
+/// 
+/// +/// Without OwnedRef() it would not be possible to pass a mutable reference to +/// an object owned by the callback. +/// +template +cef_internal::OwnedRefWrapper> OwnedRef(T&& t) { + return cef_internal::OwnedRefWrapper>(std::forward(t)); +} - // For methods, we need to be careful for parameter 1. We do not require - // a scoped_refptr because BindState<> itself takes care of AddRef() for - // methods. We also disallow binding of an array as the method's target - // object. - COMPILE_ASSERT(cef_internal::HasIsMethodTag::value || - !cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p1_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::HasIsMethodTag::value || - !is_array::value, - first_bound_argument_to_method_cannot_be_array); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p2_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p3_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p4_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p5_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p6_is_refcounted_type_and_needs_scoped_refptr); - COMPILE_ASSERT(!cef_internal::NeedsScopedRefptrButGetsRawPtr::value, - p7_is_refcounted_type_and_needs_scoped_refptr); - typedef cef_internal::BindState< - RunnableType, RunType, - void(typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType, - typename cef_internal::CallbackParamTraits::StorageType)> - BindState; +/// +/// Passed() is for transferring movable-but-not-copyable types (eg. unique_ptr) +/// through a RepeatingCallback. Logically, this signifies a destructive +/// transfer of the state of the argument into the target function. Invoking +/// RepeatingCallback::Run() twice on a callback that was created with a +/// Passed() argument will CHECK() because the first invocation would have +/// already transferred ownership to the target function. +/// +/// Note that Passed() is not necessary with BindOnce(), as std::move() does the +/// same thing. Avoid Passed() in favor of std::move() with BindOnce(). +/// +/// EXAMPLE OF Passed(): +/// +///
+///   void TakesOwnership(std::unique_ptr arg) { }
+///   std::unique_ptr CreateFoo() { return std::make_unique();
+///   }
+///
+///   auto f = std::make_unique();
+///
+///   // |cb| is given ownership of Foo(). |f| is now NULL.
+///   // You can use std::move(f) in place of &f, but it's more verbose.
+///   RepeatingClosure cb = BindRepeating(&TakesOwnership, Passed(&f));
+///
+///   // Run was never called so |cb| still owns Foo() and deletes
+///   // it on Reset().
+///   cb.Reset();
+///
+///   // |cb| is given a new Foo created by CreateFoo().
+///   cb = BindRepeating(&TakesOwnership, Passed(CreateFoo()));
+///
+///   // |arg| in TakesOwnership() is given ownership of Foo(). |cb|
+///   // no longer owns Foo() and, if reset, would not delete Foo().
+///   cb.Run();  // Foo() is now transferred to |arg| and deleted.
+///   cb.Run();  // This CHECK()s since Foo() already been used once.
+/// 
+/// +/// We offer 2 syntaxes for calling Passed(). The first takes an rvalue and is +/// best suited for use with the return value of a function or other temporary +/// rvalues. The second takes a pointer to the scoper and is just syntactic +/// sugar to avoid having to write Passed(std::move(scoper)). +/// +/// Both versions of Passed() prevent T from being an lvalue reference. The +/// first via use of enable_if, and the second takes a T* which will not bind to +/// T&. +/// +/// DEPRECATED - Do not use in new code. See https://crbug.com/1326449 +/// +template + requires(!std::is_lvalue_reference_v) +inline cef_internal::PassedWrapper Passed(T&& scoper) { + return cef_internal::PassedWrapper(std::move(scoper)); +} +template +inline cef_internal::PassedWrapper Passed(T* scoper) { + return cef_internal::PassedWrapper(std::move(*scoper)); +} - return Callback(new BindState( - cef_internal::MakeRunnable(functor), p1, p2, p3, p4, p5, p6, p7)); +/// +/// IgnoreResult() is used to adapt a function or callback with a return type to +/// one with a void return. This is most useful if you have a function with, +/// say, a pesky ignorable bool return that you want to use with PostTask or +/// something else that expect a callback with a void return. +/// +/// EXAMPLE OF IgnoreResult(): +/// +///
+///   int DoSomething(int arg) { cout << arg << endl; }
+///
+///   // Assign to a callback with a void return type.
+///   OnceCallback cb = BindOnce(IgnoreResult(&DoSomething));
+///   std::move(cb).Run(1);  // Prints "1".
+///
+///   // Prints "2" on |ml|.
+///   ml->PostTask(FROM_HERE, BindOnce(IgnoreResult(&DoSomething), 2);
+/// 
+/// +template +inline cef_internal::IgnoreResultHelper IgnoreResult(T data) { + return cef_internal::IgnoreResultHelper(std::move(data)); } } // namespace base diff --git a/src/include/base/cef_bind_helpers.h b/src/include/base/cef_bind_helpers.h deleted file mode 100644 index 2b4798b2c..000000000 --- a/src/include/base/cef_bind_helpers.h +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright (c) 2014 Marshall A. Greenblatt. Portions copyright (c) 2011 -// Google Inc. All rights reserved. -// -// 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 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. - -// This defines a set of argument wrappers and related factory methods that -// can be used specify the refcounting and reference semantics of arguments -// that are bound by the Bind() function in base/bind.h. -// -// It also defines a set of simple functions and utilities that people want -// when using Callback<> and Bind(). -// -// -// ARGUMENT BINDING WRAPPERS -// -// The wrapper functions are base::Unretained(), base::Owned(), base::Passed(), -// base::ConstRef(), and base::IgnoreResult(). -// -// Unretained() allows Bind() to bind a non-refcounted class, and to disable -// refcounting on arguments that are refcounted objects. -// -// Owned() transfers ownership of an object to the Callback resulting from -// bind; the object will be deleted when the Callback is deleted. -// -// Passed() is for transferring movable-but-not-copyable types (eg. scoped_ptr) -// through a Callback. Logically, this signifies a destructive transfer of -// the state of the argument into the target function. Invoking -// Callback::Run() twice on a Callback that was created with a Passed() -// argument will CHECK() because the first invocation would have already -// transferred ownership to the target function. -// -// ConstRef() allows binding a constant reference to an argument rather -// than a copy. -// -// IgnoreResult() is used to adapt a function or Callback with a return type to -// one with a void return. This is most useful if you have a function with, -// say, a pesky ignorable bool return that you want to use with PostTask or -// something else that expect a Callback with a void return. -// -// EXAMPLE OF Unretained(): -// -// class Foo { -// public: -// void func() { cout << "Foo:f" << endl; } -// }; -// -// // In some function somewhere. -// Foo foo; -// Closure foo_callback = -// Bind(&Foo::func, Unretained(&foo)); -// foo_callback.Run(); // Prints "Foo:f". -// -// Without the Unretained() wrapper on |&foo|, the above call would fail -// to compile because Foo does not support the AddRef() and Release() methods. -// -// -// EXAMPLE OF Owned(): -// -// void foo(int* arg) { cout << *arg << endl } -// -// int* pn = new int(1); -// Closure foo_callback = Bind(&foo, Owned(pn)); -// -// foo_callback.Run(); // Prints "1" -// foo_callback.Run(); // Prints "1" -// *n = 2; -// foo_callback.Run(); // Prints "2" -// -// foo_callback.Reset(); // |pn| is deleted. Also will happen when -// // |foo_callback| goes out of scope. -// -// Without Owned(), someone would have to know to delete |pn| when the last -// reference to the Callback is deleted. -// -// -// EXAMPLE OF ConstRef(): -// -// void foo(int arg) { cout << arg << endl } -// -// int n = 1; -// Closure no_ref = Bind(&foo, n); -// Closure has_ref = Bind(&foo, ConstRef(n)); -// -// no_ref.Run(); // Prints "1" -// has_ref.Run(); // Prints "1" -// -// n = 2; -// no_ref.Run(); // Prints "1" -// has_ref.Run(); // Prints "2" -// -// Note that because ConstRef() takes a reference on |n|, |n| must outlive all -// its bound callbacks. -// -// -// EXAMPLE OF IgnoreResult(): -// -// int DoSomething(int arg) { cout << arg << endl; } -// -// // Assign to a Callback with a void return type. -// Callback cb = Bind(IgnoreResult(&DoSomething)); -// cb->Run(1); // Prints "1". -// -// // Prints "1" on |ml|. -// ml->PostTask(FROM_HERE, Bind(IgnoreResult(&DoSomething), 1); -// -// -// EXAMPLE OF Passed(): -// -// void TakesOwnership(scoped_ptr arg) { } -// scoped_ptr CreateFoo() { return scoped_ptr(new Foo()); } -// -// scoped_ptr f(new Foo()); -// -// // |cb| is given ownership of Foo(). |f| is now NULL. -// // You can use f.Pass() in place of &f, but it's more verbose. -// Closure cb = Bind(&TakesOwnership, Passed(&f)); -// -// // Run was never called so |cb| still owns Foo() and deletes -// // it on Reset(). -// cb.Reset(); -// -// // |cb| is given a new Foo created by CreateFoo(). -// cb = Bind(&TakesOwnership, Passed(CreateFoo())); -// -// // |arg| in TakesOwnership() is given ownership of Foo(). |cb| -// // no longer owns Foo() and, if reset, would not delete Foo(). -// cb.Run(); // Foo() is now transferred to |arg| and deleted. -// cb.Run(); // This CHECK()s since Foo() already been used once. -// -// Passed() is particularly useful with PostTask() when you are transferring -// ownership of an argument into a task, but don't necessarily know if the -// task will always be executed. This can happen if the task is cancellable -// or if it is posted to a MessageLoopProxy. -// -// -// SIMPLE FUNCTIONS AND UTILITIES. -// -// DoNothing() - Useful for creating a Closure that does nothing when called. -// DeletePointer() - Useful for creating a Closure that will delete a -// pointer when invoked. Only use this when necessary. -// In most cases MessageLoop::DeleteSoon() is a better -// fit. - -#ifndef CEF_INCLUDE_BASE_CEF_BIND_HELPERS_H_ -#define CEF_INCLUDE_BASE_CEF_BIND_HELPERS_H_ -#pragma once - -#if defined(BASE_BIND_HELPERS_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) -// When building CEF include the Chromium header directly. -#include "base/bind_helpers.h" -#else // !USING_CHROMIUM_INCLUDES -// The following is substantially similar to the Chromium implementation. -// If the Chromium implementation diverges the below implementation should be -// updated to match. - -#include "include/base/cef_basictypes.h" -#include "include/base/cef_callback.h" -#include "include/base/cef_template_util.h" -#include "include/base/cef_weak_ptr.h" - -namespace base { -namespace cef_internal { - -// Use the Substitution Failure Is Not An Error (SFINAE) trick to inspect T -// for the existence of AddRef() and Release() functions of the correct -// signature. -// -// http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error -// http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence -// http://stackoverflow.com/questions/4358584/sfinae-approach-comparison -// http://stackoverflow.com/questions/1966362/sfinae-to-check-for-inherited-member-functions -// -// The last link in particular show the method used below. -// -// For SFINAE to work with inherited methods, we need to pull some extra tricks -// with multiple inheritance. In the more standard formulation, the overloads -// of Check would be: -// -// template -// Yes NotTheCheckWeWant(Helper<&C::TargetFunc>*); -// -// template -// No NotTheCheckWeWant(...); -// -// static const bool value = sizeof(NotTheCheckWeWant(0)) == sizeof(Yes); -// -// The problem here is that template resolution will not match -// C::TargetFunc if TargetFunc does not exist directly in C. That is, if -// TargetFunc in inherited from an ancestor, &C::TargetFunc will not match, -// |value| will be false. This formulation only checks for whether or -// not TargetFunc exist directly in the class being introspected. -// -// To get around this, we play a dirty trick with multiple inheritance. -// First, We create a class BaseMixin that declares each function that we -// want to probe for. Then we create a class Base that inherits from both T -// (the class we wish to probe) and BaseMixin. Note that the function -// signature in BaseMixin does not need to match the signature of the function -// we are probing for; thus it's easiest to just use void(void). -// -// Now, if TargetFunc exists somewhere in T, then &Base::TargetFunc has an -// ambiguous resolution between BaseMixin and T. This lets us write the -// following: -// -// template -// No GoodCheck(Helper<&C::TargetFunc>*); -// -// template -// Yes GoodCheck(...); -// -// static const bool value = sizeof(GoodCheck(0)) == sizeof(Yes); -// -// Notice here that the variadic version of GoodCheck() returns Yes here -// instead of No like the previous one. Also notice that we calculate |value| -// by specializing GoodCheck() on Base instead of T. -// -// We've reversed the roles of the variadic, and Helper overloads. -// GoodCheck(Helper<&C::TargetFunc>*), when C = Base, fails to be a valid -// substitution if T::TargetFunc exists. Thus GoodCheck(0) will resolve -// to the variadic version if T has TargetFunc. If T::TargetFunc does not -// exist, then &C::TargetFunc is not ambiguous, and the overload resolution -// will prefer GoodCheck(Helper<&C::TargetFunc>*). -// -// This method of SFINAE will correctly probe for inherited names, but it cannot -// typecheck those names. It's still a good enough sanity check though. -// -// Works on gcc-4.2, gcc-4.4, and Visual Studio 2008. -// -// TODO(ajwong): Move to ref_counted.h or template_util.h when we've vetted -// this works well. -// -// TODO(ajwong): Make this check for Release() as well. -// See http://crbug.com/82038. -template -class SupportsAddRefAndRelease { - typedef char Yes[1]; - typedef char No[2]; - - struct BaseMixin { - void AddRef(); - }; - -// MSVC warns when you try to use Base if T has a private destructor, the -// common pattern for refcounted types. It does this even though no attempt to -// instantiate Base is made. We disable the warning for this definition. -#if defined(OS_WIN) -#pragma warning(push) -#pragma warning(disable : 4624) -#endif - struct Base : public T, public BaseMixin {}; -#if defined(OS_WIN) -#pragma warning(pop) -#endif - - template - struct Helper {}; - - template - static No& Check(Helper<&C::AddRef>*); - - template - static Yes& Check(...); - - public: - static const bool value = sizeof(Check(0)) == sizeof(Yes); -}; - -// Helpers to assert that arguments of a recounted type are bound with a -// scoped_refptr. -template -struct UnsafeBindtoRefCountedArgHelper : false_type {}; - -template -struct UnsafeBindtoRefCountedArgHelper - : integral_constant::value> {}; - -template -struct UnsafeBindtoRefCountedArg : false_type {}; - -template -struct UnsafeBindtoRefCountedArg - : UnsafeBindtoRefCountedArgHelper::value, T> {}; - -template -class HasIsMethodTag { - typedef char Yes[1]; - typedef char No[2]; - - template - static Yes& Check(typename U::IsMethod*); - - template - static No& Check(...); - - public: - static const bool value = sizeof(Check(0)) == sizeof(Yes); -}; - -template -class UnretainedWrapper { - public: - explicit UnretainedWrapper(T* o) : ptr_(o) {} - T* get() const { return ptr_; } - - private: - T* ptr_; -}; - -template -class ConstRefWrapper { - public: - explicit ConstRefWrapper(const T& o) : ptr_(&o) {} - const T& get() const { return *ptr_; } - - private: - const T* ptr_; -}; - -template -struct IgnoreResultHelper { - explicit IgnoreResultHelper(T functor) : functor_(functor) {} - - T functor_; -}; - -template -struct IgnoreResultHelper> { - explicit IgnoreResultHelper(const Callback& functor) : functor_(functor) {} - - const Callback& functor_; -}; - -// An alternate implementation is to avoid the destructive copy, and instead -// specialize ParamTraits<> for OwnedWrapper<> to change the StorageType to -// a class that is essentially a scoped_ptr<>. -// -// The current implementation has the benefit though of leaving ParamTraits<> -// fully in callback_internal.h as well as avoiding type conversions during -// storage. -template -class OwnedWrapper { - public: - explicit OwnedWrapper(T* o) : ptr_(o) {} - ~OwnedWrapper() { delete ptr_; } - T* get() const { return ptr_; } - OwnedWrapper(const OwnedWrapper& other) { - ptr_ = other.ptr_; - other.ptr_ = NULL; - } - - private: - mutable T* ptr_; -}; - -// PassedWrapper is a copyable adapter for a scoper that ignores const. -// -// It is needed to get around the fact that Bind() takes a const reference to -// all its arguments. Because Bind() takes a const reference to avoid -// unnecessary copies, it is incompatible with movable-but-not-copyable -// types; doing a destructive "move" of the type into Bind() would violate -// the const correctness. -// -// This conundrum cannot be solved without either C++11 rvalue references or -// a O(2^n) blowup of Bind() templates to handle each combination of regular -// types and movable-but-not-copyable types. Thus we introduce a wrapper type -// that is copyable to transmit the correct type information down into -// BindState<>. Ignoring const in this type makes sense because it is only -// created when we are explicitly trying to do a destructive move. -// -// Two notes: -// 1) PassedWrapper supports any type that has a "Pass()" function. -// This is intentional. The whitelisting of which specific types we -// support is maintained by CallbackParamTraits<>. -// 2) is_valid_ is distinct from NULL because it is valid to bind a "NULL" -// scoper to a Callback and allow the Callback to execute once. -template -class PassedWrapper { - public: - explicit PassedWrapper(T scoper) : is_valid_(true), scoper_(scoper.Pass()) {} - PassedWrapper(const PassedWrapper& other) - : is_valid_(other.is_valid_), scoper_(other.scoper_.Pass()) {} - T Pass() const { - CHECK(is_valid_); - is_valid_ = false; - return scoper_.Pass(); - } - - private: - mutable bool is_valid_; - mutable T scoper_; -}; - -// Unwrap the stored parameters for the wrappers above. -template -struct UnwrapTraits { - typedef const T& ForwardType; - static ForwardType Unwrap(const T& o) { return o; } -}; - -template -struct UnwrapTraits> { - typedef T* ForwardType; - static ForwardType Unwrap(UnretainedWrapper unretained) { - return unretained.get(); - } -}; - -template -struct UnwrapTraits> { - typedef const T& ForwardType; - static ForwardType Unwrap(ConstRefWrapper const_ref) { - return const_ref.get(); - } -}; - -template -struct UnwrapTraits> { - typedef T* ForwardType; - static ForwardType Unwrap(const scoped_refptr& o) { return o.get(); } -}; - -template -struct UnwrapTraits> { - typedef const WeakPtr& ForwardType; - static ForwardType Unwrap(const WeakPtr& o) { return o; } -}; - -template -struct UnwrapTraits> { - typedef T* ForwardType; - static ForwardType Unwrap(const OwnedWrapper& o) { return o.get(); } -}; - -template -struct UnwrapTraits> { - typedef T ForwardType; - static T Unwrap(PassedWrapper& o) { return o.Pass(); } -}; - -// Utility for handling different refcounting semantics in the Bind() -// function. -template -struct MaybeRefcount; - -template -struct MaybeRefcount { - static void AddRef(const T&) {} - static void Release(const T&) {} -}; - -template -struct MaybeRefcount { - static void AddRef(const T*) {} - static void Release(const T*) {} -}; - -template -struct MaybeRefcount { - static void AddRef(const T&) {} - static void Release(const T&) {} -}; - -template -struct MaybeRefcount { - static void AddRef(T* o) { o->AddRef(); } - static void Release(T* o) { o->Release(); } -}; - -// No need to additionally AddRef() and Release() since we are storing a -// scoped_refptr<> inside the storage object already. -template -struct MaybeRefcount> { - static void AddRef(const scoped_refptr& o) {} - static void Release(const scoped_refptr& o) {} -}; - -template -struct MaybeRefcount { - static void AddRef(const T* o) { o->AddRef(); } - static void Release(const T* o) { o->Release(); } -}; - -// IsWeakMethod is a helper that determine if we are binding a WeakPtr<> to a -// method. It is used internally by Bind() to select the correct -// InvokeHelper that will no-op itself in the event the WeakPtr<> for -// the target object is invalidated. -// -// P1 should be the type of the object that will be received of the method. -template -struct IsWeakMethod : public false_type {}; - -template -struct IsWeakMethod> : public true_type {}; - -template -struct IsWeakMethod>> : public true_type {}; - -} // namespace cef_internal - -template -static inline cef_internal::UnretainedWrapper Unretained(T* o) { - return cef_internal::UnretainedWrapper(o); -} - -template -static inline cef_internal::ConstRefWrapper ConstRef(const T& o) { - return cef_internal::ConstRefWrapper(o); -} - -template -static inline cef_internal::OwnedWrapper Owned(T* o) { - return cef_internal::OwnedWrapper(o); -} - -// We offer 2 syntaxes for calling Passed(). The first takes a temporary and -// is best suited for use with the return value of a function. The second -// takes a pointer to the scoper and is just syntactic sugar to avoid having -// to write Passed(scoper.Pass()). -template -static inline cef_internal::PassedWrapper Passed(T scoper) { - return cef_internal::PassedWrapper(scoper.Pass()); -} -template -static inline cef_internal::PassedWrapper Passed(T* scoper) { - return cef_internal::PassedWrapper(scoper->Pass()); -} - -template -static inline cef_internal::IgnoreResultHelper IgnoreResult(T data) { - return cef_internal::IgnoreResultHelper(data); -} - -template -static inline cef_internal::IgnoreResultHelper> IgnoreResult( - const Callback& data) { - return cef_internal::IgnoreResultHelper>(data); -} - -void DoNothing(); - -template -void DeletePointer(T* obj) { - delete obj; -} - -} // namespace base - -#endif // !USING_CHROMIUM_INCLUDES - -#endif // CEF_INCLUDE_BASE_CEF_BIND_HELPERS_H_ diff --git a/src/include/base/cef_build.h b/src/include/base/cef_build.h index 1e2065ce3..7f156f3c4 100644 --- a/src/include/base/cef_build.h +++ b/src/include/base/cef_build.h @@ -1,4 +1,5 @@ -// Copyright (c) 2011 Marshall A. Greenblatt. All rights reserved. +// Copyright (c) 2011 Marshall A. Greenblatt. Portions copyright (c) 2012 +// Google Inc. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -27,53 +28,152 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// \file +/// This file adds defines about the platform we're currently building on. +/// +///
+///  Operating System:
+///    OS_AIX / OS_ANDROID / OS_ASMJS / OS_FREEBSD / OS_FUCHSIA / OS_IOS /
+///    OS_LINUX / OS_MAC / OS_NACL (SFI or NONSFI) / OS_NETBSD / OS_OPENBSD /
+///    OS_QNX / OS_SOLARIS / OS_WIN
+///  Operating System family:
+///    OS_APPLE: IOS or MAC
+///    OS_BSD: FREEBSD or NETBSD or OPENBSD
+///    OS_POSIX: AIX or ANDROID or ASMJS or CHROMEOS or FREEBSD or IOS or LINUX
+///              or MAC or NACL or NETBSD or OPENBSD or QNX or SOLARIS
+///
+///  /!\ Note: OS_CHROMEOS is set by the build system, not this file
+///
+///  Compiler:
+///    COMPILER_MSVC / COMPILER_GCC
+///
+///  Processor:
+///    ARCH_CPU_ARM64 / ARCH_CPU_ARMEL / ARCH_CPU_MIPS / ARCH_CPU_MIPS64 /
+///    ARCH_CPU_MIPS64EL / ARCH_CPU_MIPSEL / ARCH_CPU_PPC64 / ARCH_CPU_S390 /
+///    ARCH_CPU_S390X / ARCH_CPU_X86 / ARCH_CPU_X86_64
+///  Processor family:
+///    ARCH_CPU_ARM_FAMILY: ARMEL or ARM64
+///    ARCH_CPU_MIPS_FAMILY: MIPS64EL or MIPSEL or MIPS64 or MIPS
+///    ARCH_CPU_PPC64_FAMILY: PPC64
+///    ARCH_CPU_S390_FAMILY: S390 or S390X
+///    ARCH_CPU_X86_FAMILY: X86 or X86_64
+///  Processor features:
+///    ARCH_CPU_31_BITS / ARCH_CPU_32_BITS / ARCH_CPU_64_BITS
+///    ARCH_CPU_BIG_ENDIAN / ARCH_CPU_LITTLE_ENDIAN
+/// 
+/// + #ifndef CEF_INCLUDE_BASE_CEF_BUILD_H_ #define CEF_INCLUDE_BASE_CEF_BUILD_H_ #pragma once #if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. -#include "base/compiler_specific.h" +#include "build/build_config.h" +#include "cef/libcef/features/features.h" + +// The following #defines are used in cef/include/ headers and CEF client-side +// code. CEF library-side code should use BUILDFLAG checks directly instead of +// these #defines. CEF client-side code will get these #defines from +// cef_config.h so any changes must also be reflected in +// tools/make_config_header.py. + +#if BUILDFLAG(IS_LINUX) +#include "ui/base/ozone_buildflags.h" +#if BUILDFLAG(SUPPORTS_OZONE_X11) +#define CEF_X11 1 +#endif +#endif + #else // !USING_CHROMIUM_INCLUDES + +#if !defined(GENERATING_CEF_API_HASH) +#include "include/cef_config.h" +#endif + // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be // updated to match. -#if defined(_WIN32) -#ifndef OS_WIN -#define OS_WIN 1 -#endif +// A set of macros to use for platform detection. +#if defined(ANDROID) +#define OS_ANDROID 1 #elif defined(__APPLE__) -#ifndef OS_MACOSX +// Only include TargetConditionals after testing ANDROID as some Android builds +// on the Mac have this header available and it's not needed unless the target +// is really an Apple platform. +#include +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#define OS_IOS 1 +#else +#define OS_MAC 1 +// For backwards compatibility. #define OS_MACOSX 1 -#endif +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE #elif defined(__linux__) -#ifndef OS_LINUX +#if !defined(OS_CHROMEOS) +// Do not define OS_LINUX on Chrome OS build. +// The OS_CHROMEOS macro is defined in GN. #define OS_LINUX 1 +#endif // !defined(OS_CHROMEOS) +// Include a system header to pull in features.h for glibc/uclibc macros. +#include +#if defined(__GLIBC__) && !defined(__UCLIBC__) +// We really are using glibc, not uClibc pretending to be glibc. +#define LIBC_GLIBC 1 #endif +#elif defined(_WIN32) +#define OS_WIN 1 +#elif defined(__Fuchsia__) +#define OS_FUCHSIA 1 +#elif defined(__FreeBSD__) +#define OS_FREEBSD 1 +#elif defined(__NetBSD__) +#define OS_NETBSD 1 +#elif defined(__OpenBSD__) +#define OS_OPENBSD 1 +#elif defined(__sun) +#define OS_SOLARIS 1 +#elif defined(__QNXNTO__) +#define OS_QNX 1 +#elif defined(_AIX) +#define OS_AIX 1 +#elif defined(__asmjs__) || defined(__wasm__) +#define OS_ASMJS 1 #else -#error Please add support for your platform in cef_build.h +#error Please add support for your platform in include/base/cef_build.h +#endif +// NOTE: Adding a new port? Please follow +// https://chromium.googlesource.com/chromium/src/+/master/docs/new_port_policy.md + +#if defined(OS_MAC) || defined(OS_IOS) +#define OS_APPLE 1 +#endif + +// For access to standard BSD features, use OS_BSD instead of a +// more specific macro. +#if defined(OS_FREEBSD) || defined(OS_NETBSD) || defined(OS_OPENBSD) +#define OS_BSD 1 #endif // For access to standard POSIXish features, use OS_POSIX instead of a // more specific macro. -#if defined(OS_MACOSX) || defined(OS_LINUX) -#ifndef OS_POSIX +#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) || \ + defined(OS_FREEBSD) || defined(OS_IOS) || defined(OS_LINUX) || \ + defined(OS_CHROMEOS) || defined(OS_MAC) || defined(OS_NACL) || \ + defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_QNX) || \ + defined(OS_SOLARIS) #define OS_POSIX 1 #endif -#endif -// Compiler detection. +// Compiler detection. Note: clang masquerades as GCC on POSIX and as MSVC on +// Windows. #if defined(__GNUC__) -#ifndef COMPILER_GCC #define COMPILER_GCC 1 -#endif #elif defined(_MSC_VER) -#ifndef COMPILER_MSVC #define COMPILER_MSVC 1 -#endif #else -#error Please add support for your compiler in cef_build.h +#error Please add support for your compiler in build/build_config.h #endif // Processor architecture detection. For more info on what's defined, see: @@ -90,108 +190,95 @@ #define ARCH_CPU_X86 1 #define ARCH_CPU_32_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__s390x__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390X 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__s390__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390 1 +#define ARCH_CPU_31_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif (defined(__PPC64__) || defined(__PPC__)) && defined(__BIG_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 #elif defined(__ARMEL__) #define ARCH_CPU_ARM_FAMILY 1 #define ARCH_CPU_ARMEL 1 #define ARCH_CPU_32_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 -#elif defined(__aarch64__) +#elif defined(__aarch64__) || defined(_M_ARM64) #define ARCH_CPU_ARM_FAMILY 1 #define ARCH_CPU_ARM64 1 #define ARCH_CPU_64_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 -#elif defined(__pnacl__) +#elif defined(__pnacl__) || defined(__asmjs__) || defined(__wasm__) #define ARCH_CPU_32_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 #elif defined(__MIPSEL__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS64EL 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#else #define ARCH_CPU_MIPS_FAMILY 1 #define ARCH_CPU_MIPSEL 1 #define ARCH_CPU_32_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 +#endif +#elif defined(__MIPSEB__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#else +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPS 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#endif #else -#error Please add support for your architecture in cef_build.h +#error Please add support for your architecture in include/base/cef_build.h #endif // Type detection for wchar_t. #if defined(OS_WIN) -#define WCHAR_T_IS_UTF16 +#define WCHAR_T_IS_16_BIT +#elif defined(OS_FUCHSIA) +#define WCHAR_T_IS_32_BIT #elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \ (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) -#define WCHAR_T_IS_UTF32 +#define WCHAR_T_IS_32_BIT #elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \ (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff) // On Posix, we'll detect short wchar_t, but projects aren't guaranteed to // compile in this mode (in particular, Chrome doesn't). This is intended for // other projects using base who manage their own dependencies and make sure // short wchar works for them. -#define WCHAR_T_IS_UTF16 +#define WCHAR_T_IS_16_BIT #else -#error Please add support for your compiler in cef_build.h +#error Please add support for your compiler in include/base/cef_build.h #endif -// Annotate a function indicating the caller must examine the return value. -// Use like: -// int foo() WARN_UNUSED_RESULT; -// To explicitly ignore a result, see |ignore_result()| in . -#ifndef WARN_UNUSED_RESULT -#if defined(COMPILER_GCC) -#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -#define WARN_UNUSED_RESULT -#endif -#endif // WARN_UNUSED_RESULT - -// Annotate a typedef or function indicating it's ok if it's not used. -// Use like: -// typedef Foo Bar ALLOW_UNUSED_TYPE; -#ifndef ALLOW_UNUSED_TYPE -#if defined(COMPILER_GCC) -#define ALLOW_UNUSED_TYPE __attribute__((unused)) -#else -#define ALLOW_UNUSED_TYPE -#endif -#endif // ALLOW_UNUSED_TYPE - -// Annotate a variable indicating it's ok if the variable is not used. -// (Typically used to silence a compiler warning when the assignment -// is important for some other reason.) -// Use like: -// int x = ...; -// ALLOW_UNUSED_LOCAL(x); -#ifndef ALLOW_UNUSED_LOCAL -#define ALLOW_UNUSED_LOCAL(x) false ? (void)x : (void)0 +#if defined(OS_ANDROID) +// The compiler thinks std::string::const_iterator and "const char*" are +// equivalent types. +#define STD_STRING_ITERATOR_IS_CHAR_POINTER +// The compiler thinks std::u16string::const_iterator and "char16_t*" are +// equivalent types. +#define BASE_STRING16_ITERATOR_IS_CHAR16_POINTER #endif #endif // !USING_CHROMIUM_INCLUDES -// Annotate a virtual method indicating it must be overriding a virtual method -// in the parent class. -// Use like: -// void foo() OVERRIDE; -// NOTE: This define should only be used in classes exposed to the client since -// C++11 support may not be enabled in client applications. CEF internal classes -// should use the `override` keyword directly. -#ifndef OVERRIDE -#if defined(__clang__) -#define OVERRIDE override -#elif defined(COMPILER_MSVC) && _MSC_VER >= 1600 -// Visual Studio 2010 and later support override. -#define OVERRIDE override -#elif defined(COMPILER_GCC) && __cplusplus >= 201103 && \ - (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 -// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. -#define OVERRIDE override -#else -#define OVERRIDE -#endif -#endif // OVERRIDE - -// Check for C++11 template alias support which was added in VS2013 and GCC4.7. -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2258.pdf -#if __cplusplus > 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/base/cef_callback.h b/src/include/base/cef_callback.h index 16e238a97..3c13ced73 100644 --- a/src/include/base/cef_callback.h +++ b/src/include/base/cef_callback.h @@ -28,771 +28,486 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// +/// \file +/// A callback is similar in concept to a function pointer: it wraps a runnable +/// object such as a function, method, lambda, or even another callback, +/// allowing the runnable object to be invoked later via the callback object. +/// +/// Unlike function pointers, callbacks are created with base::BindOnce() or +/// base::BindRepeating() and support partial function application. +/// +/// A base::OnceCallback may be Run() at most once; a base::RepeatingCallback +/// may be Run() any number of times. |is_null()| is guaranteed to return true +/// for a moved-from callback. +/// +///
+///   // The lambda takes two arguments, but the first argument |x| is bound at
+///   // callback creation.
+///   base::OnceCallback cb = base::BindOnce([] (int x, int y) {
+///     return x + y;
+///   }, 1);
+///   // Run() only needs the remaining unbound argument |y|.
+///   printf("1 + 2 = %d\n", std::move(cb).Run(2));  // Prints 3
+///   printf("cb is null? %s\n",
+///          cb.is_null() ? "true" : "false");  // Prints true
+///   std::move(cb).Run(2);  // Crashes since |cb| has already run.
+/// 
+/// +/// Callbacks also support cancellation. A common use is binding the receiver +/// object as a WeakPtr. If that weak pointer is invalidated, calling Run() +/// will be a no-op. Note that |IsCancelled()| and |is_null()| are distinct: +/// simply cancelling a callback will not also make it null. +/// +/// See https://chromium.googlesource.com/chromium/src/+/lkgr/docs/callback.md +/// for the full documentation. +/// + #ifndef CEF_INCLUDE_BASE_CEF_CALLBACK_H_ #define CEF_INCLUDE_BASE_CEF_CALLBACK_H_ #pragma once -#if defined(BASE_CALLBACK_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) +#if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. -#include "base/callback.h" +#include "base/functional/callback.h" #else // !USING_CHROMIUM_INCLUDES // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be // updated to match. -#include "include/base/cef_callback_forward.h" -#include "include/base/cef_template_util.h" -#include "include/base/internal/cef_callback_internal.h" +#include -// NOTE: Header files that do not require the full definition of Callback or -// Closure should #include "base/cef_callback_forward.h" instead of this file. +#include +#include -// ----------------------------------------------------------------------------- -// Introduction -// ----------------------------------------------------------------------------- -// -// The templated Callback class is a generalized function object. Together -// with the Bind() function in bind.h, they provide a type-safe method for -// performing partial application of functions. -// -// Partial application (or "currying") is the process of binding a subset of -// a function's arguments to produce another function that takes fewer -// arguments. This can be used to pass around a unit of delayed execution, -// much like lexical closures are used in other languages. For example, it -// is used in Chromium code to schedule tasks on different MessageLoops. -// -// A callback with no unbound input parameters (base::Callback) -// is called a base::Closure. Note that this is NOT the same as what other -// languages refer to as a closure -- it does not retain a reference to its -// enclosing environment. -// -// MEMORY MANAGEMENT AND PASSING -// -// The Callback objects themselves should be passed by const-reference, and -// stored by copy. They internally store their state via a refcounted class -// and thus do not need to be deleted. -// -// The reason to pass via a const-reference is to avoid unnecessary -// AddRef/Release pairs to the internal state. -// -// -// ----------------------------------------------------------------------------- -// Quick reference for basic stuff -// ----------------------------------------------------------------------------- -// -// BINDING A BARE FUNCTION -// -// int Return5() { return 5; } -// base::Callback func_cb = base::Bind(&Return5); -// LOG(INFO) << func_cb.Run(); // Prints 5. -// -// BINDING A CLASS METHOD -// -// The first argument to bind is the member function to call, the second is -// the object on which to call it. -// -// class Ref : public base::RefCountedThreadSafe { -// public: -// int Foo() { return 3; } -// void PrintBye() { LOG(INFO) << "bye."; } -// }; -// scoped_refptr ref = new Ref(); -// base::Callback ref_cb = base::Bind(&Ref::Foo, ref); -// LOG(INFO) << ref_cb.Run(); // Prints out 3. -// -// By default the object must support RefCounted or you will get a compiler -// error. If you're passing between threads, be sure it's -// RefCountedThreadSafe! See "Advanced binding of member functions" below if -// you don't want to use reference counting. -// -// RUNNING A CALLBACK -// -// Callbacks can be run with their "Run" method, which has the same -// signature as the template argument to the callback. -// -// void DoSomething(const base::Callback& callback) { -// callback.Run(5, "hello"); -// } -// -// Callbacks can be run more than once (they don't get deleted or marked when -// run). However, this precludes using base::Passed (see below). -// -// void DoSomething(const base::Callback& callback) { -// double myresult = callback.Run(3.14159); -// myresult += callback.Run(2.71828); -// } -// -// PASSING UNBOUND INPUT PARAMETERS -// -// Unbound parameters are specified at the time a callback is Run(). They are -// specified in the Callback template type: -// -// void MyFunc(int i, const std::string& str) {} -// base::Callback cb = base::Bind(&MyFunc); -// cb.Run(23, "hello, world"); -// -// PASSING BOUND INPUT PARAMETERS -// -// Bound parameters are specified when you create thee callback as arguments -// to Bind(). They will be passed to the function and the Run()ner of the -// callback doesn't see those values or even know that the function it's -// calling. -// -// void MyFunc(int i, const std::string& str) {} -// base::Callback cb = base::Bind(&MyFunc, 23, "hello world"); -// cb.Run(); -// -// A callback with no unbound input parameters (base::Callback) -// is called a base::Closure. So we could have also written: -// -// base::Closure cb = base::Bind(&MyFunc, 23, "hello world"); -// -// When calling member functions, bound parameters just go after the object -// pointer. -// -// base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world"); -// -// PARTIAL BINDING OF PARAMETERS -// -// You can specify some parameters when you create the callback, and specify -// the rest when you execute the callback. -// -// void MyFunc(int i, const std::string& str) {} -// base::Callback cb = base::Bind(&MyFunc, 23); -// cb.Run("hello world"); -// -// When calling a function bound parameters are first, followed by unbound -// parameters. -// -// -// ----------------------------------------------------------------------------- -// Quick reference for advanced binding -// ----------------------------------------------------------------------------- -// -// BINDING A CLASS METHOD WITH WEAK POINTERS -// -// base::Bind(&MyClass::Foo, GetWeakPtr()); -// -// The callback will not be run if the object has already been destroyed. -// DANGER: weak pointers are not threadsafe, so don't use this -// when passing between threads! -// -// BINDING A CLASS METHOD WITH MANUAL LIFETIME MANAGEMENT -// -// base::Bind(&MyClass::Foo, base::Unretained(this)); -// -// This disables all lifetime management on the object. You're responsible -// for making sure the object is alive at the time of the call. You break it, -// you own it! -// -// BINDING A CLASS METHOD AND HAVING THE CALLBACK OWN THE CLASS -// -// MyClass* myclass = new MyClass; -// base::Bind(&MyClass::Foo, base::Owned(myclass)); -// -// The object will be deleted when the callback is destroyed, even if it's -// not run (like if you post a task during shutdown). Potentially useful for -// "fire and forget" cases. -// -// IGNORING RETURN VALUES -// -// Sometimes you want to call a function that returns a value in a callback -// that doesn't expect a return value. -// -// int DoSomething(int arg) { cout << arg << endl; } -// base::Callback) cb = -// base::Bind(base::IgnoreResult(&DoSomething)); -// -// -// ----------------------------------------------------------------------------- -// Quick reference for binding parameters to Bind() -// ----------------------------------------------------------------------------- -// -// Bound parameters are specified as arguments to Bind() and are passed to the -// function. A callback with no parameters or no unbound parameters is called a -// Closure (base::Callback and base::Closure are the same thing). -// -// PASSING PARAMETERS OWNED BY THE CALLBACK -// -// void Foo(int* arg) { cout << *arg << endl; } -// int* pn = new int(1); -// base::Closure foo_callback = base::Bind(&foo, base::Owned(pn)); -// -// The parameter will be deleted when the callback is destroyed, even if it's -// not run (like if you post a task during shutdown). -// -// PASSING PARAMETERS AS A scoped_ptr -// -// void TakesOwnership(scoped_ptr arg) {} -// scoped_ptr f(new Foo); -// // f becomes null during the following call. -// base::Closure cb = base::Bind(&TakesOwnership, base::Passed(&f)); -// -// Ownership of the parameter will be with the callback until the it is run, -// when ownership is passed to the callback function. This means the callback -// can only be run once. If the callback is never run, it will delete the -// object when it's destroyed. -// -// PASSING PARAMETERS AS A scoped_refptr -// -// void TakesOneRef(scoped_refptr arg) {} -// scoped_refptr f(new Foo) -// base::Closure cb = base::Bind(&TakesOneRef, f); -// -// This should "just work." The closure will take a reference as long as it -// is alive, and another reference will be taken for the called function. -// -// PASSING PARAMETERS BY REFERENCE -// -// Const references are *copied* unless ConstRef is used. Example: -// -// void foo(const int& arg) { printf("%d %p\n", arg, &arg); } -// int n = 1; -// base::Closure has_copy = base::Bind(&foo, n); -// base::Closure has_ref = base::Bind(&foo, base::ConstRef(n)); -// n = 2; -// foo(n); // Prints "2 0xaaaaaaaaaaaa" -// has_copy.Run(); // Prints "1 0xbbbbbbbbbbbb" -// has_ref.Run(); // Prints "2 0xaaaaaaaaaaaa" -// -// Normally parameters are copied in the closure. DANGER: ConstRef stores a -// const reference instead, referencing the original parameter. This means -// that you must ensure the object outlives the callback! -// -// -// ----------------------------------------------------------------------------- -// Implementation notes -// ----------------------------------------------------------------------------- -// -// WHERE IS THIS DESIGN FROM: -// -// The design Callback and Bind is heavily influenced by C++'s -// tr1::function/tr1::bind, and by the "Google Callback" system used inside -// Google. -// -// -// HOW THE IMPLEMENTATION WORKS: -// -// There are three main components to the system: -// 1) The Callback classes. -// 2) The Bind() functions. -// 3) The arguments wrappers (e.g., Unretained() and ConstRef()). -// -// The Callback classes represent a generic function pointer. Internally, -// it stores a refcounted piece of state that represents the target function -// and all its bound parameters. Each Callback specialization has a templated -// constructor that takes an BindState<>*. In the context of the constructor, -// the static type of this BindState<> pointer uniquely identifies the -// function it is representing, all its bound parameters, and a Run() method -// that is capable of invoking the target. -// -// Callback's constructor takes the BindState<>* that has the full static type -// and erases the target function type as well as the types of the bound -// parameters. It does this by storing a pointer to the specific Run() -// function, and upcasting the state of BindState<>* to a -// BindStateBase*. This is safe as long as this BindStateBase pointer -// is only used with the stored Run() pointer. -// -// To BindState<> objects are created inside the Bind() functions. -// These functions, along with a set of internal templates, are responsible for -// -// - Unwrapping the function signature into return type, and parameters -// - Determining the number of parameters that are bound -// - Creating the BindState storing the bound parameters -// - Performing compile-time asserts to avoid error-prone behavior -// - Returning an Callback<> with an arity matching the number of unbound -// parameters and that knows the correct refcounting semantics for the -// target object if we are binding a method. -// -// The Bind functions do the above using type-inference, and template -// specializations. -// -// By default Bind() will store copies of all bound parameters, and attempt -// to refcount a target object if the function being bound is a class method. -// These copies are created even if the function takes parameters as const -// references. (Binding to non-const references is forbidden, see bind.h.) -// -// To change this behavior, we introduce a set of argument wrappers -// (e.g., Unretained(), and ConstRef()). These are simple container templates -// that are passed by value, and wrap a pointer to argument. See the -// file-level comment in base/bind_helpers.h for more info. -// -// These types are passed to the Unwrap() functions, and the MaybeRefcount() -// functions respectively to modify the behavior of Bind(). The Unwrap() -// and MaybeRefcount() functions change behavior by doing partial -// specialization based on whether or not a parameter is a wrapper type. -// -// ConstRef() is similar to tr1::cref. Unretained() is specific to Chromium. -// -// -// WHY NOT TR1 FUNCTION/BIND? -// -// Direct use of tr1::function and tr1::bind was considered, but ultimately -// rejected because of the number of copy constructors invocations involved -// in the binding of arguments during construction, and the forwarding of -// arguments during invocation. These copies will no longer be an issue in -// C++0x because C++0x will support rvalue reference allowing for the compiler -// to avoid these copies. However, waiting for C++0x is not an option. -// -// Measured with valgrind on gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5), the -// tr1::bind call itself will invoke a non-trivial copy constructor three times -// for each bound parameter. Also, each when passing a tr1::function, each -// bound argument will be copied again. -// -// In addition to the copies taken at binding and invocation, copying a -// tr1::function causes a copy to be made of all the bound parameters and -// state. -// -// Furthermore, in Chromium, it is desirable for the Callback to take a -// reference on a target object when representing a class method call. This -// is not supported by tr1. -// -// Lastly, tr1::function and tr1::bind has a more general and flexible API. -// This includes things like argument reordering by use of -// tr1::bind::placeholder, support for non-const reference parameters, and some -// limited amount of subtyping of the tr1::function object (e.g., -// tr1::function is convertible to tr1::function). -// -// These are not features that are required in Chromium. Some of them, such as -// allowing for reference parameters, and subtyping of functions, may actually -// become a source of errors. Removing support for these features actually -// allows for a simpler implementation, and a terser Currying API. -// -// -// WHY NOT GOOGLE CALLBACKS? -// -// The Google callback system also does not support refcounting. Furthermore, -// its implementation has a number of strange edge cases with respect to type -// conversion of its arguments. In particular, the argument's constness must -// at times match exactly the function signature, or the type-inference might -// break. Given the above, writing a custom solution was easier. -// -// -// MISSING FUNCTIONALITY -// - Invoking the return of Bind. Bind(&foo).Run() does not work; -// - Binding arrays to functions that take a non-const pointer. -// Example: -// void Foo(const char* ptr); -// void Bar(char* ptr); -// Bind(&Foo, "test"); -// Bind(&Bar, "test"); // This fails because ptr is not const. +#include "include/base/cef_bind.h" +#include "include/base/cef_callback_forward.h" +#include "include/base/cef_compiler_specific.h" +#include "include/base/cef_logging.h" +#include "include/base/internal/cef_callback_internal.h" +#include "include/base/internal/cef_callback_tags.h" namespace base { -// First, we forward declare the Callback class template. This informs the -// compiler that the template only has 1 type parameter which is the function -// signature that the Callback is representing. -// -// After this, create template specializations for 0-7 parameters. Note that -// even though the template typelist grows, the specialization still -// only has one type: the function signature. -// -// If you are thinking of forward declaring Callback in your own header file, -// please include "base/callback_forward.h" instead. -template -class Callback; - namespace cef_internal { -template -struct BindState; + +template +auto ToDoNothingCallback( + DoNothingCallbackTag::WithBoundArguments t); + } // namespace cef_internal -template -class Callback : public cef_internal::CallbackBase { +template +class TRIVIAL_ABI OnceCallback { public: - typedef R(RunType)(); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); + using ResultType = R; + using RunType = R(Args...); + using PolymorphicInvoke = R (*)(cef_internal::BindStateBase*, + cef_internal::PassingType...); + + // Constructs a null `OnceCallback`. A null callback has no associated functor + // and cannot be run. + constexpr OnceCallback() = default; + // Disallowed to prevent ambiguity. + OnceCallback(std::nullptr_t) = delete; + + // `OnceCallback` is not copyable since its bound functor may only run at most + // once. + OnceCallback(const OnceCallback&) = delete; + OnceCallback& operator=(const OnceCallback&) = delete; + + // Subtle: since `this` is marked as TRIVIAL_ABI, the move operations + // must leave the moved-from callback in a trivially destructible state. + OnceCallback(OnceCallback&&) noexcept = default; + OnceCallback& operator=(OnceCallback&&) noexcept = default; + + ~OnceCallback() = default; + + // A `OnceCallback` is a strict subset of `RepeatingCallback`'s functionality, + // so allow seamless conversion. + // NOLINTNEXTLINE(google-explicit-constructor) + OnceCallback(RepeatingCallback other) + : holder_(std::move(other.holder_)) {} + OnceCallback& operator=(RepeatingCallback other) { + holder_ = std::move(other.holder_); + return *this; } - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + // Returns true if `this` is non-null and can be `Run()`. + explicit operator bool() const { return !!holder_; } + // Returns true if `this` is null and cannot be `Run()`. + bool is_null() const { return holder_.is_null(); } + + // Returns true if calling `Run()` is a no-op because of cancellation. + // + // - Not thread-safe, i.e. must be called on the same sequence that will + // ultimately `Run()` the callback + // - May not be called on a null callback. + bool IsCancelled() const { return holder_.IsCancelled(); } + + // Subtle version of `IsCancelled()` that allows cancellation state to be + // queried from any sequence. May return true even if the callback has + // actually been cancelled. + // + // Do not use. This is intended for internal //base usage. + // TODO(dcheng): Restrict this since it, in fact, being used outside of its + // originally intended use. + bool MaybeValid() const { return holder_.MaybeValid(); } + + // Resets this to a null state. + REINITIALIZES_AFTER_MOVE void Reset() { holder_.Reset(); } + + // Non-consuming `Run()` is disallowed for `OnceCallback`. + R Run(Args... args) const& { + static_assert(!sizeof(*this), + "OnceCallback::Run() may only be invoked on a non-const " + "rvalue, i.e. std::move(callback).Run()."); + NOTREACHED(); } - R Run() const { + // Calls the bound functor with any already-bound arguments + `args`. Consumes + // `this`, i.e. `this` becomes null. + // + // May not be called on a null callback. + R Run(Args... args) && { + CHECK(!is_null()); + + // Move the callback instance into a local variable before the invocation, + // that ensures the internal state is cleared after the invocation. + // It's not safe to touch |this| after the invocation, since running the + // bound function may destroy |this|. + cef_internal::BindStateHolder holder = std::move(holder_); PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); - - return f(bind_state_.get()); + reinterpret_cast(holder.polymorphic_invoke()); + return f(holder.bind_state().get(), std::forward(args)...); } - private: - typedef R (*PolymorphicInvoke)(cef_internal::BindStateBase*); -}; - -template -class Callback : public cef_internal::CallbackBase { - public: - typedef R(RunType)(A1); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); + // Then() returns a new OnceCallback that receives the same arguments as + // |this|, and with the return type of |then|. The returned callback will: + // 1) Run the functor currently bound to |this| callback. + // 2) Run the |then| callback with the result from step 1 as its single + // argument. + // 3) Return the value from running the |then| callback. + // + // Since this method generates a callback that is a replacement for `this`, + // `this` will be consumed and reset to a null callback to ensure the + // originally-bound functor can be run at most once. + template + OnceCallback Then(OnceCallback then) && { + CHECK(then); + return base::BindOnce( + cef_internal::ThenHelper< + OnceCallback, OnceCallback>::CreateTrampoline(), + std::move(*this), std::move(then)); } - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + // This overload is required; even though RepeatingCallback is implicitly + // convertible to OnceCallback, that conversion will not used when matching + // for template argument deduction. + template + OnceCallback Then( + RepeatingCallback then) && { + CHECK(then); + return base::BindOnce( + cef_internal::ThenHelper< + OnceCallback, + RepeatingCallback>::CreateTrampoline(), + std::move(*this), std::move(then)); } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1) const { - PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); + // Internal constructors for various callback helper tag types, e.g. + // `base::DoNothing()`. - return f(bind_state_.get(), cef_internal::CallbackForward(a1)); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr OnceCallback(cef_internal::NullCallbackTag) : OnceCallback() {} + constexpr OnceCallback& operator=(cef_internal::NullCallbackTag) { + *this = OnceCallback(); + return *this; } - private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType); -}; - -template -class Callback : public cef_internal::CallbackBase { - public: - typedef R(RunType)(A1, A2); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr OnceCallback(cef_internal::NullCallbackTag::WithSignature) + : OnceCallback(cef_internal::NullCallbackTag()) {} + constexpr OnceCallback& operator=( + cef_internal::NullCallbackTag::WithSignature) { + *this = cef_internal::NullCallbackTag(); + return *this; } - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr OnceCallback(cef_internal::DoNothingCallbackTag) + requires(std::is_void_v) + : OnceCallback(BindOnce([](Args... args) {})) {} + constexpr OnceCallback& operator=(cef_internal::DoNothingCallbackTag) + requires(std::is_void_v) + { + *this = BindOnce([](Args... args) {}); + return *this; } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2) const { - PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); - - return f(bind_state_.get(), cef_internal::CallbackForward(a1), - cef_internal::CallbackForward(a2)); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr OnceCallback( + cef_internal::DoNothingCallbackTag::WithSignature) + requires(std::is_void_v) + : OnceCallback(cef_internal::DoNothingCallbackTag()) {} + constexpr OnceCallback& operator=( + cef_internal::DoNothingCallbackTag::WithSignature) + requires(std::is_void_v) + { + *this = cef_internal::DoNothingCallbackTag(); + return *this; } - private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType); -}; - -template -class Callback : public cef_internal::CallbackBase { - public: - typedef R(RunType)(A1, A2, A3); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); - } - - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + template + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr OnceCallback( + cef_internal::DoNothingCallbackTag::WithBoundArguments tag) + requires(std::is_void_v) + : OnceCallback(cef_internal::ToDoNothingCallback( + std::move(tag))) {} + template + constexpr OnceCallback& operator=( + cef_internal::DoNothingCallbackTag::WithBoundArguments tag) + requires(std::is_void_v) + { + *this = cef_internal::ToDoNothingCallback(std::move(tag)); + return *this; } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3) const { - PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); - - return f(bind_state_.get(), cef_internal::CallbackForward(a1), - cef_internal::CallbackForward(a2), - cef_internal::CallbackForward(a3)); - } + // Internal constructor for `base::BindOnce()`. + explicit OnceCallback(cef_internal::BindStateBase* bind_state) + : holder_(bind_state) {} private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType); + cef_internal::BindStateHolder holder_; }; -template -class Callback : public cef_internal::CallbackBase { +template +class TRIVIAL_ABI RepeatingCallback { public: - typedef R(RunType)(A1, A2, A3, A4); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); - } + using ResultType = R; + using RunType = R(Args...); + using PolymorphicInvoke = R (*)(cef_internal::BindStateBase*, + cef_internal::PassingType...); + + // Constructs a null `RepeatingCallback`. A null callback has no associated + // functor and cannot be run. + constexpr RepeatingCallback() = default; + // Disallowed to prevent ambiguity. + RepeatingCallback(std::nullptr_t) = delete; + + // Unlike a `OnceCallback`, a `RepeatingCallback` may be copied since its + // bound functor may be run more than once. + RepeatingCallback(const RepeatingCallback&) = default; + RepeatingCallback& operator=(const RepeatingCallback&) = default; + + // Subtle: since `this` is marked as TRIVIAL_ABI, the move operations + // must leave the moved-from callback in a trivially destructible state. + RepeatingCallback(RepeatingCallback&&) noexcept = default; + RepeatingCallback& operator=(RepeatingCallback&&) noexcept = default; + + ~RepeatingCallback() = default; + + // Returns true if `this` is non-null and can be `Run()`. + explicit operator bool() const { return !!holder_; } + // Returns true if `this` is null and cannot be `Run()`. + bool is_null() const { return holder_.is_null(); } + + // Returns true if calling `Run()` is a no-op because of cancellation. + // + // - Not thread-safe, i.e. must be called on the same sequence that will + // ultimately `Run()` the callback + // - May not be called on a null callback. + bool IsCancelled() const { return holder_.IsCancelled(); } + + // Subtle version of `IsCancelled()` that allows cancellation state to be + // queried from any sequence. May return true even if the callback has + // actually been cancelled. + // + // Do not use. This is intended for internal //base usage. + // TODO(dcheng): Restrict this since it, in fact, being used outside of its + // originally intended use. + bool MaybeValid() const { return holder_.MaybeValid(); } + + // Equality operators: two `RepeatingCallback`'s are equal + friend bool operator==(const RepeatingCallback&, + const RepeatingCallback&) = default; + + // Resets this to null. + REINITIALIZES_AFTER_MOVE void Reset() { holder_.Reset(); } + + // Calls the bound functor with any already-bound arguments + `args`. Does not + // consume `this`, i.e. this remains non-null. + // + // May not be called on a null callback. + R Run(Args... args) const& { + CHECK(!is_null()); + + // Keep `bind_state` alive at least until after the invocation to ensure all + // bound `Unretained` arguments remain valid. + scoped_refptr bind_state = + holder_.bind_state(); - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + PolymorphicInvoke f = + reinterpret_cast(holder_.polymorphic_invoke()); + return f(bind_state.get(), std::forward(args)...); } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4) const { + // Calls the bound functor with any already-bound arguments + `args`. Consumes + // `this`, i.e. `this` becomes null. + // + // May not be called on a null callback. + R Run(Args... args) && { + CHECK(!holder_.is_null()); + + // Move the callback instance into a local variable before the invocation, + // that ensures the internal state is cleared after the invocation. + // It's not safe to touch |this| after the invocation, since running the + // bound function may destroy |this|. + cef_internal::BindStateHolder holder = std::move(holder_); PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); - - return f(bind_state_.get(), cef_internal::CallbackForward(a1), - cef_internal::CallbackForward(a2), - cef_internal::CallbackForward(a3), - cef_internal::CallbackForward(a4)); + reinterpret_cast(holder.polymorphic_invoke()); + return f(holder.bind_state().get(), std::forward(args)...); } - private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType); -}; - -template -class Callback : public cef_internal::CallbackBase { - public: - typedef R(RunType)(A1, A2, A3, A4, A5); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); + // Then() returns a new RepeatingCallback that receives the same arguments as + // |this|, and with the return type of |then|. The + // returned callback will: + // 1) Run the functor currently bound to |this| callback. + // 2) Run the |then| callback with the result from step 1 as its single + // argument. + // 3) Return the value from running the |then| callback. + // + // If called on an rvalue (e.g. std::move(cb).Then(...)), this method + // generates a callback that is a replacement for `this`. Therefore, `this` + // will be consumed and reset to a null callback to ensure the + // originally-bound functor will be run at most once. + template + RepeatingCallback Then( + RepeatingCallback then) const& { + CHECK(then); + return BindRepeating( + cef_internal::ThenHelper< + RepeatingCallback, + RepeatingCallback>::CreateTrampoline(), + *this, std::move(then)); } - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + template + RepeatingCallback Then( + RepeatingCallback then) && { + CHECK(then); + return BindRepeating( + cef_internal::ThenHelper< + RepeatingCallback, + RepeatingCallback>::CreateTrampoline(), + std::move(*this), std::move(then)); } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4, - typename cef_internal::CallbackParamTraits::ForwardType a5) const { - PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); + // Internal constructors for various callback helper tag types, e.g. + // `base::DoNothing()`. - return f( - bind_state_.get(), cef_internal::CallbackForward(a1), - cef_internal::CallbackForward(a2), cef_internal::CallbackForward(a3), - cef_internal::CallbackForward(a4), cef_internal::CallbackForward(a5)); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr RepeatingCallback(cef_internal::NullCallbackTag) + : RepeatingCallback() {} + constexpr RepeatingCallback& operator=(cef_internal::NullCallbackTag) { + *this = RepeatingCallback(); + return *this; } - private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType); -}; - -template -class Callback : public cef_internal::CallbackBase { - public: - typedef R(RunType)(A1, A2, A3, A4, A5, A6); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr RepeatingCallback( + cef_internal::NullCallbackTag::WithSignature) + : RepeatingCallback(cef_internal::NullCallbackTag()) {} + constexpr RepeatingCallback& operator=( + cef_internal::NullCallbackTag::WithSignature) { + *this = cef_internal::NullCallbackTag(); + return *this; } - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr RepeatingCallback(cef_internal::DoNothingCallbackTag) + requires(std::is_void_v) + : RepeatingCallback(BindRepeating([](Args... args) {})) {} + constexpr RepeatingCallback& operator=(cef_internal::DoNothingCallbackTag) + requires(std::is_void_v) + { + *this = BindRepeating([](Args... args) {}); + return *this; } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4, - typename cef_internal::CallbackParamTraits::ForwardType a5, - typename cef_internal::CallbackParamTraits::ForwardType a6) const { - PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); - - return f( - bind_state_.get(), cef_internal::CallbackForward(a1), - cef_internal::CallbackForward(a2), cef_internal::CallbackForward(a3), - cef_internal::CallbackForward(a4), cef_internal::CallbackForward(a5), - cef_internal::CallbackForward(a6)); - } - - private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType); -}; - -template -class Callback - : public cef_internal::CallbackBase { - public: - typedef R(RunType)(A1, A2, A3, A4, A5, A6, A7); - - Callback() : CallbackBase(NULL) {} - - // Note that this constructor CANNOT be explicit, and that Bind() CANNOT - // return the exact Callback<> type. See base/bind.h for details. - template - Callback( - cef_internal::BindState* bind_state) - : CallbackBase(bind_state) { - // Force the assignment to a local variable of PolymorphicInvoke - // so the compiler will typecheck that the passed in Run() method has - // the correct type. - PolymorphicInvoke invoke_func = - &cef_internal::BindState::InvokerType::Run; - polymorphic_invoke_ = reinterpret_cast(invoke_func); + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr RepeatingCallback( + cef_internal::DoNothingCallbackTag::WithSignature) + requires(std::is_void_v) + : RepeatingCallback(cef_internal::DoNothingCallbackTag()) {} + constexpr RepeatingCallback& operator=( + cef_internal::DoNothingCallbackTag::WithSignature) + requires(std::is_void_v) + { + *this = cef_internal::DoNothingCallbackTag(); + return *this; } - bool Equals(const Callback& other) const { - return CallbackBase::Equals(other); + template + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr RepeatingCallback( + cef_internal::DoNothingCallbackTag::WithBoundArguments tag) + requires(std::is_void_v) + : RepeatingCallback(cef_internal::ToDoNothingCallback( + std::move(tag))) {} + template + constexpr RepeatingCallback& operator=( + cef_internal::DoNothingCallbackTag::WithBoundArguments tag) + requires(std::is_void_v) + { + *this = + cef_internal::ToDoNothingCallback(std::move(tag)); + return this; } - R Run(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4, - typename cef_internal::CallbackParamTraits::ForwardType a5, - typename cef_internal::CallbackParamTraits::ForwardType a6, - typename cef_internal::CallbackParamTraits::ForwardType a7) const { - PolymorphicInvoke f = - reinterpret_cast(polymorphic_invoke_); - - return f( - bind_state_.get(), cef_internal::CallbackForward(a1), - cef_internal::CallbackForward(a2), cef_internal::CallbackForward(a3), - cef_internal::CallbackForward(a4), cef_internal::CallbackForward(a5), - cef_internal::CallbackForward(a6), cef_internal::CallbackForward(a7)); - } + // Internal constructor for `base::BindRepeating()`. + explicit RepeatingCallback(cef_internal::BindStateBase* bind_state) + : holder_(bind_state) {} private: - typedef R (*PolymorphicInvoke)( - cef_internal::BindStateBase*, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType, - typename cef_internal::CallbackParamTraits::ForwardType); + friend class OnceCallback; + + cef_internal::BindStateHolder holder_; }; -// Syntactic sugar to make Callbacks easier to declare since it -// will be used in a lot of APIs with delayed execution. -typedef Callback Closure; +namespace cef_internal { + +// Helper for the `DoNothingWithBoundArgs()` implementation. +// Unlike the other helpers, this cannot be easily moved to callback_internal.h, +// since it depends on `BindOnce()` and `BindRepeating()`. +template +auto ToDoNothingCallback( + DoNothingCallbackTag::WithBoundArguments t) { + return std::apply( + [](auto&&... args) { + if constexpr (is_once) { + return base::BindOnce( + [](TransformToUnwrappedType..., + UnboundArgs...) {}, + std::move(args)...); + } else { + return base::BindRepeating( + [](TransformToUnwrappedType..., + UnboundArgs...) {}, + std::move(args)...); + } + }, + std::move(t.bound_args)); +} + +} // namespace cef_internal } // namespace base diff --git a/src/include/base/cef_callback_forward.h b/src/include/base/cef_callback_forward.h index d604d7cfa..eb49e932c 100644 --- a/src/include/base/cef_callback_forward.h +++ b/src/include/base/cef_callback_forward.h @@ -28,18 +28,13 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef INCLUDE_BASE_CEF_CALLBACK_FORWARD_H_ -#define INCLUDE_BASE_CEF_CALLBACK_FORWARD_H_ +#ifndef CEF_INCLUDE_BASE_CEF_CALLBACK_FORWARD_H_ +#define CEF_INCLUDE_BASE_CEF_CALLBACK_FORWARD_H_ #pragma once -#if defined(BASE_CALLBACK_FORWARD_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) +#if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. -#include "base/callback_forward.h" +#include "base/functional/callback_forward.h" #else // !USING_CHROMIUM_INCLUDES // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be @@ -47,13 +42,22 @@ namespace base { -template -class Callback; +template +class OnceCallback; -typedef Callback Closure; +template +class RepeatingCallback; + +/// +/// Syntactic sugar to make OnceClosure and RepeatingClosure +/// easier to declare since they will be used in a lot of APIs with delayed +/// execution. +/// +using OnceClosure = OnceCallback; +using RepeatingClosure = RepeatingCallback; } // namespace base -#endif // !!USING_CHROMIUM_INCLUDES +#endif // !USING_CHROMIUM_INCLUDES -#endif // INCLUDE_BASE_CEF_CALLBACK_FORWARD_H_ +#endif // CEF_INCLUDE_BASE_CEF_CALLBACK_FORWARD_H_ diff --git a/src/include/base/cef_callback_helpers.h b/src/include/base/cef_callback_helpers.h index ebe074a1f..51b10e985 100644 --- a/src/include/base/cef_callback_helpers.h +++ b/src/include/base/cef_callback_helpers.h @@ -32,60 +32,332 @@ // are implemented using templates, with a class per callback signature, adding // methods to Callback<> itself is unattractive (lots of extra code gets // generated). Instead, consider adding methods here. -// -// ResetAndReturn(&cb) is like cb.Reset() but allows executing a callback (via a -// copy) after the original callback is Reset(). This can be handy if Run() -// reads/writes the variable holding the Callback. #ifndef CEF_INCLUDE_BASE_CEF_CALLBACK_HELPERS_H_ #define CEF_INCLUDE_BASE_CEF_CALLBACK_HELPERS_H_ #pragma once -#if defined(BASE_CALLBACK_HELPERS_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) +#if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. -#include "base/callback_helpers.h" +#include "base/functional/callback_helpers.h" #else // !USING_CHROMIUM_INCLUDES // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be // updated to match. -#include "include/base/cef_basictypes.h" -#include "include/base/cef_build.h" +#include +#include +#include +#include +#include +#include + +#include "include/base/cef_bind.h" #include "include/base/cef_callback.h" -#include "include/base/cef_macros.h" +#include "include/base/cef_logging.h" +#include "include/base/internal/cef_callback_tags.h" namespace base { -template -base::Callback ResetAndReturn(base::Callback* cb) { - base::Callback ret(*cb); - cb->Reset(); - return ret; +namespace cef_internal { + +template +struct IsBaseCallbackImpl : std::false_type {}; + +template +struct IsBaseCallbackImpl> : std::true_type {}; + +template +struct IsBaseCallbackImpl> : std::true_type {}; + +} // namespace cef_internal + +/// +/// IsBaseCallback is satisfied if and only if T is an instantiation of +/// base::OnceCallback or base::RepeatingCallback. +/// +template +concept IsBaseCallback = + cef_internal::IsBaseCallbackImpl>::value; + +namespace cef_internal { + +template +class OnceCallbackHolder final { + public: + OnceCallbackHolder(OnceCallback callback, + bool ignore_extra_runs) + : callback_(std::move(callback)), ignore_extra_runs_(ignore_extra_runs) { + DCHECK(callback_); + } + OnceCallbackHolder(const OnceCallbackHolder&) = delete; + OnceCallbackHolder& operator=(const OnceCallbackHolder&) = delete; + + void Run(Args... args) { + if (has_run_.exchange(true, std::memory_order_relaxed)) { + CHECK(ignore_extra_runs_) << "Both OnceCallbacks returned by " + "base::SplitOnceCallback() were run. " + "At most one of the pair should be run."; + return; + } + DCHECK(callback_); + std::move(callback_).Run(std::forward(args)...); + } + + private: + std::atomic has_run_{false}; + base::OnceCallback callback_; + const bool ignore_extra_runs_; +}; + +template +void ForwardRepeatingCallbacksImpl( + std::vector> cbs, + Args... args) { + for (auto& cb : cbs) { + if (cb) { + cb.Run(std::forward(args)...); + } + } +} + +} // namespace cef_internal + +/// +/// Wraps the given RepeatingCallbacks and return one RepeatingCallbacks with an +/// identical signature. On invocation of this callback, all the given +/// RepeatingCallbacks will be called with the same arguments. Unbound arguments +/// must be copyable. +/// +template +RepeatingCallback ForwardRepeatingCallbacks( + std::initializer_list>&& cbs) { + std::vector> v( + std::forward>>( + cbs)); + return BindRepeating(&cef_internal::ForwardRepeatingCallbacksImpl, + std::move(v)); +} + +/// +/// Wraps the given OnceCallback and returns two OnceCallbacks with an identical +/// signature. On first invokation of either returned callbacks, the original +/// callback is invoked. Invoking the remaining callback results in a crash. +/// +template +std::pair, OnceCallback> +SplitOnceCallback(OnceCallback callback) { + if (!callback) { + // Empty input begets two empty outputs. + return std::make_pair(OnceCallback(), + OnceCallback()); + } + using Helper = cef_internal::OnceCallbackHolder; + auto wrapped_once = base::BindRepeating( + &Helper::Run, std::make_unique(std::move(callback), + /*ignore_extra_runs=*/false)); + return std::make_pair(wrapped_once, wrapped_once); +} + +/// +/// Adapts `callback` for use in a context which is expecting a callback with +/// additional parameters. Returns a null callback if `callback` is null. +/// +/// Usage: +/// +///
+///   bool LogError(char* error_message) {
+///     if (error_message) {
+///       cout << "Log: " << error_message << endl;
+///       return false;
+///     }
+///     return true;
+///   }
+///   base::RepeatingCallback cb =
+///      base::IgnoreArgs(base::BindRepeating(&LogError));
+///   CHECK_EQ(true, cb.Run(42, nullptr));
+/// 
+/// +/// Note in the example above that the type(s) passed to `IgnoreArgs` +/// represent the additional prepended parameters (those which will be +/// "ignored"). +/// +template +RepeatingCallback IgnoreArgs( + RepeatingCallback callback) { + return callback ? ::base::BindRepeating( + [](RepeatingCallback callback, Preargs..., + Args... args) { + return std::move(callback).Run( + std::forward(args)...); + }, + std::move(callback)) + : RepeatingCallback(); +} + +/// As above, but for OnceCallback. +template +OnceCallback IgnoreArgs( + OnceCallback callback) { + return callback ? ::base::BindOnce( + [](OnceCallback callback, Preargs..., + Args... args) { + return std::move(callback).Run( + std::forward(args)...); + }, + std::move(callback)) + : OnceCallback(); } -// ScopedClosureRunner is akin to scoped_ptr for Closures. It ensures that the -// Closure is executed and deleted no matter how the current scope exits. +/// +/// ScopedClosureRunner is akin to std::unique_ptr<> for Closures. It ensures +/// that the Closure is executed no matter how the current scope exits. +/// If you are looking for "ScopedCallback", "CallbackRunner", or +/// "CallbackScoper" this is the class you want. +/// class ScopedClosureRunner { public: ScopedClosureRunner(); - explicit ScopedClosureRunner(const Closure& closure); + [[nodiscard]] explicit ScopedClosureRunner(OnceClosure closure); + ScopedClosureRunner(ScopedClosureRunner&& other); + // Runs the current closure if it's set, then replaces it with the closure + // from |other|. This is akin to how unique_ptr frees the contained pointer in + // its move assignment operator. If you need to explicitly avoid running any + // current closure, use ReplaceClosure(). + ScopedClosureRunner& operator=(ScopedClosureRunner&& other); ~ScopedClosureRunner(); - void Reset(); - void Reset(const Closure& closure); - Closure Release() WARN_UNUSED_RESULT; + explicit operator bool() const { return !!closure_; } - private: - Closure closure_; + // Calls the current closure and resets it, so it wont be called again. + void RunAndReset(); + + // Replaces closure with the new one releasing the old one without calling it. + void ReplaceClosure(OnceClosure closure); + + // Releases the Closure without calling. + [[nodiscard]] OnceClosure Release(); - DISALLOW_COPY_AND_ASSIGN(ScopedClosureRunner); + private: + OnceClosure closure_; }; +/// +/// Returns a placeholder type that will implicitly convert into a null +/// callback, similar to how std::nullopt / std::nullptr work in conjunction +/// with std::optional and various smart pointer types. +/// +constexpr auto NullCallback() { + return cef_internal::NullCallbackTag(); +} + +/// +/// Returns a placeholder type that will implicitly convert into a callback that +/// does nothing, similar to how std::nullopt / std::nullptr work in conjunction +/// with std::optional and various smart pointer types. +/// +constexpr auto DoNothing() { + return cef_internal::DoNothingCallbackTag(); +} + +/// +/// Similar to the above, but with a type hint. Useful for disambiguating +/// among multiple function overloads that take callbacks with different +/// signatures: +/// +///
+/// void F(base::OnceCallback callback);     // 1
+/// void F(base::OnceCallback callback);  // 2
+///
+/// F(base::NullCallbackAs());               // calls 1
+/// F(base::DoNothingAs());               // calls 2
+/// 
+/// +template +constexpr auto NullCallbackAs() { + return cef_internal::NullCallbackTag::WithSignature(); +} + +template +constexpr auto DoNothingAs() { + return cef_internal::DoNothingCallbackTag::WithSignature(); +} + +/// +/// Similar to DoNothing above, but with bound arguments. This helper is useful +/// for keeping objects alive until the callback runs. +/// Example: +/// +///
+/// void F(base::OnceCallback result_callback);
+///
+/// std::unique_ptr ptr;
+/// F(base::DoNothingWithBoundArgs(std::move(ptr)));
+/// 
+/// +template +constexpr auto DoNothingWithBoundArgs(Args&&... args) { + return cef_internal::DoNothingCallbackTag::WithBoundArguments( + std::forward(args)...); +} + +/// +/// Creates a callback that returns `value` when invoked. This helper is useful +/// for implementing factories that return a constant value. +/// Example: +/// +///
+/// void F(base::OnceCallback factory);
+///
+/// Widget widget = ...;
+/// F(base::ReturnValueOnce(std::move(widget)));
+/// 
+/// +template +constexpr OnceCallback ReturnValueOnce(T value) { + static_assert(!std::is_reference_v); + return base::BindOnce([](T value) { return value; }, std::move(value)); +} + +/// +/// Useful for creating a Closure that will delete a pointer when invoked. Only +/// use this when necessary. In most cases MessageLoop::DeleteSoon() is a better +/// fit. +/// +template +void DeletePointer(T* obj) { + delete obj; +} + +#if __OBJC__ + +/// +/// Creates an Objective-C block with the same signature as the corresponding +/// callback. Can be used to implement a callback based API internally based +/// on a block based Objective-C API. +/// +/// Overloaded to work with both repeating and one shot callbacks. Calling the +/// block wrapping a base::OnceCallback<...> multiple times will crash (there +/// is no way to mark the block as callable only once). Only use that when you +/// know that Objective-C API will only invoke the block once. +/// +template +auto CallbackToBlock(base::OnceCallback callback) { + __block base::OnceCallback block_callback = std::move(callback); + return ^(Args... args) { + return std::move(block_callback).Run(std::forward(args)...); + }; +} + +template +auto CallbackToBlock(base::RepeatingCallback callback) { + return ^(Args... args) { + return callback.Run(std::forward(args)...); + }; +} + +#endif // __OBJC__ + } // namespace base #endif // !USING_CHROMIUM_INCLUDES diff --git a/src/include/base/cef_callback_list.h b/src/include/base/cef_callback_list.h index e0ef3665b..2038acb7d 100644 --- a/src/include/base/cef_callback_list.h +++ b/src/include/base/cef_callback_list.h @@ -28,420 +28,374 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// +/// \file +/// A container for a list of callbacks. Provides callers the ability to +/// manually or automatically unregister callbacks at any time, including during +/// callback notification. +/// +/// TYPICAL USAGE: +/// +///
+/// class MyWidget {
+///  public:
+///   using CallbackList = base::RepeatingCallbackList;
+///
+///   // Registers |cb| to be called whenever NotifyFoo() is executed.
+///   CallbackListSubscription RegisterCallback(CallbackList::CallbackType cb) {
+///     return callback_list_.Add(std::move(cb));
+///   }
+///
+///  private:
+///   // Calls all registered callbacks, with |foo| as the supplied arg.
+///   void NotifyFoo(const Foo& foo) {
+///     callback_list_.Notify(foo);
+///   }
+///
+///   CallbackList callback_list_;
+/// };
+///
+///
+/// class MyWidgetListener {
+///  private:
+///   void OnFoo(const Foo& foo) {
+///     // Called whenever MyWidget::NotifyFoo() is executed, unless
+///     // |foo_subscription_| has been destroyed.
+///   }
+///
+///   // Automatically deregisters the callback when deleted (e.g. in
+///   // ~MyWidgetListener()).  Unretained(this) is safe here since the
+///   // ScopedClosureRunner does not outlive |this|.
+///   CallbackListSubscription foo_subscription_ =
+///       MyWidget::Get()->RegisterCallback(
+///           base::BindRepeating(&MyWidgetListener::OnFoo,
+///                               base::Unretained(this)));
+/// };
+/// 
+/// +/// UNSUPPORTED: +/// +/// * Destroying the CallbackList during callback notification. +/// +/// This is possible to support, but not currently necessary. +/// + #ifndef CEF_INCLUDE_BASE_CEF_CALLBACK_LIST_H_ #define CEF_INCLUDE_BASE_CEF_CALLBACK_LIST_H_ #pragma once -#if defined(BASE_CALLBACK_LIST_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) +#if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. -#include "base/callback_list.h" +#include "base/functional/callback_list.h" #else // !USING_CHROMIUM_INCLUDES // The following is substantially similar to the Chromium implementation. // If the Chromium implementation diverges the below implementation should be // updated to match. +#include #include +#include +#include -#include "include/base/cef_basictypes.h" -#include "include/base/cef_build.h" +#include "include/base/cef_auto_reset.h" +#include "include/base/cef_bind.h" #include "include/base/cef_callback.h" +#include "include/base/cef_callback_helpers.h" #include "include/base/cef_logging.h" -#include "include/base/cef_macros.h" -#include "include/base/cef_scoped_ptr.h" -#include "include/base/internal/cef_callback_internal.h" - -// OVERVIEW: -// -// A container for a list of callbacks. Unlike a normal STL vector or list, -// this container can be modified during iteration without invalidating the -// iterator. It safely handles the case of a callback removing itself -// or another callback from the list while callbacks are being run. -// -// TYPICAL USAGE: -// -// class MyWidget { -// public: -// ... -// -// typedef base::Callback OnFooCallback; -// -// scoped_ptr::Subscription> -// RegisterCallback(const OnFooCallback& cb) { -// return callback_list_.Add(cb); -// } -// -// private: -// void NotifyFoo(const Foo& foo) { -// callback_list_.Notify(foo); -// } -// -// base::CallbackList callback_list_; -// -// DISALLOW_COPY_AND_ASSIGN(MyWidget); -// }; -// -// -// class MyWidgetListener { -// public: -// MyWidgetListener::MyWidgetListener() { -// foo_subscription_ = MyWidget::GetCurrent()->RegisterCallback( -// base::Bind(&MyWidgetListener::OnFoo, this))); -// } -// -// MyWidgetListener::~MyWidgetListener() { -// // Subscription gets deleted automatically and will deregister -// // the callback in the process. -// } -// -// private: -// void OnFoo(const Foo& foo) { -// // Do something. -// } -// -// scoped_ptr::Subscription> -// foo_subscription_; -// -// DISALLOW_COPY_AND_ASSIGN(MyWidgetListener); -// }; +#include "include/base/cef_weak_ptr.h" namespace base { +namespace internal { +template +class CallbackListBase; +} // namespace internal + +template +class OnceCallbackList; + +template +class RepeatingCallbackList; + +// A trimmed-down version of ScopedClosureRunner that can be used to guarantee a +// closure is run on destruction. This is designed to be used by +// CallbackListBase to run CancelCallback() when this subscription dies; +// consumers can avoid callbacks on dead objects by ensuring the subscription +// returned by CallbackListBase::Add() does not outlive the bound object in the +// callback. A typical way to do this is to bind a callback to a member function +// on `this` and store the returned subscription as a member variable. +class CallbackListSubscription { + public: + CallbackListSubscription(); + CallbackListSubscription(CallbackListSubscription&& subscription); + CallbackListSubscription& operator=(CallbackListSubscription&& subscription); + ~CallbackListSubscription(); + + explicit operator bool() const { return !!closure_; } + + private: + template + friend class internal::CallbackListBase; + + explicit CallbackListSubscription(base::OnceClosure closure); + + void Run(); + + OnceClosure closure_; +}; -namespace cef_internal { +namespace internal { + +// From base/stl_util.h. +template +size_t EraseIf(std::list& container, Predicate pred) { + size_t old_size = container.size(); + container.remove_if(pred); + return old_size - container.size(); +} + +// A traits class to break circular type dependencies between CallbackListBase +// and its subclasses. +template +struct CallbackListTraits; + +// NOTE: It's important that Callbacks provide iterator stability when items are +// added to the end, so e.g. a std::vector<> is not suitable here. +template +struct CallbackListTraits> { + using CallbackType = OnceCallback; + using Callbacks = std::list; +}; +template +struct CallbackListTraits> { + using CallbackType = RepeatingCallback; + using Callbacks = std::list; +}; -template +template class CallbackListBase { public: - class Subscription { - public: - Subscription(CallbackListBase* list, - typename std::list::iterator iter) - : list_(list), iter_(iter) {} - - ~Subscription() { - if (list_->active_iterator_count_) { - iter_->Reset(); - } else { - list_->callbacks_.erase(iter_); - if (!list_->removal_callback_.is_null()) - list_->removal_callback_.Run(); - } - } + using CallbackType = + typename CallbackListTraits::CallbackType; + static_assert(IsBaseCallback::value, ""); + + // TODO(crbug.com/1103086): Update references to use this directly and by + // value, then remove. + using Subscription = CallbackListSubscription; - private: - CallbackListBase* list_; - typename std::list::iterator iter_; + CallbackListBase() = default; + CallbackListBase(const CallbackListBase&) = delete; + CallbackListBase& operator=(const CallbackListBase&) = delete; - DISALLOW_COPY_AND_ASSIGN(Subscription); - }; + ~CallbackListBase() { + // Destroying the list during iteration is unsupported and will cause a UAF. + CHECK(!iterating_); + } - // Add a callback to the list. The callback will remain registered until the - // returned Subscription is destroyed, which must occur before the - // CallbackList is destroyed. - scoped_ptr Add(const CallbackType& cb) WARN_UNUSED_RESULT { + // Registers |cb| for future notifications. Returns a CallbackListSubscription + // whose destruction will cancel |cb|. + [[nodiscard]] CallbackListSubscription Add(CallbackType cb) { DCHECK(!cb.is_null()); - return scoped_ptr( - new Subscription(this, callbacks_.insert(callbacks_.end(), cb))); + return CallbackListSubscription(base::BindOnce( + &CallbackListBase::CancelCallback, weak_ptr_factory_.GetWeakPtr(), + callbacks_.insert(callbacks_.end(), std::move(cb)))); } - // Sets a callback which will be run when a subscription list is changed. - void set_removal_callback(const Closure& callback) { - removal_callback_ = callback; + // Registers |cb| for future notifications. Provides no way for the caller to + // cancel, so this is only safe for cases where the callback is guaranteed to + // live at least as long as this list (e.g. if it's bound on the same object + // that owns the list). + // TODO(pkasting): Attempt to use Add() instead and see if callers can relax + // other lifetime/ordering mechanisms as a result. + void AddUnsafe(CallbackType cb) { + DCHECK(!cb.is_null()); + callbacks_.push_back(std::move(cb)); } - // Returns true if there are no subscriptions. This is only valid to call when - // not looping through the list. - bool empty() { - DCHECK_EQ(0, active_iterator_count_); - return callbacks_.empty(); + // Registers |removal_callback| to be run after elements are removed from the + // list of registered callbacks. + void set_removal_callback(const RepeatingClosure& removal_callback) { + removal_callback_ = removal_callback; } - protected: - // An iterator class that can be used to access the list of callbacks. - class Iterator { - public: - explicit Iterator(CallbackListBase* list) - : list_(list), list_iter_(list_->callbacks_.begin()) { - ++list_->active_iterator_count_; - } + // Returns whether the list of registered callbacks is empty (from an external + // perspective -- meaning no remaining callbacks are live). + bool empty() const { + return std::all_of(callbacks_.cbegin(), callbacks_.cend(), + [](const auto& callback) { return callback.is_null(); }); + } - Iterator(const Iterator& iter) - : list_(iter.list_), list_iter_(iter.list_iter_) { - ++list_->active_iterator_count_; + // Calls all registered callbacks that are not canceled beforehand. If any + // callbacks are unregistered, notifies any registered removal callback at the + // end. + // + // Arguments must be copyable, since they must be supplied to all callbacks. + // Move-only types would be destructively modified by passing them to the + // first callback and not reach subsequent callbacks as intended. + // + // Notify() may be called re-entrantly, in which case the nested call + // completes before the outer one continues. Callbacks are only ever added at + // the end and canceled callbacks are not pruned from the list until the + // outermost iteration completes, so existing iterators should never be + // invalidated. However, this does mean that a callback added during a nested + // call can be notified by outer calls -- meaning it will be notified about + // things that happened before it was added -- if its subscription outlives + // the reentrant Notify() call. + template + void Notify(RunArgs&&... args) { + if (empty()) { + return; // Nothing to do. } - ~Iterator() { - if (list_ && --list_->active_iterator_count_ == 0) { - list_->Compact(); + { + AutoReset iterating(&iterating_, true); + + // Skip any callbacks that are canceled during iteration. + // NOTE: Since RunCallback() may call Add(), it's not safe to cache the + // value of callbacks_.end() across loop iterations. + const auto next_valid = [this](const auto it) { + return std::find_if_not(it, callbacks_.end(), [](const auto& callback) { + return callback.is_null(); + }); + }; + for (auto it = next_valid(callbacks_.begin()); it != callbacks_.end(); + it = next_valid(it)) { + // NOTE: Intentionally does not call std::forward(args)..., + // since that would allow move-only arguments. + static_cast(this)->RunCallback(it++, args...); } } - CallbackType* GetNext() { - while ((list_iter_ != list_->callbacks_.end()) && list_iter_->is_null()) - ++list_iter_; - - CallbackType* cb = NULL; - if (list_iter_ != list_->callbacks_.end()) { - cb = &(*list_iter_); - ++list_iter_; - } - return cb; + // Re-entrant invocations shouldn't prune anything from the list. This can + // invalidate iterators from underneath higher call frames. It's safe to + // simply do nothing, since the outermost frame will continue through here + // and prune all null callbacks below. + if (iterating_) { + return; } - private: - CallbackListBase* list_; - typename std::list::iterator list_iter_; - }; - - CallbackListBase() : active_iterator_count_(0) {} - - ~CallbackListBase() { - DCHECK_EQ(0, active_iterator_count_); - DCHECK_EQ(0U, callbacks_.size()); - } - - // Returns an instance of a CallbackListBase::Iterator which can be used - // to run callbacks. - Iterator GetIterator() { return Iterator(this); } - - // Compact the list: remove any entries which were NULLed out during - // iteration. - void Compact() { - typename std::list::iterator it = callbacks_.begin(); - bool updated = false; - while (it != callbacks_.end()) { - if ((*it).is_null()) { - updated = true; - it = callbacks_.erase(it); - } else { - ++it; - } - - if (updated && !removal_callback_.is_null()) - removal_callback_.Run(); + // Any null callbacks remaining in the list were canceled due to + // Subscription destruction during iteration, and can safely be erased now. + const size_t erased_callbacks = + EraseIf(callbacks_, [](const auto& cb) { return cb.is_null(); }); + + // Run |removal_callback_| if any callbacks were canceled. Note that we + // cannot simply compare list sizes before and after iterating, since + // notification may result in Add()ing new callbacks as well as canceling + // them. Also note that if this is a OnceCallbackList, the OnceCallbacks + // that were executed above have all been removed regardless of whether + // they're counted in |erased_callbacks_|. + if (removal_callback_ && + (erased_callbacks || IsOnceCallback::value)) { + removal_callback_.Run(); // May delete |this|! } } - private: - std::list callbacks_; - int active_iterator_count_; - Closure removal_callback_; - - DISALLOW_COPY_AND_ASSIGN(CallbackListBase); -}; - -} // namespace cef_internal - -template -class CallbackList; - -template <> -class CallbackList - : public cef_internal::CallbackListBase> { - public: - typedef Callback CallbackType; - - CallbackList() {} + protected: + using Callbacks = typename CallbackListTraits::Callbacks; - void Notify() { - cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(); - } - } + // Holds non-null callbacks, which will be called during Notify(). + Callbacks callbacks_; private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); -}; - -template -class CallbackList - : public cef_internal::CallbackListBase> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1); + // Cancels the callback pointed to by |it|, which is guaranteed to be valid. + void CancelCallback(const typename Callbacks::iterator& it) { + if (static_cast(this)->CancelNullCallback(it)) { + return; } - } - - private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); -}; -template -class CallbackList - : public cef_internal::CallbackListBase> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1, a2); + if (iterating_) { + // Calling erase() here is unsafe, since the loop in Notify() may be + // referencing this same iterator, e.g. if adjacent callbacks' + // Subscriptions are both destroyed when the first one is Run(). Just + // reset the callback and let Notify() clean it up at the end. + it->Reset(); + } else { + callbacks_.erase(it); + if (removal_callback_) { + removal_callback_.Run(); // May delete |this|! + } } } - private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); -}; + // Set while Notify() is traversing |callbacks_|. Used primarily to avoid + // invalidating iterators that may be in use. + bool iterating_ = false; -template -class CallbackList - : public cef_internal::CallbackListBase> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1, a2, a3); - } - } + // Called after elements are removed from |callbacks_|. + RepeatingClosure removal_callback_; - private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); + WeakPtrFactory weak_ptr_factory_{this}; }; -template -class CallbackList - : public cef_internal::CallbackListBase> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1, a2, a3, a4); - } - } +} // namespace internal +template +class OnceCallbackList + : public internal::CallbackListBase> { private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); -}; - -template -class CallbackList - : public cef_internal::CallbackListBase< - Callback> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4, - typename cef_internal::CallbackParamTraits::ForwardType a5) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1, a2, a3, a4, a5); - } + friend internal::CallbackListBase; + using Traits = internal::CallbackListTraits; + + // Runs the current callback, which may cancel it or any other callbacks. + template + void RunCallback(typename Traits::Callbacks::iterator it, RunArgs&&... args) { + // OnceCallbacks still have Subscriptions with outstanding iterators; + // splice() removes them from |callbacks_| without invalidating those. + null_callbacks_.splice(null_callbacks_.end(), this->callbacks_, it); + + // NOTE: Intentionally does not call std::forward(args)...; see + // comments in Notify(). + std::move(*it).Run(args...); } - private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); -}; - -template -class CallbackList - : public cef_internal::CallbackListBase< - Callback> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4, - typename cef_internal::CallbackParamTraits::ForwardType a5, - typename cef_internal::CallbackParamTraits::ForwardType a6) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1, a2, a3, a4, a5, a6); + // If |it| refers to an already-canceled callback, does any necessary cleanup + // and returns true. Otherwise returns false. + bool CancelNullCallback(const typename Traits::Callbacks::iterator& it) { + if (it->is_null()) { + null_callbacks_.erase(it); + return true; } + return false; } - private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); + // Holds null callbacks whose Subscriptions are still alive, so the + // Subscriptions will still contain valid iterators. Only needed for + // OnceCallbacks, since RepeatingCallbacks are not canceled except by + // Subscription destruction. + typename Traits::Callbacks null_callbacks_; }; -template -class CallbackList - : public cef_internal::CallbackListBase< - Callback> { - public: - typedef Callback CallbackType; - - CallbackList() {} - - void Notify(typename cef_internal::CallbackParamTraits::ForwardType a1, - typename cef_internal::CallbackParamTraits::ForwardType a2, - typename cef_internal::CallbackParamTraits::ForwardType a3, - typename cef_internal::CallbackParamTraits::ForwardType a4, - typename cef_internal::CallbackParamTraits::ForwardType a5, - typename cef_internal::CallbackParamTraits::ForwardType a6, - typename cef_internal::CallbackParamTraits::ForwardType a7) { - typename cef_internal::CallbackListBase::Iterator it = - this->GetIterator(); - CallbackType* cb; - while ((cb = it.GetNext()) != NULL) { - cb->Run(a1, a2, a3, a4, a5, a6, a7); - } +template +class RepeatingCallbackList + : public internal::CallbackListBase> { + private: + friend internal::CallbackListBase; + using Traits = internal::CallbackListTraits; + // Runs the current callback, which may cancel it or any other callbacks. + template + void RunCallback(typename Traits::Callbacks::iterator it, RunArgs&&... args) { + // NOTE: Intentionally does not call std::forward(args)...; see + // comments in Notify(). + it->Run(args...); } - private: - DISALLOW_COPY_AND_ASSIGN(CallbackList); + // If |it| refers to an already-canceled callback, does any necessary cleanup + // and returns true. Otherwise returns false. + bool CancelNullCallback(const typename Traits::Callbacks::iterator& it) { + // Because at most one Subscription can point to a given callback, and + // RepeatingCallbacks are only reset by CancelCallback(), no one should be + // able to request cancellation of a canceled RepeatingCallback. + DCHECK(!it->is_null()); + return false; + } }; +/// +/// Syntactic sugar to parallel that used for Callbacks. +/// +using OnceClosureList = OnceCallbackList; +using RepeatingClosureList = RepeatingCallbackList; + } // namespace base #endif // !USING_CHROMIUM_INCLUDES diff --git a/src/include/base/cef_cancelable_callback.h b/src/include/base/cef_cancelable_callback.h index febce3a32..35631491f 100644 --- a/src/include/base/cef_cancelable_callback.h +++ b/src/include/base/cef_cancelable_callback.h @@ -27,54 +27,58 @@ // 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. -// -// CancelableCallback is a wrapper around base::Callback that allows -// cancellation of a callback. CancelableCallback takes a reference on the -// wrapped callback until this object is destroyed or Reset()/Cancel() are -// called. -// -// NOTE: -// -// Calling CancelableCallback::Cancel() brings the object back to its natural, -// default-constructed state, i.e., CancelableCallback::callback() will return -// a null callback. -// -// THREAD-SAFETY: -// -// CancelableCallback objects must be created on, posted to, cancelled on, and -// destroyed on the same thread. -// -// -// EXAMPLE USAGE: -// -// In the following example, the test is verifying that RunIntensiveTest() -// Quit()s the message loop within 4 seconds. The cancelable callback is posted -// to the message loop, the intensive test runs, the message loop is run, -// then the callback is cancelled. -// -// void TimeoutCallback(const std::string& timeout_message) { -// FAIL() << timeout_message; -// MessageLoop::current()->QuitWhenIdle(); -// } -// -// CancelableClosure timeout(base::Bind(&TimeoutCallback, "Test timed out.")); -// MessageLoop::current()->PostDelayedTask(FROM_HERE, timeout.callback(), -// 4000) // 4 seconds to run. -// RunIntensiveTest(); -// MessageLoop::current()->Run(); -// timeout.Cancel(); // Hopefully this is hit before the timeout callback runs. -// + +/// +/// \file +/// CancelableCallback is a wrapper around base::Callback that allows +/// cancellation of a callback. CancelableCallback takes a reference on the +/// wrapped callback until this object is destroyed or Reset()/Cancel() are +/// called. +/// +/// NOTE: +/// +/// Calling CancelableCallback::Cancel() brings the object back to its natural, +/// default-constructed state, i.e., CancelableCallback::callback() will return +/// a null callback. +/// +/// THREAD-SAFETY: +/// +/// CancelableCallback objects must be created on, posted to, cancelled on, and +/// destroyed on the same thread. +/// +/// +/// EXAMPLE USAGE: +/// +/// In the following example, the test is verifying that RunIntensiveTest() +/// Quit()s the message loop within 4 seconds. The cancelable callback is posted +/// to the message loop, the intensive test runs, the message loop is run, +/// then the callback is cancelled. +/// +///
+///   RunLoop run_loop;
+///
+///   void TimeoutCallback(const std::string& timeout_message) {
+///     FAIL() << timeout_message;
+///     run_loop.QuitWhenIdle();
+///   }
+///
+///   CancelableOnceClosure timeout(
+///       base::BindOnce(&TimeoutCallback, "Test timed out."));
+///   ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
+///                                                  timeout.callback(),
+///                                                  TimeDelta::FromSeconds(4));
+///   RunIntensiveTest();
+///   run_loop.Run();
+///   // Hopefully this is hit before the timeout callback runs.
+///   timeout.Cancel();
+/// 
+/// #ifndef CEF_INCLUDE_BASE_CEF_CANCELABLE_CALLBACK_H_ #define CEF_INCLUDE_BASE_CEF_CANCELABLE_CALLBACK_H_ #pragma once -#if defined(BASE_CANCELABLE_CALLBACK_H_) -// Do nothing if the Chromium header has already been included. -// This can happen in cases where Chromium code is used directly by the -// client application. When using Chromium code directly always include -// the Chromium header first to avoid type conflicts. -#elif defined(USING_CHROMIUM_INCLUDES) +#if defined(USING_CHROMIUM_INCLUDES) // When building CEF include the Chromium header directly. #include "base/cancelable_callback.h" #else // !USING_CHROMIUM_INCLUDES @@ -82,37 +86,36 @@ // If the Chromium implementation diverges the below implementation should be // updated to match. +#include + #include "include/base/cef_bind.h" -#include "include/base/cef_build.h" #include "include/base/cef_callback.h" +#include "include/base/cef_compiler_specific.h" #include "include/base/cef_logging.h" -#include "include/base/cef_macros.h" #include "include/base/cef_weak_ptr.h" #include "include/base/internal/cef_callback_internal.h" namespace base { +namespace internal { -template -class CancelableCallback; - -template <> -class CancelableCallback { +template +class CancelableCallbackImpl { public: - CancelableCallback() : weak_factory_(this) {} + CancelableCallbackImpl() = default; + CancelableCallbackImpl(const CancelableCallbackImpl&) = delete; + CancelableCallbackImpl& operator=(const CancelableCallbackImpl&) = delete; // |callback| must not be null. - explicit CancelableCallback(const base::Callback& callback) - : weak_factory_(this), callback_(callback) { - DCHECK(!callback.is_null()); - InitializeForwarder(); + explicit CancelableCallbackImpl(CallbackType callback) + : callback_(std::move(callback)) { + DCHECK(callback_); } - ~CancelableCallback() {} + ~CancelableCallbackImpl() = default; // Cancels and drops the reference to the wrapped callback. void Cancel() { - weak_factory_.InvalidateWeakPtrs(); - forwarder_.Reset(); + weak_ptr_factory_.InvalidateWeakPtrs(); callback_.Reset(); } @@ -121,170 +124,69 @@ class CancelableCallback { // Sets |callback| as the closure that may be cancelled. |callback| may not // be null. Outstanding and any previously wrapped callbacks are cancelled. - void Reset(const base::Callback& callback) { - DCHECK(!callback.is_null()); - + void Reset(CallbackType callback) { + DCHECK(callback); // Outstanding tasks (e.g., posted to a message loop) must not be called. Cancel(); - - // |forwarder_| is no longer valid after Cancel(), so re-bind. - InitializeForwarder(); - - callback_ = callback; + callback_ = std::move(callback); } // Returns a callback that can be disabled by calling Cancel(). - const base::Callback& callback() const { return forwarder_; } - - private: - void Forward() { callback_.Run(); } - - // Helper method to bind |forwarder_| using a weak pointer from - // |weak_factory_|. - void InitializeForwarder() { - forwarder_ = base::Bind(&CancelableCallback::Forward, - weak_factory_.GetWeakPtr()); - } - - // Used to ensure Forward() is not run when this object is destroyed. - base::WeakPtrFactory> weak_factory_; - - // The wrapper closure. - base::Callback forwarder_; - - // The stored closure that may be cancelled. - base::Callback callback_; - - DISALLOW_COPY_AND_ASSIGN(CancelableCallback); -}; - -template -class CancelableCallback { - public: - CancelableCallback() : weak_factory_(this) {} - - // |callback| must not be null. - explicit CancelableCallback(const base::Callback& callback) - : weak_factory_(this), callback_(callback) { - DCHECK(!callback.is_null()); - InitializeForwarder(); - } - - ~CancelableCallback() {} - - // Cancels and drops the reference to the wrapped callback. - void Cancel() { - weak_factory_.InvalidateWeakPtrs(); - forwarder_.Reset(); - callback_.Reset(); - } - - // Returns true if the wrapped callback has been cancelled. - bool IsCancelled() const { return callback_.is_null(); } - - // Sets |callback| as the closure that may be cancelled. |callback| may not - // be null. Outstanding and any previously wrapped callbacks are cancelled. - void Reset(const base::Callback& callback) { - DCHECK(!callback.is_null()); - - // Outstanding tasks (e.g., posted to a message loop) must not be called. - Cancel(); - - // |forwarder_| is no longer valid after Cancel(), so re-bind. - InitializeForwarder(); - - callback_ = callback; + CallbackType callback() const { + if (!callback_) { + return CallbackType(); + } + CallbackType forwarder; + MakeForwarder(&forwarder); + return forwarder; } - // Returns a callback that can be disabled by calling Cancel(). - const base::Callback& callback() const { return forwarder_; } - private: - void Forward(A1 a1) const { callback_.Run(a1); } - - // Helper method to bind |forwarder_| using a weak pointer from - // |weak_factory_|. - void InitializeForwarder() { - forwarder_ = base::Bind(&CancelableCallback::Forward, - weak_factory_.GetWeakPtr()); - } - - // Used to ensure Forward() is not run when this object is destroyed. - base::WeakPtrFactory> weak_factory_; - - // The wrapper closure. - base::Callback forwarder_; - - // The stored closure that may be cancelled. - base::Callback callback_; - - DISALLOW_COPY_AND_ASSIGN(CancelableCallback); -}; - -template -class CancelableCallback { - public: - CancelableCallback() : weak_factory_(this) {} - - // |callback| must not be null. - explicit CancelableCallback(const base::Callback& callback) - : weak_factory_(this), callback_(callback) { - DCHECK(!callback.is_null()); - InitializeForwarder(); + template + void MakeForwarder(RepeatingCallback* out) const { + using ForwarderType = void (CancelableCallbackImpl::*)(Args...); + ForwarderType forwarder = &CancelableCallbackImpl::ForwardRepeating; + *out = BindRepeating(forwarder, weak_ptr_factory_.GetWeakPtr()); } - ~CancelableCallback() {} - - // Cancels and drops the reference to the wrapped callback. - void Cancel() { - weak_factory_.InvalidateWeakPtrs(); - forwarder_.Reset(); - callback_.Reset(); + template + void MakeForwarder(OnceCallback* out) const { + using ForwarderType = void (CancelableCallbackImpl::*)(Args...); + ForwarderType forwarder = &CancelableCallbackImpl::ForwardOnce; + *out = BindOnce(forwarder, weak_ptr_factory_.GetWeakPtr()); } - // Returns true if the wrapped callback has been cancelled. - bool IsCancelled() const { return callback_.is_null(); } - - // Sets |callback| as the closure that may be cancelled. |callback| may not - // be null. Outstanding and any previously wrapped callbacks are cancelled. - void Reset(const base::Callback& callback) { - DCHECK(!callback.is_null()); - - // Outstanding tasks (e.g., posted to a message loop) must not be called. - Cancel(); - - // |forwarder_| is no longer valid after Cancel(), so re-bind. - InitializeForwarder(); - - callback_ = callback; + template + void ForwardRepeating(Args... args) { + callback_.Run(std::forward(args)...); } - // Returns a callback that can be disabled by calling Cancel(). - const base::Callback& callback() const { return forwarder_; } - - private: - void Forward(A1 a1, A2 a2) const { callback_.Run(a1, a2); } - - // Helper method to bind |forwarder_| using a weak pointer from - // |weak_factory_|. - void InitializeForwarder() { - forwarder_ = base::Bind(&CancelableCallback::Forward, - weak_factory_.GetWeakPtr()); + template + void ForwardOnce(Args... args) { + weak_ptr_factory_.InvalidateWeakPtrs(); + std::move(callback_).Run(std::forward(args)...); } - // Used to ensure Forward() is not run when this object is destroyed. - base::WeakPtrFactory> weak_factory_; - - // The wrapper closure. - base::Callback forwarder_; - // The stored closure that may be cancelled. - base::Callback callback_; - - DISALLOW_COPY_AND_ASSIGN(CancelableCallback); + CallbackType callback_; + mutable base::WeakPtrFactory weak_ptr_factory_{this}; }; -typedef CancelableCallback CancelableClosure; +} // namespace internal + +/// +/// Consider using base::WeakPtr directly instead of base::CancelableCallback +/// for the task cancellation. +/// +template +using CancelableOnceCallback = + internal::CancelableCallbackImpl>; +using CancelableOnceClosure = CancelableOnceCallback; + +template +using CancelableRepeatingCallback = + internal::CancelableCallbackImpl>; +using CancelableRepeatingClosure = CancelableRepeatingCallback; } // namespace base diff --git a/src/include/base/cef_compiler_specific.h b/src/include/base/cef_compiler_specific.h new file mode 100644 index 000000000..4e297e92f --- /dev/null +++ b/src/include/base/cef_compiler_specific.h @@ -0,0 +1,525 @@ +// Copyright (c) 2021 Marshall A. Greenblatt. Portions copyright (c) 2012 +// Google Inc. All rights reserved. +// +// 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 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. + +#ifndef CEF_INCLUDE_BASE_CEF_COMPILER_SPECIFIC_H_ +#define CEF_INCLUDE_BASE_CEF_COMPILER_SPECIFIC_H_ +#pragma once + +#if defined(USING_CHROMIUM_INCLUDES) +// When building CEF include the Chromium header directly. +#include "base/compiler_specific.h" +#else // !USING_CHROMIUM_INCLUDES +// The following is substantially similar to the Chromium implementation. +// If the Chromium implementation diverges the below implementation should be +// updated to match. + +#include "include/base/cef_build.h" + +// A wrapper around `__has_attribute()`, which is similar to the C++20-standard +// `__has_cpp_attribute()`, but tests for support for `__attribute__(())`s. +// Compilers that do not support this (e.g. MSVC) are also assumed not to +// support `__attribute__`, so this is simply mapped to `0` there. +// +// See also: +// https://clang.llvm.org/docs/LanguageExtensions.html#has-attribute +#if defined(__has_attribute) +#define HAS_ATTRIBUTE(x) __has_attribute(x) +#else +#define HAS_ATTRIBUTE(x) 0 +#endif + +// A wrapper around `__has_builtin`, similar to `HAS_ATTRIBUTE()`. +// +// See also: +// https://clang.llvm.org/docs/LanguageExtensions.html#has-builtin +#if defined(__has_builtin) +#define HAS_BUILTIN(x) __has_builtin(x) +#else +#define HAS_BUILTIN(x) 0 +#endif + +// A wrapper around `__has_feature`, similar to `HAS_ATTRIBUTE()`. +// +// See also: +// https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension +#if defined(__has_feature) +#define HAS_FEATURE(FEATURE) __has_feature(FEATURE) +#else +#define HAS_FEATURE(FEATURE) 0 +#endif + +// Annotates a function indicating it should not be inlined. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#noinline +// +// Usage: +// ``` +// NOINLINE void Func() { +// // This body will not be inlined into callers. +// } +// ``` +#if __has_cpp_attribute(clang::noinline) +#define NOINLINE [[clang::noinline]] +#elif __has_cpp_attribute(gnu::noinline) +#define NOINLINE [[gnu::noinline]] +#elif __has_cpp_attribute(msvc::noinline) +#define NOINLINE [[msvc::noinline]] +#else +#define NOINLINE +#endif + +// Annotates a function indicating it should always be inlined. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline +// +// Usage: +// ``` +// ALWAYS_INLINE void Func() { +// // This body will be inlined into callers whenever possible. +// } +// ``` +// +// Since `ALWAYS_INLINE` is performance-oriented but can hamper debugging, +// ignore it in debug mode. +#if defined(NDEBUG) +#if __has_cpp_attribute(clang::always_inline) +#define ALWAYS_INLINE [[clang::always_inline]] inline +#elif __has_cpp_attribute(gnu::always_inline) +#define ALWAYS_INLINE [[gnu::always_inline]] inline +#elif defined(COMPILER_MSVC) +#define ALWAYS_INLINE __forceinline +#endif +#endif +#if !defined(ALWAYS_INLINE) +#define ALWAYS_INLINE inline +#endif + +// Annotates a function indicating it should never be tail called. Useful to +// make sure callers of the annotated function are never omitted from call +// stacks. Often useful with `NOINLINE` to make sure the function itself is also +// not omitted from call stacks. Note: this does not prevent code folding of +// multiple identical callers into a single signature; to do that, see +// `NO_CODE_FOLDING()` in base/debug/alias.h. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#not-tail-called +// +// Usage: +// ``` +// // Calls to this function will not be tail calls. +// NOT_TAIL_CALLED void Func(); +// ``` +#if __has_cpp_attribute(clang::not_tail_called) +#define NOT_TAIL_CALLED [[clang::not_tail_called]] +#else +#define NOT_TAIL_CALLED +#endif + +// Annotates a data member indicating it need not have an address distinct from +// all other non-static data members of the class, and its tail padding may be +// used for other objects' storage. This can have subtle and dangerous effects, +// including on containing objects; use with caution. +// +// See also: +// https://en.cppreference.com/w/cpp/language/attributes/no_unique_address +// https://wg21.link/dcl.attr.nouniqueaddr +// Usage: +// ``` +// // In the following struct, `t` might not have a unique address from `i`, +// // and `t`'s tail padding (if any) may be reused by subsequent objects. +// struct S { +// int i; +// NO_UNIQUE_ADDRESS T t; +// }; +// ``` +// +// Unfortunately MSVC ignores [[no_unique_address]] (see +// https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#msvc-extensions-and-abi), +// and clang-cl matches it for ABI compatibility reasons. We need to prefer +// [[msvc::no_unique_address]] when available if we actually want any effect. +#if __has_cpp_attribute(msvc::no_unique_address) +#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif __has_cpp_attribute(no_unique_address) +#define NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +#define NO_UNIQUE_ADDRESS +#endif + +// Annotates a function indicating it takes a `printf()`-style format string. +// The compiler will check that the provided arguments match the type specifiers +// in the format string. Useful to detect mismatched format strings/args. +// +// `format_param` is the one-based index of the format string parameter; +// `dots_param` is the one-based index of the "..." parameter. +// For `v*printf()` functions (which take a `va_list`), `dots_param` should be +// 0. For member functions, the implicit `this` parameter is at index 1. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#format +// https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-format-function-attribute +// +// Usage: +// ``` +// PRINTF_FORMAT(1, 2) +// void Print(const char* format, ...); +// void Func() { +// // The following call will not compile; diagnosed as format and argument +// // types mismatching. +// Print("%s", 1); +// } +// ``` +#if __has_cpp_attribute(gnu::format) +#define PRINTF_FORMAT(format_param, dots_param) \ + [[gnu::format(printf, format_param, dots_param)]] +#else +#define PRINTF_FORMAT(format_param, dots_param) +#endif + +// Annotates a function disabling the named sanitizer within its body. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#no-sanitize +// https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation +// +// Usage: +// ``` +// NO_SANITIZE("cfi-icall") void Func() { +// // CFI indirect call checks will not be performed in this body. +// } +// ``` +#if __has_cpp_attribute(clang::no_sanitize) +#define NO_SANITIZE(sanitizer) [[clang::no_sanitize(sanitizer)]] +#else +#define NO_SANITIZE(sanitizer) +#endif + +// Annotates a pointer and size directing MSAN to treat that memory region as +// fully initialized. Useful for e.g. code that deliberately reads uninitialized +// data, such as a GC scavenging root set pointers from the stack. +// +// See also: +// https://github.com/google/sanitizers/wiki/MemorySanitizer +// +// Usage: +// ``` +// T* ptr = ...; +// // After the next statement, MSAN will assume `ptr` points to an +// // initialized `T`. +// MSAN_UNPOISON(ptr, sizeof(T)); +// ``` +#if defined(MEMORY_SANITIZER) +#include +#define MSAN_UNPOISON(p, size) __msan_unpoison(p, size) +#else +#define MSAN_UNPOISON(p, size) +#endif + +// Annotates a pointer and size directing MSAN to check whether that memory +// region is initialized, as if it was being read from. If any bits are +// uninitialized, crashes with an MSAN report. Useful for e.g. sanitizing data +// MSAN won't be able to track, such as data that is about to be passed to +// another process via shared memory. +// +// See also: +// https://www.chromium.org/developers/testing/memorysanitizer/#debugging-msan-reports +// +// Usage: +// ``` +// T* ptr = ...; +// // The following line will crash at runtime in MSAN builds if `ptr` does +// // not point to an initialized `T`. +// MSAN_CHECK_MEM_IS_INITIALIZED(ptr, sizeof(T)); +// ``` +#if defined(MEMORY_SANITIZER) +#define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) \ + __msan_check_mem_is_initialized(p, size) +#else +#define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) +#endif + +// Annotates a function disabling Control Flow Integrity checks due to perf +// impact. +// +// See also: +// https://clang.llvm.org/docs/ControlFlowIntegrity.html#performance +// https://www.chromium.org/developers/testing/control-flow-integrity/#overhead-only-tested-on-x64 +// +// Usage: +// ``` +// DISABLE_CFI_PERF void Func() { +// // CFI checks will not be performed in this body, due to perf reasons. +// } +// ``` +#if !defined(DISABLE_CFI_PERF) +#if defined(__clang__) && defined(OFFICIAL_BUILD) +#define DISABLE_CFI_PERF NO_SANITIZE("cfi") +#else +#define DISABLE_CFI_PERF +#endif +#endif + +// Annotates a function disabling Control Flow Integrity indirect call checks. +// +// See also: +// https://clang.llvm.org/docs/ControlFlowIntegrity.html#available-schemes +// https://www.chromium.org/developers/testing/control-flow-integrity/#indirect-call-failures +// +// Usage: +// ``` +// DISABLE_CFI_ICALL void Func() { +// // CFI indirect call checks will not be performed in this body. +// } +// ``` +#if !defined(DISABLE_CFI_ICALL) +#if defined(OS_WIN) +#define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") __declspec(guard(nocf)) +#else +#define DISABLE_CFI_ICALL NO_SANITIZE("cfi-icall") +#endif +#endif + +// Evaluates to a string constant containing the function signature. +// +// See also: +// https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins +// https://en.cppreference.com/w/c/language/function_definition#func +// +// Usage: +// ``` +// void Func(int arg) { +// std::cout << PRETTY_FUNCTION; // Prints `void Func(int)` or similar. +// } +// ``` +#if defined(COMPILER_GCC) +#define PRETTY_FUNCTION __PRETTY_FUNCTION__ +#elif defined(COMPILER_MSVC) +#define PRETTY_FUNCTION __FUNCSIG__ +#else +#define PRETTY_FUNCTION __func__ +#endif + +// Annotates a variable indicating that its storage should not be filled with a +// fixed pattern when uninitialized. +// +// The `init_stack_vars` gn arg (enabled on most build configs) causes the +// compiler to generate code that writes a fixed pattern into uninitialized +// parts of all local variables, to mitigate security risks. In most cases, e.g. +// when such memory is either never accessed or will be initialized later before +// reading, the compiler is able to remove the additional stores, and any +// remaining stores are unlikely to affect program performance. +// +// If hot code suffers unavoidable perf penalties, this can disable the +// pattern-filling there. This should only be done when necessary, since reads +// from uninitialized variables are not only UB, they can in practice allow +// attackers to control logic by pre-filling the variable's memory with a +// desirable value. +// +// NOTE: This behavior also increases the likelihood the compiler will generate +// `memcpy()`/`memset()` calls to init variables. If this causes link errors for +// targets that don't link against the CRT, this macro can help; you may instead +// want 'configs -= [ "//build/config/compiler:default_init_stack_vars" ]' in +// the relevant .gn file to disable this on the whole target. +// +// See also: +// https://source.chromium.org/chromium/chromium/src/+/main:build/config/compiler/BUILD.gn;l=3088;drc=24ccaf63ff5b1883be1ebe5f979d917ce28b0131 +// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-ftrivial-auto-var-init +// https://clang.llvm.org/docs/AttributeReference.html#uninitialized +// +// Usage: +// ``` +// // The following line declares `i` without ensuring it initially contains +// // any particular pattern. +// STACK_UNINITIALIZED int i; +// ``` +#if __has_cpp_attribute(clang::uninitialized) +#define STACK_UNINITIALIZED [[clang::uninitialized]] +#elif __has_cpp_attribute(gnu::uninitialized) +#define STACK_UNINITIALIZED [[gnu::uninitialized]] +#else +#define STACK_UNINITIALIZED +#endif + +// Annotates a function disabling stack canary checks. +// +// The `-fstack-protector` compiler flag (passed on most non-Windows builds) +// causes the compiler to extend some function prologues and epilogues to set +// and check a canary value, to detect stack buffer overflows and crash in +// response. If hot code suffers unavoidable perf penalties, or intentionally +// modifies the canary value, this can disable the behavior there. +// +// See also: +// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fstack-protector +// https://clang.llvm.org/docs/AttributeReference.html#no-stack-protector-safebuffers +// +// Usage: +// ``` +// NO_STACK_PROTECTOR void Func() { +// // Stack canary checks will not be performed in this body. +// } +// ``` +#if __has_cpp_attribute(gnu::no_stack_protector) +#define NO_STACK_PROTECTOR [[gnu::no_stack_protector]] +#elif __has_cpp_attribute(gnu::optimize) +#define NO_STACK_PROTECTOR [[gnu::optimize("-fno-stack-protector")]] +#else +#define NO_STACK_PROTECTOR +#endif + +// Annotates a codepath suppressing static analysis along that path. Useful when +// code is safe in practice for reasons the analyzer can't detect, e.g. because +// the condition leading to that path guarantees a param is non-null. +// +// Usage: +// ``` +// if (cond) { +// ANALYZER_SKIP_THIS_PATH(); +// // Static analysis will be disabled for the remainder of this block. +// delete ptr; +// } +// ``` +#if defined(__clang_analyzer__) +inline constexpr bool AnalyzerNoReturn() +#if HAS_ATTRIBUTE(analyzer_noreturn) + __attribute__((analyzer_noreturn)) +#endif +{ + return false; +} +#define ANALYZER_SKIP_THIS_PATH() static_cast(::AnalyzerNoReturn()) +#else +// The above definition would be safe even outside the analyzer, but defining +// the macro away entirely avoids the need for the optimizer to eliminate it. +#define ANALYZER_SKIP_THIS_PATH() +#endif + +// Annotates a condition directing static analysis to assume it is always true. +// Evaluates to the provided `arg` as a `bool`. +// +// Usage: +// ``` +// // Static analysis will assume the following condition always holds. +// if (ANALYZER_ASSUME_TRUE(cond)) ... +// ``` +#if defined(__clang_analyzer__) +inline constexpr bool AnalyzerAssumeTrue(bool arg) { + return arg || AnalyzerNoReturn(); +} +#define ANALYZER_ASSUME_TRUE(arg) ::AnalyzerAssumeTrue(!!(arg)) +#else +// Again, the above definition is safe, this is just simpler for the optimizer. +#define ANALYZER_ASSUME_TRUE(arg) (arg) +#endif + +// Annotates a function, function pointer, or statement to disallow +// optimizations that merge calls. Useful to ensure the source locations of such +// calls are not obscured. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#nomerge +// +// Usage: +// ``` +// NOMERGE void Func(); // No direct calls to `Func()` will be merged. +// +// using Ptr = decltype(&Func); +// NOMERGE Ptr ptr = &Func; // No calls through `ptr` will be merged. +// +// NOMERGE if (cond) { +// // No calls in this block will be merged. +// } +// ``` +#if __has_cpp_attribute(clang::nomerge) +#define NOMERGE [[clang::nomerge]] +#else +#define NOMERGE +#endif + +// Annotates a type as being suitable for passing in registers despite having a +// non-trivial copy or move constructor or destructor. This requires the type +// not be concerned about its address remaining constant, be safely usable after +// copying its memory, and have a destructor that may be safely omitted on +// moved-from instances; an example is `std::unique_ptr`. Unnecessary if the +// copy/move constructor(s) and destructor are unconditionally trivial; likely +// ineffective if the type is too large to be passed in one or two registers +// with the target ABI. +// +// NOTE: Use with caution; this has subtle effects on constructor/destructor +// ordering. When used with types passed or returned by value, values may be +// constructed in the source stack frame, passed in a register, and then used +// and destroyed in the target stack frame. +// +// See also: +// https://clang.llvm.org/docs/AttributeReference.html#trivial-abi +// https://libcxx.llvm.org/docs/DesignDocs/UniquePtrTrivialAbi.html +// +// Usage: +// ``` +// // Instances of type `S` will be eligible to be passed in registers despite +// // `S`'s nontrivial destructor. +// struct TRIVIAL_ABI S { ~S(); } +// ``` +#if __has_cpp_attribute(clang::trivial_abi) +#define TRIVIAL_ABI [[clang::trivial_abi]] +#else +#define TRIVIAL_ABI +#endif + +// Annotates a member function as safe to call on a moved-from object, which it +// will reinitialize. +// +// See also: +// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html#reinitialization +// +// Usage: +// ``` +// struct S { +// REINITIALIZES_AFTER_MOVE void Reset(); +// }; +// void Func1(const S&); +// void Func2() { +// S s1; +// S s2 = std::move(s1); +// s1.Reset(); +// // clang-tidy's `bugprone-use-after-move` check will not flag the +// // following call as a use-after-move, due to the intervening `Reset()`. +// Func1(s1); +// } +// ``` +#if __has_cpp_attribute(clang::reinitializes) +#define REINITIALIZES_AFTER_MOVE [[clang::reinitializes]] +#else +#define REINITIALIZES_AFTER_MOVE +#endif + +#endif // !USING_CHROMIUM_INCLUDES + +#endif // CEF_INCLUDE_BASE_CEF_COMPILER_SPECIFIC_H_ diff --git a/src/include/base/cef_dump_without_crashing.h b/src/include/base/cef_dump_without_crashing.h new file mode 100644 index 000000000..80bd685e2 --- /dev/null +++ b/src/include/base/cef_dump_without_crashing.h @@ -0,0 +1,92 @@ +// Copyright (c) 2024 Marshall A. Greenblatt. Portions copyright (c) 2012 +// Google Inc. All rights reserved. +// +// 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 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. + +/// +/// \file +/// Provides functions for generating crash dumps. +/// +/// NOTE: The contents of this file are only available to applications that link +/// against the libcef_dll_wrapper target. +/// +/// NOTE: Ensure crash reporting is configured before use. See +/// https://chromiumembedded.github.io/cef/crash_reporting +/// for more information +/// +/// WARNING: Crash reporting should not be used in the main/browser process +/// before calling CefInitialize or in sub-processes before CefExecuteProcess. +/// + +#ifndef CEF_INCLUDE_BASE_CEF_DUMP_WITHOUT_CRASHING_H_ +#define CEF_INCLUDE_BASE_CEF_DUMP_WITHOUT_CRASHING_H_ +#pragma once + +#include "include/cef_api_hash.h" + +constexpr long long kOneDayInMilliseconds = 86400000; + +/// +/// This function allows for generating of crash dumps with a throttling +/// mechanism, preventing frequent dumps from being generated in a short period +/// of time from the same location. If should only be called after CefInitialize +/// has been successfully called. The |function_name|, |file_name|, and +/// |line_number| parameters specify the origin location of the dump. The +/// |mseconds_between_dumps| is an interval between consecutive dumps in +/// milliseconds from the same location. +/// +/// Returns true if the dump was successfully generated, false otherwise. +/// +/// For detailed behavior, usage instructions, and considerations, refer to the +/// documentation of DumpWithoutCrashing in base/debug/dump_without_crashing.h. +/// +bool CefDumpWithoutCrashing( + long long mseconds_between_dumps = kOneDayInMilliseconds, + const char* function_name = __builtin_FUNCTION(), + const char* file_name = __builtin_FILE(), + int line_number = __builtin_LINE()); + +#if CEF_API_REMOVED(13500) +/// +/// This function allows for generating of crash dumps without any throttling +/// constraints. If should also only be called after CefInitialize has been +/// successfully called. +/// +/// Returns true if the dump was successfully generated, false otherwise. +/// +/// For detailed behavior, usage instructions, and considerations, refer to the +/// documentation of DumpWithoutCrashingUnthrottled in +/// base/debug/dump_without_crashing.h. +/// +/// This function is removed in API version 13500. Use CefDumpWithoutCrashing() +/// instead. +/// +bool CefDumpWithoutCrashingUnthrottled(); +#endif + +#endif // CEF_INCLUDE_BASE_CEF_DUMP_WITHOUT_CRASHING_H_ diff --git a/src/include/base/cef_immediate_crash.h b/src/include/base/cef_immediate_crash.h new file mode 100644 index 000000000..0197d3cca --- /dev/null +++ b/src/include/base/cef_immediate_crash.h @@ -0,0 +1,197 @@ +// Copyright (c) 2025 Marshall A. Greenblatt. Portions copyright (c) 2019 +// Google Inc. All rights reserved. +// +// 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 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. + +#ifndef CEF_INCLUDE_BASE_CEF_IMMEDIATE_CRASH_H_ +#define CEF_INCLUDE_BASE_CEF_IMMEDIATE_CRASH_H_ +#pragma once + +#if defined(USING_CHROMIUM_INCLUDES) +// When building CEF include the Chromium header directly. +#include "base/immediate_crash.h" +#else // !USING_CHROMIUM_INCLUDES +// The following is substantially similar to the Chromium implementation. +// If the Chromium implementation diverges the below implementation should be +// updated to match. + +#include "include/base/cef_build.h" + +#if defined(OS_WIN) +#include +#endif + +// Crashes in the fastest possible way with no attempt at logging. +// There are several constraints; see http://crbug.com/664209 for more context. +// +// - TRAP_SEQUENCE_() must be fatal. It should not be possible to ignore the +// resulting exception or simply hit 'continue' to skip over it in a debugger. +// - Different instances of TRAP_SEQUENCE_() must not be folded together, to +// ensure crash reports are debuggable. Unlike __builtin_trap(), asm volatile +// blocks will not be folded together. +// Note: TRAP_SEQUENCE_() previously required an instruction with a unique +// nonce since unlike clang, GCC folds together identical asm volatile +// blocks. +// - TRAP_SEQUENCE_() must produce a signal that is distinct from an invalid +// memory access. +// - TRAP_SEQUENCE_() must be treated as a set of noreturn instructions. +// __builtin_unreachable() is used to provide that hint here. clang also uses +// this as a heuristic to pack the instructions in the function epilogue to +// improve code density. +// - base::ImmediateCrash() is used in allocation hooks. To prevent recursions, +// TRAP_SEQUENCE_() must not allocate. +// +// Additional properties that are nice to have: +// - TRAP_SEQUENCE_() should be as compact as possible. +// - The first instruction of TRAP_SEQUENCE_() should not change, to avoid +// shifting crash reporting clusters. As a consequence of this, explicit +// assembly is preferred over intrinsics. +// Note: this last bullet point may no longer be true, and may be removed in +// the future. + +// Note: TRAP_SEQUENCE Is currently split into two macro helpers due to the fact +// that clang emits an actual instruction for __builtin_unreachable() on certain +// platforms (see https://crbug.com/958675). In addition, the int3/bkpt/brk will +// be removed in followups, so splitting it up like this now makes it easy to +// land the followups. + +#if defined(COMPILER_GCC) + +#if defined(ARCH_CPU_X86_FAMILY) + +// TODO(crbug.com/40625592): In theory, it should be possible to use just +// int3. However, there are a number of crashes with SIGILL as the exception +// code, so it seems likely that there's a signal handler that allows execution +// to continue after SIGTRAP. +#define TRAP_SEQUENCE1_() asm volatile("int3") + +#if defined(OS_APPLE) +// Intentionally empty: __builtin_unreachable() is always part of the sequence +// (see IMMEDIATE_CRASH below) and already emits a ud2 on Mac. +#define TRAP_SEQUENCE2_() asm volatile("") +#else +#define TRAP_SEQUENCE2_() asm volatile("ud2") +#endif // defined(OS_APPLE) + +#elif defined(ARCH_CPU_ARMEL) + +// bkpt will generate a SIGBUS when running on armv7 and a SIGTRAP when running +// as a 32 bit userspace app on arm64. There doesn't seem to be any way to +// cause a SIGTRAP from userspace without using a syscall (which would be a +// problem for sandboxing). +// TODO(crbug.com/40625592): Remove bkpt from this sequence. +#define TRAP_SEQUENCE1_() asm volatile("bkpt #0") +#define TRAP_SEQUENCE2_() asm volatile("udf #0") + +#elif defined(ARCH_CPU_ARM64) + +// This will always generate a SIGTRAP on arm64. +// TODO(crbug.com/40625592): Remove brk from this sequence. +#define TRAP_SEQUENCE1_() asm volatile("brk #0") +#define TRAP_SEQUENCE2_() asm volatile("hlt #0") + +#else + +// Crash report accuracy will not be guaranteed on other architectures, but at +// least this will crash as expected. +#define TRAP_SEQUENCE1_() __builtin_trap() +#define TRAP_SEQUENCE2_() asm volatile("") + +#endif // ARCH_CPU_* + +#elif defined(COMPILER_MSVC) + +#if !defined(__clang__) + +// MSVC x64 doesn't support inline asm, so use the MSVC intrinsic. +#define TRAP_SEQUENCE1_() __debugbreak() +#define TRAP_SEQUENCE2_() + +#elif defined(ARCH_CPU_ARM64) + +// Windows ARM64 uses "BRK #F000" as its breakpoint instruction, and +// __debugbreak() generates that in both VC++ and clang. +#define TRAP_SEQUENCE1_() __debugbreak() +// Intentionally empty: __builtin_unreachable() is always part of the sequence +// (see IMMEDIATE_CRASH below) and already emits a ud2 on Win64, +// https://crbug.com/958373 +#define TRAP_SEQUENCE2_() __asm volatile("") + +#else + +#define TRAP_SEQUENCE1_() asm volatile("int3") +#define TRAP_SEQUENCE2_() asm volatile("ud2") + +#endif // __clang__ + +#else + +#error No supported trap sequence! + +#endif // COMPILER_GCC + +#define TRAP_SEQUENCE_() \ + do { \ + TRAP_SEQUENCE1_(); \ + TRAP_SEQUENCE2_(); \ + } while (false) + +// This version of ALWAYS_INLINE inlines even in is_debug=true. +// TODO(pbos): See if NDEBUG can be dropped from ALWAYS_INLINE as well, and if +// so merge. Otherwise document why it cannot inline in debug in +// base/compiler_specific.h. +#if defined(COMPILER_GCC) +#define IMMEDIATE_CRASH_ALWAYS_INLINE inline __attribute__((__always_inline__)) +#elif defined(COMPILER_MSVC) +#define IMMEDIATE_CRASH_ALWAYS_INLINE __forceinline +#else +#define IMMEDIATE_CRASH_ALWAYS_INLINE inline +#endif + +namespace base { + +[[noreturn]] IMMEDIATE_CRASH_ALWAYS_INLINE void ImmediateCrash() { +#if defined(OS_WIN) + // We can't use abort() on Windows because it results in the + // abort/retry/ignore dialog which disrupts automated tests. + // TODO(crbug.com/40948553): investigate if such dialogs can + // be suppressed + TRAP_SEQUENCE_(); +#if defined(__clang__) || defined(COMPILER_GCC) + __builtin_unreachable(); +#endif // defined(__clang__) || defined(COMPILER_GCC) +#else // !defined(OS_WIN) + abort(); +#endif // !defined(OS_WIN) +} + +} // namespace base + +#endif // !USING_CHROMIUM_INCLUDES + +#endif // CEF_INCLUDE_BASE_CEF_IMMEDIATE_CRASH_H_ diff --git a/src/include/base/cef_is_complete.h b/src/include/base/cef_is_complete.h new file mode 100644 index 000000000..204dd8d1e --- /dev/null +++ b/src/include/base/cef_is_complete.h @@ -0,0 +1,58 @@ +// Copyright (c) 2024 Marshall A. Greenblatt. Portions copyright (c) 2024 +// Google Inc. All rights reserved. +// +// 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 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. + +#ifndef CEF_INCLUDE_BASE_CEF_IS_COMPLETE_H_ +#define CEF_INCLUDE_BASE_CEF_IS_COMPLETE_H_ +#pragma once + +#if defined(USING_CHROMIUM_INCLUDES) +// When building CEF include the Chromium header directly. +#include "base/types/is_complete.h" +#else // !USING_CHROMIUM_INCLUDES +// The following is substantially similar to the Chromium implementation. +// If the Chromium implementation diverges the below implementation should be +// updated to match. + +#include + +namespace base { + +/// True if `T` is completely defined. +template +concept IsComplete = requires { sizeof(T); } || + // Function types must be included explicitly since you + // cannot apply `sizeof()` to a function type. + std::is_function_v>; + +} // namespace base + +#endif // !USING_CHROMIUM_INCLUDES + +#endif // CEF_INCLUDE_BASE_CEF_IS_COMPLETE_H_ diff --git a/src/include/base/cef_is_instantiation.h b/src/include/base/cef_is_instantiation.h new file mode 100644 index 000000000..d7af7360f --- /dev/null +++ b/src/include/base/cef_is_instantiation.h @@ -0,0 +1,73 @@ +// Copyright (c) 2023 Marshall A. Greenblatt. Portions copyright (c) 2023 +// Google Inc. All rights reserved. +// +// 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 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. + +#ifndef CEF_INCLUDE_BASE_CEF_IS_INSTANTIATION_H_ +#define CEF_INCLUDE_BASE_CEF_IS_INSTANTIATION_H_ +#pragma once + +#if defined(USING_CHROMIUM_INCLUDES) +// When building CEF include the Chromium header directly. +#include "base/types/is_instantiation.h" +#else // !USING_CHROMIUM_INCLUDES +// The following is substantially similar to the Chromium implementation. +// If the Chromium implementation diverges the below implementation should be +// updated to match. + +#include + +namespace base { +namespace cef_internal { + +// True if and only if `T` is `C` for some set of types, i.e. `T` is +// an instantiation of the template `C`. +// +// This is false by default. We specialize it to true below for pairs of +// arguments that satisfy the condition. +template class C> +inline constexpr bool is_instantiation_v = false; + +template